From 56e4f007058013a83df7ec4b857f1246a6f265af Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 25 Feb 2018 22:22:11 +0200 Subject: [PATCH] Add sync command and move commands around --- mautrix_telegram/commands/__init__.py | 2 +- mautrix_telegram/commands/meta.py | 19 +-- mautrix_telegram/commands/portal.py | 182 ++++++++++++++++++++++++++ mautrix_telegram/commands/telegram.py | 172 ++---------------------- 4 files changed, 207 insertions(+), 168 deletions(-) create mode 100644 mautrix_telegram/commands/portal.py diff --git a/mautrix_telegram/commands/__init__.py b/mautrix_telegram/commands/__init__.py index 3e456c9b..9f6f7c42 100644 --- a/mautrix_telegram/commands/__init__.py +++ b/mautrix_telegram/commands/__init__.py @@ -1,2 +1,2 @@ from .handler import command_handler, CommandHandler -from . import clean_rooms, auth, meta, telegram +from . import clean_rooms, auth, meta, telegram, portal diff --git a/mautrix_telegram/commands/meta.py b/mautrix_telegram/commands/meta.py index 92f10577..9171f9ce 100644 --- a/mautrix_telegram/commands/meta.py +++ b/mautrix_telegram/commands/meta.py @@ -54,18 +54,19 @@ def help(evt): **logout** - Log out from Telegram. **ping** - Check if you're logged into Telegram. -#### Initiating chats +#### Miscellaneous things **search** [_-r|--remote_] <_query_> - Search your contacts or the Telegram servers for users. -**pm** <_identifier_> - Open a private chat with the given Telegram user. The - identifier is either the internal user ID, the username or - the phone number. -**join** <_link_> - Join a chat with an invite link. -**create** [_type_] - Create a Telegram chat of the given type for the current - Matrix room. The type is either `group`, `supergroup` or - `channel` (defaults to `group`). +**sync** [`chats`|`contacts`|`me`] - Synchronize your chat portals, contacts and/or own info. +**ping-bot** - Get info of the message relay Telegram bot. + +#### Initiating chats +**pm** <_identifier_> - Open a private chat with the given Telegram user. The identifier is either + the internal user ID, the username or the phone number. +**join** <_link_> - Join a chat with an invite link. +**create** [_type_] - Create a Telegram chat of the given type for the current Matrix room. The + type is either `group`, `supergroup` or `channel` (defaults to `group`). #### Portal management -**ping-bot** - Get info of the message relay Telegram bot. **upgrade** - Upgrade a normal Telegram group to a supergroup. **invite-link** - Get a Telegram invite link to the current chat. **delete-portal** - Forget the current portal room. Only works for group chats; to delete diff --git a/mautrix_telegram/commands/portal.py b/mautrix_telegram/commands/portal.py new file mode 100644 index 00000000..04f2e5ca --- /dev/null +++ b/mautrix_telegram/commands/portal.py @@ -0,0 +1,182 @@ +# -*- coding: future_fstrings -*- +# mautrix-telegram - A Matrix-Telegram puppeting bridge +# Copyright (C) 2018 Tulir Asokan +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +from telethon.errors import * + +from .. import portal as po +from . import command_handler + + +@command_handler() +async def invite_link(evt): + portal = po.Portal.get_by_mxid(evt.room_id) + if not portal: + return await evt.reply("This is not a portal room.") + + if portal.peer_type == "user": + return await evt.reply("You can't invite users to private chats.") + + try: + link = await portal.get_invite_link(evt.sender) + return await evt.reply(f"Invite link to {portal.title}: {link}") + except ValueError as e: + return await evt.reply(e.args[0]) + except ChatAdminRequiredError: + return await evt.reply("You don't have the permission to create an invite link.") + + +@command_handler(needs_admin=True) +async def delete_portal(evt): + room_id = evt.args[0] if len(evt.args) > 0 else evt.room_id + + portal = po.Portal.get_by_mxid(room_id) + if not portal: + that_this = "This" if room_id == evt.room_id else "That" + return await evt.reply(f"{that_this} is not a portal room.") + + async def post_confirm(confirm): + evt.sender.command_status = None + if len(confirm.args) > 0 and confirm.args[0] == "confirm-delete": + await portal.cleanup_and_delete() + if confirm.room_id != room_id: + return await confirm.reply("Portal successfully deleted.") + else: + return await confirm.reply("Portal deletion cancelled.") + + evt.sender.command_status = { + "next": post_confirm, + "action": "Portal deletion", + } + return await evt.reply("Please confirm deletion of portal " + f"[{room_id}](https://matrix.to/#/{room_id}) " + f"to Telegram chat \"{portal.title}\" " + "by typing `$cmdprefix+sp confirm-delete`") + + +async def _get_initial_state(evt): + state = await evt.az.intent.get_room_state(evt.room_id) + title = None + about = None + levels = None + for event in state: + if event["type"] == "m.room.name": + title = event["content"]["name"] + elif event["type"] == "m.room.topic": + about = event["content"]["topic"] + elif event["type"] == "m.room.power_levels": + levels = event["content"] + return title, about, levels + + +def _check_power_levels(levels, bot_mxid): + try: + if levels["users"][bot_mxid] < 100: + raise ValueError() + except (TypeError, KeyError, ValueError): + return (f"Please give [the bridge bot](https://matrix.to/#/{bot_mxid}) a power level of " + "100 before creating a Telegram chat.") + + for user, level in levels["users"].items(): + if level >= 100 and user != bot_mxid: + return (f"Please make sure only the bridge bot has power level above 99 before " + f"creating a Telegram chat.\n\n" + f"Use power level 95 instead of 100 for admins.") + + +@command_handler() +async def create(evt): + type = evt.args[0] if len(evt.args) > 0 else "group" + if type not in {"chat", "group", "supergroup", "channel"}: + return await evt.reply( + "**Usage:** `$cmdprefix+sp create ['group'/'supergroup'/'channel']`") + + if po.Portal.get_by_mxid(evt.room_id): + return await evt.reply("This is already a portal room.") + + title, about, levels = await _get_initial_state(evt) + if not title: + return await evt.reply("Please set a title before creating a Telegram chat.") + + power_level_error = _check_power_levels(levels, evt.az.bot_mxid) + if power_level_error: + return await evt.reply(power_level_error) + + supergroup = type == "supergroup" + type = { + "supergroup": "channel", + "channel": "channel", + "chat": "chat", + "group": "chat", + }[type] + + portal = po.Portal(tgid=None, mxid=evt.room_id, title=title, about=about, peer_type=type) + try: + await portal.create_telegram_chat(evt.sender, supergroup=supergroup) + except ValueError as e: + portal.delete() + return await evt.reply(e.args[0]) + return await evt.reply(f"Telegram chat created. ID: {portal.tgid}") + + +@command_handler() +async def upgrade(evt): + portal = po.Portal.get_by_mxid(evt.room_id) + if not portal: + return await evt.reply("This is not a portal room.") + elif portal.peer_type == "channel": + return await evt.reply("This is already a supergroup or a channel.") + elif portal.peer_type == "user": + return await evt.reply("You can't upgrade private chats.") + + try: + await portal.upgrade_telegram_chat(evt.sender) + return await evt.reply(f"Group upgraded to supergroup. New ID: {portal.tgid}") + except ChatAdminRequiredError: + return await evt.reply("You don't have the permission to upgrade this group.") + except ValueError as e: + return await evt.reply(e.args[0]) + + +@command_handler() +async def group_name(evt): + if len(evt.args) == 0: + return await evt.reply("**Usage:** `$cmdprefix+sp group-name `") + + portal = po.Portal.get_by_mxid(evt.room_id) + if not portal: + return await evt.reply("This is not a portal room.") + elif portal.peer_type != "channel": + return await evt.reply("Only channels and supergroups have usernames.") + + try: + await portal.set_telegram_username(evt.sender, + evt.args[0] if evt.args[0] != "-" else "") + if portal.username: + return await evt.reply(f"Username of channel changed to {portal.username}.") + else: + return await evt.reply(f"Channel is now private.") + except ChatAdminRequiredError: + return await evt.reply( + "You don't have the permission to set the username of this channel.") + except UsernameNotModifiedError: + if portal.username: + return await evt.reply("That is already the username of this channel.") + else: + return await evt.reply("This channel is already private") + except UsernameOccupiedError: + return await evt.reply("That username is already in use.") + except UsernameInvalidError: + return await evt.reply("Invalid username") diff --git a/mautrix_telegram/commands/telegram.py b/mautrix_telegram/commands/telegram.py index 43f663a2..21b3f6fc 100644 --- a/mautrix_telegram/commands/telegram.py +++ b/mautrix_telegram/commands/telegram.py @@ -75,52 +75,6 @@ async def private_message(evt): f"{pu.Puppet.get_displayname(user, False)}") -@command_handler() -async def invite_link(evt): - portal = po.Portal.get_by_mxid(evt.room_id) - if not portal: - return await evt.reply("This is not a portal room.") - - if portal.peer_type == "user": - return await evt.reply("You can't invite users to private chats.") - - try: - link = await portal.get_invite_link(evt.sender) - return await evt.reply(f"Invite link to {portal.title}: {link}") - except ValueError as e: - return await evt.reply(e.args[0]) - except ChatAdminRequiredError: - return await evt.reply("You don't have the permission to create an invite link.") - - -@command_handler(needs_admin=True) -async def delete_portal(evt): - room_id = evt.args[0] if len(evt.args) > 0 else evt.room_id - - portal = po.Portal.get_by_mxid(room_id) - if not portal: - that_this = "This" if room_id == evt.room_id else "That" - return await evt.reply(f"{that_this} is not a portal room.") - - async def post_confirm(confirm): - evt.sender.command_status = None - if len(confirm.args) > 0 and confirm.args[0] == "confirm-delete": - await portal.cleanup_and_delete() - if confirm.room_id != room_id: - return await confirm.reply("Portal successfully deleted.") - else: - return await confirm.reply("Portal deletion cancelled.") - - evt.sender.command_status = { - "next": post_confirm, - "action": "Portal deletion", - } - return await evt.reply("Please confirm deletion of portal " - f"[{room_id}](https://matrix.to/#/{room_id}) " - f"to Telegram chat \"{portal.title}\" " - "by typing `$cmdprefix+sp confirm-delete`") - - async def _join(evt, arg): if arg.startswith("joinchat/"): invite_hash = arg[len("joinchat/"):] @@ -166,117 +120,19 @@ async def join(evt): return await evt.reply(f"Created room for {portal.title}") -async def _get_initial_state(evt): - state = await evt.az.intent.get_room_state(evt.room_id) - title = None - about = None - levels = None - for event in state: - if event["type"] == "m.room.name": - title = event["content"]["name"] - elif event["type"] == "m.room.topic": - about = event["content"]["topic"] - elif event["type"] == "m.room.power_levels": - levels = event["content"] - return title, about, levels - - -def _check_power_levels(levels, bot_mxid): - try: - if levels["users"][bot_mxid] < 100: - raise ValueError() - except (TypeError, KeyError, ValueError): - return (f"Please give [the bridge bot](https://matrix.to/#/{bot_mxid}) a power level of " - "100 before creating a Telegram chat.") - - for user, level in levels["users"].items(): - if level >= 100 and user != bot_mxid: - return (f"Please make sure only the bridge bot has power level above 99 before " - f"creating a Telegram chat.\n\n" - f"Use power level 95 instead of 100 for admins.") - - @command_handler() -async def create(evt): - type = evt.args[0] if len(evt.args) > 0 else "group" - if type not in {"chat", "group", "supergroup", "channel"}: - return await evt.reply( - "**Usage:** `$cmdprefix+sp create ['group'/'supergroup'/'channel']`") +async def sync(evt): + if len(evt.args) > 0: + sync_only = evt.args[0] + if sync_only not in ("chats", "contacts", "me"): + return await evt.reply("**Usage:** `$cmdprefix+sp sync [chats|contacts|me]`") + else: + sync_only = None - if po.Portal.get_by_mxid(evt.room_id): - return await evt.reply("This is already a portal room.") - - title, about, levels = await _get_initial_state(evt) - if not title: - return await evt.reply("Please set a title before creating a Telegram chat.") - - power_level_error = _check_power_levels(levels, evt.az.bot_mxid) - if power_level_error: - return await evt.reply(power_level_error) - - supergroup = type == "supergroup" - type = { - "supergroup": "channel", - "channel": "channel", - "chat": "chat", - "group": "chat", - }[type] - - portal = po.Portal(tgid=None, mxid=evt.room_id, title=title, about=about, peer_type=type) - try: - await portal.create_telegram_chat(evt.sender, supergroup=supergroup) - except ValueError as e: - portal.delete() - return await evt.reply(e.args[0]) - return await evt.reply(f"Telegram chat created. ID: {portal.tgid}") - - -@command_handler() -async def upgrade(evt): - portal = po.Portal.get_by_mxid(evt.room_id) - if not portal: - return await evt.reply("This is not a portal room.") - elif portal.peer_type == "channel": - return await evt.reply("This is already a supergroup or a channel.") - elif portal.peer_type == "user": - return await evt.reply("You can't upgrade private chats.") - - try: - await portal.upgrade_telegram_chat(evt.sender) - return await evt.reply(f"Group upgraded to supergroup. New ID: {portal.tgid}") - except ChatAdminRequiredError: - return await evt.reply("You don't have the permission to upgrade this group.") - except ValueError as e: - return await evt.reply(e.args[0]) - - -@command_handler() -async def group_name(evt): - if len(evt.args) == 0: - return await evt.reply("**Usage:** `$cmdprefix+sp group-name `") - - portal = po.Portal.get_by_mxid(evt.room_id) - if not portal: - return await evt.reply("This is not a portal room.") - elif portal.peer_type != "channel": - return await evt.reply("Only channels and supergroups have usernames.") - - try: - await portal.set_telegram_username(evt.sender, - evt.args[0] if evt.args[0] != "-" else "") - if portal.username: - return await evt.reply(f"Username of channel changed to {portal.username}.") - else: - return await evt.reply(f"Channel is now private.") - except ChatAdminRequiredError: - return await evt.reply( - "You don't have the permission to set the username of this channel.") - except UsernameNotModifiedError: - if portal.username: - return await evt.reply("That is already the username of this channel.") - else: - return await evt.reply("This channel is already private") - except UsernameOccupiedError: - return await evt.reply("That username is already in use.") - except UsernameInvalidError: - return await evt.reply("Invalid username") + if not sync_only or sync_only == "chats": + await evt.sender.sync_dialogs() + if not sync_only or sync_only == "contacts": + await evt.sender.sync_contacts() + if not sync_only or sync_only == "me": + await evt.sender.update_info() + return await evt.reply("Synchronization complete.")