# mautrix-telegram - A Matrix-Telegram puppeting bridge # Copyright (C) 2019 Tulir Asokan # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from typing import Optional, List, Tuple from datetime import timedelta, datetime import re from telethon.tl.functions.channels import GetFullChannelRequest from telethon.tl.functions.messages import GetFullChatRequest from telethon.errors import (ChatAdminRequiredError, UsernameInvalidError, UsernameNotModifiedError, UsernameOccupiedError, RPCError) from mautrix.types import EventID from ... import portal as po from .. import command_handler, CommandEvent, SECTION_PORTAL_MANAGEMENT, SECTION_MISC from .util import user_has_power_level @command_handler(needs_admin=False, needs_puppeting=False, needs_auth=False, help_section=SECTION_MISC, help_text="Fetch Matrix room state to ensure the bridge has up-to-date info.") async def sync_state(evt: CommandEvent) -> EventID: portal = po.Portal.get_by_mxid(evt.room_id) if not portal: return await evt.reply("This is not a portal room.") elif not await user_has_power_level(evt.room_id, evt.az.intent, evt.sender, "bridge"): return await evt.reply(f"You do not have the permissions to synchronize this room.") await portal.main_intent.get_joined_members(portal.mxid) await evt.reply("Synchronization complete") @command_handler(needs_admin=False, needs_puppeting=False, needs_auth=False, help_section=SECTION_MISC) async def sync_full(evt: CommandEvent) -> EventID: portal = po.Portal.get_by_mxid(evt.room_id) if not portal: return await evt.reply("This is not a portal room.") if len(evt.args) > 0 and evt.args[0] == "--usebot" and evt.sender.is_admin: src = evt.tgbot else: src = evt.tgbot if await evt.sender.needs_relaybot(portal) else evt.sender try: if portal.peer_type == "channel": res = await src.client(GetFullChannelRequest(portal.peer)) elif portal.peer_type == "chat": res = await src.client(GetFullChatRequest(portal.tgid)) else: return await evt.reply("This is not a channel or chat portal.") except (ValueError, RPCError): return await evt.reply("Failed to get portal info from Telegram.") await portal.update_matrix_room(src, res.full_chat) return await evt.reply("Portal synced successfully.") @command_handler(name="id", needs_admin=False, needs_puppeting=False, needs_auth=False, help_section=SECTION_MISC, help_text="Get the ID of the Telegram chat where this room is bridged.") async def get_id(evt: CommandEvent) -> EventID: portal = po.Portal.get_by_mxid(evt.room_id) if not portal: return await evt.reply("This is not a portal room.") tgid = portal.tgid if portal.peer_type == "chat": tgid = -tgid elif portal.peer_type == "channel": tgid = f"-100{tgid}" await evt.reply(f"This room is bridged to Telegram chat ID `{tgid}`.") invite_link_usage = ("**Usage:** `$cmdprefix+sp invite-link [--uses=] [--expire=]`" "\n\n" "* `--uses`: the number of times the invite link can be used." " Defaults to unlimited.\n" "* `--expire`: the duration after which the link will expire." " A number suffixed with d(ay), h(our), m(inute) or s(econd)") def _parse_flag(args: List[str]) -> Tuple[str, str]: arg = args.pop(0).lower() if arg.startswith("--"): value_start = arg.index("=") if value_start: flag = arg[2:value_start] value = arg[value_start+1:] else: flag = arg[2:] value = args.pop(0).lower() elif arg.startswith("-"): flag = arg[1] if len(arg) > 3 and arg[2] == "=": value = arg[3:] else: value = args.pop(0).lower() else: raise ValueError("invalid flag") return flag, value delta_regex = re.compile("([0-9]+)(w(?:eek)?|d(?:ay)?|h(?:our)?|m(?:in(?:ute)?)?|s(?:ec(?:ond)?)?)") def _parse_delta(value: str) -> Optional[timedelta]: match = delta_regex.fullmatch(value) if not match: return None number = int(match.group(1)) unit = match.group(2)[0] if unit == "w": return timedelta(weeks=number) elif unit == "d": return timedelta(days=number) elif unit == "h": return timedelta(hours=number) elif unit == "m": return timedelta(minutes=number) elif unit == "s": return timedelta(seconds=number) else: return None @command_handler(help_section=SECTION_PORTAL_MANAGEMENT, help_text="Get a Telegram invite link to the current chat.", help_args="[--uses=] [--expire=