Switch from SQLAlchemy to asyncpg/aiosqlite

This commit is contained in:
Tulir Asokan
2021-12-20 22:39:09 +02:00
parent f12f3fe007
commit 89ab29ea5f
61 changed files with 4681 additions and 4628 deletions
+17 -13
View File
@@ -13,8 +13,9 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""This module contains classes handling commands issued by Matrix users."""
from typing import Awaitable, Callable, List, Optional, NamedTuple, Any
from __future__ import annotations
from typing import Awaitable, Callable, NamedTuple, Any, TYPE_CHECKING
from telethon.errors import FloodWaitError
@@ -25,7 +26,10 @@ from mautrix.bridge.commands import (HelpSection, CommandEvent as BaseCommandEve
CommandHandlerFunc, command_handler as base_command_handler)
from mautrix.util.format_duration import format_duration
from .. import user as u, context as c, portal as po
from .. import user as u, portal as po
if TYPE_CHECKING:
from ..__main__ import TelegramBridge
class HelpCacheKey(NamedTuple):
@@ -48,9 +52,9 @@ class CommandEvent(BaseCommandEvent):
sender: u.User
portal: po.Portal
def __init__(self, processor: 'CommandProcessor', room_id: RoomID, event_id: EventID,
sender: u.User, command: str, args: List[str], content: MessageEventContent,
portal: Optional['po.Portal'], is_management: bool, has_bridge_bot: bool) -> None:
def __init__(self, processor: CommandProcessor, room_id: RoomID, event_id: EventID,
sender: u.User, command: str, args: list[str], content: MessageEventContent,
portal: po.Portal | None, is_management: bool, has_bridge_bot: bool) -> None:
super().__init__(processor, room_id, event_id, sender, command, args, content,
portal, is_management, has_bridge_bot)
self.bridge = processor.bridge
@@ -83,7 +87,7 @@ class CommandHandler(BaseCommandHandler):
needs_matrix_puppeting=needs_matrix_puppeting, needs_admin=needs_admin,
**kwargs)
async def get_permission_error(self, evt: CommandEvent) -> Optional[str]:
async def get_permission_error(self, evt: CommandEvent) -> str | None:
if self.needs_puppeting and not evt.sender.puppet_whitelisted:
return "This command requires puppeting privileges."
elif self.needs_matrix_puppeting and not evt.sender.matrix_puppet_whitelisted:
@@ -96,10 +100,10 @@ class CommandHandler(BaseCommandHandler):
(not self.needs_matrix_puppeting or key.matrix_puppet_whitelisted))
def command_handler(_func: Optional[CommandHandlerFunc] = None, *, needs_auth: bool = True,
def command_handler(_func: CommandHandlerFunc | None = None, *, needs_auth: bool = True,
needs_puppeting: bool = True, needs_matrix_puppeting: bool = False,
needs_admin: bool = False, management_only: bool = False,
name: Optional[str] = None, help_text: str = "", help_args: str = "",
name: str | None = None, help_text: str = "", help_args: str = "",
help_section: HelpSection = None) -> Callable[[CommandHandlerFunc],
CommandHandler]:
return base_command_handler(
@@ -110,10 +114,10 @@ def command_handler(_func: Optional[CommandHandlerFunc] = None, *, needs_auth: b
class CommandProcessor(BaseCommandProcessor):
def __init__(self, context: c.Context) -> None:
super().__init__(event_class=CommandEvent, bridge=context.bridge)
self.tgbot = context.bot
self.public_website = context.public_website
def __init__(self, bridge: 'TelegramBridge') -> None:
super().__init__(event_class=CommandEvent, bridge=bridge)
self.tgbot = bridge.bot
self.public_website = bridge.public_website
@staticmethod
async def _run_handler(handler: Callable[[CommandEvent], Awaitable[Any]], evt: CommandEvent
+5 -5
View File
@@ -24,7 +24,7 @@ from .. import puppet as pu
help_section=SECTION_AUTH, help_text="Revert your Telegram account's Matrix "
"puppet to use the default Matrix account.")
async def logout_matrix(evt: CommandEvent) -> EventID:
puppet = pu.Puppet.get(evt.sender.tgid)
puppet = await pu.Puppet.get_by_tgid(evt.sender.tgid)
if not puppet.is_real_user:
return await evt.reply("You are not logged in with your Matrix account.")
await puppet.switch_mxid(None, None)
@@ -36,7 +36,7 @@ async def logout_matrix(evt: CommandEvent) -> EventID:
help_text="Replace your Telegram account's Matrix puppet with your own Matrix "
"account.")
async def login_matrix(evt: CommandEvent) -> EventID:
puppet = pu.Puppet.get(evt.sender.tgid)
puppet = await pu.Puppet.get_by_tgid(evt.sender.tgid)
if puppet.is_real_user:
return await evt.reply("You have already logged in with your Matrix account. "
"Log out with `$cmdprefix+sp logout-matrix` first.")
@@ -71,7 +71,7 @@ async def login_matrix(evt: CommandEvent) -> EventID:
help_section=SECTION_AUTH,
help_text="Pings the server with the stored matrix authentication.")
async def ping_matrix(evt: CommandEvent) -> EventID:
puppet = pu.Puppet.get(evt.sender.tgid)
puppet = await pu.Puppet.get_by_tgid(evt.sender.tgid)
if not puppet.is_real_user:
return await evt.reply("You are not logged in with your Matrix account.")
try:
@@ -84,7 +84,7 @@ async def ping_matrix(evt: CommandEvent) -> EventID:
@command_handler(needs_auth=True, needs_matrix_puppeting=True, help_section=SECTION_AUTH,
help_text="Clear the Matrix sync token stored for your custom puppet.")
async def clear_cache_matrix(evt: CommandEvent) -> EventID:
puppet = pu.Puppet.get(evt.sender.tgid)
puppet = await pu.Puppet.get_by_tgid(evt.sender.tgid)
if not puppet.is_real_user:
return await evt.reply("You are not logged in with your Matrix account.")
try:
@@ -99,7 +99,7 @@ async def clear_cache_matrix(evt: CommandEvent) -> EventID:
async def enter_matrix_token(evt: CommandEvent) -> EventID:
evt.sender.command_status = None
puppet = pu.Puppet.get(evt.sender.tgid)
puppet = await pu.Puppet.get_by_tgid(evt.sender.tgid)
if puppet.is_real_user:
return await evt.reply("You have already logged in with your Matrix account. "
"Log out with `$cmdprefix+sp logout-matrix` first.")
+11 -9
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2021 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
@@ -35,12 +35,13 @@ async def clear_db_cache(evt: CommandEvent) -> EventID:
po.Portal.by_mxid = {}
await evt.reply("Cleared portal cache")
elif section == "puppet":
pu.Puppet.cache = {}
pu.Puppet.by_tgid = {}
for puppet in pu.Puppet.by_custom_mxid.values():
puppet.sync_task.cancel()
puppet.stop()
pu.Puppet.by_custom_mxid = {}
await asyncio.gather(*[puppet.try_start() for puppet in pu.Puppet.all_with_custom_mxid()],
loop=evt.loop)
await asyncio.gather(
*[puppet.try_start() async for puppet in pu.Puppet.all_with_custom_mxid()]
)
await evt.reply("Cleared puppet cache and restarted custom puppet syncers")
elif section == "user":
u.User.by_mxid = {
@@ -61,15 +62,16 @@ async def reload_user(evt: CommandEvent) -> EventID:
mxid = evt.args[0]
else:
mxid = evt.sender.mxid
user = u.User.get_by_mxid(mxid, create=False)
user = await u.User.get_by_mxid(mxid, create=False)
if not user:
return await evt.reply("User not found")
puppet = await pu.Puppet.get_by_custom_mxid(mxid)
if puppet:
puppet.sync_task.cancel()
puppet.stop()
await user.stop()
user.delete(delete_db=False)
user = u.User.get_by_mxid(mxid)
del u.User.by_tgid[user.tgid]
del u.User.by_mxid[user.mxid]
user = await u.User.get_by_mxid(mxid)
await user.ensure_started()
if puppet:
await puppet.start()
+9 -7
View File
@@ -13,7 +13,9 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Optional, Tuple, Awaitable
from __future__ import annotations
from typing import Awaitable
import asyncio
from telethon.tl.types import ChatForbidden, ChannelForbidden
@@ -43,7 +45,7 @@ async def bridge(evt: CommandEvent) -> EventID:
room_id = RoomID(evt.args[1]) if len(evt.args) > 1 else evt.room_id
that_this = "This" if room_id == evt.room_id else "That"
portal = po.Portal.get_by_mxid(room_id)
portal = await po.Portal.get_by_mxid(room_id)
if portal:
return await evt.reply(f"{that_this} room is already a portal room.")
@@ -64,7 +66,7 @@ async def bridge(evt: CommandEvent) -> EventID:
"prefix channel IDs with `-100` and normal group IDs with `-`.\n\n"
"Bridging private chats to existing rooms is not allowed.")
portal = po.Portal.get_by_tgid(tgid, peer_type=peer_type)
portal = await po.Portal.get_by_tgid(tgid, peer_type=peer_type)
if not portal.allow_bridging:
return await evt.reply("This bridge doesn't allow bridging that Telegram chat.\n"
"If you're the bridge admin, try "
@@ -105,7 +107,7 @@ async def bridge(evt: CommandEvent) -> EventID:
async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: "po.Portal"
) -> Tuple[bool, Optional[Awaitable[None]]]:
) -> tuple[bool, Awaitable[None] | None]:
if not portal.mxid:
await evt.reply("The portal seems to have lost its Matrix room between you"
"calling `$cmdprefix+sp bridge` and this command.\n\n"
@@ -126,10 +128,10 @@ async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: "po.Porta
return False, None
async def confirm_bridge(evt: CommandEvent) -> Optional[EventID]:
async def confirm_bridge(evt: CommandEvent) -> EventID | None:
status = evt.sender.command_status
try:
portal = po.Portal.get_by_tgid(status["tgid"], peer_type=status["peer_type"])
portal = await po.Portal.get_by_tgid(status["tgid"], peer_type=status["peer_type"])
bridge_to_mxid = status["bridge_to_mxid"]
except KeyError:
evt.sender.command_status = None
@@ -162,7 +164,7 @@ async def confirm_bridge(evt: CommandEvent) -> Optional[EventID]:
async def _locked_confirm_bridge(evt: CommandEvent, portal: 'po.Portal', room_id: RoomID,
is_logged_in: bool) -> Optional[EventID]:
is_logged_in: bool) -> EventID | None:
user = evt.sender if is_logged_in else evt.tgbot
try:
entity = await user.client.get_entity(portal.peer)
+1 -1
View File
@@ -37,7 +37,7 @@ async def config(evt: CommandEvent) -> None:
await config_defaults(evt)
return
portal = po.Portal.get_by_mxid(evt.room_id)
portal = await po.Portal.get_by_mxid(evt.room_id)
if not portal:
await evt.reply("This is not a portal room.")
return
@@ -32,7 +32,7 @@ async def create(evt: CommandEvent) -> EventID:
return await evt.reply(
"**Usage:** `$cmdprefix+sp create ['group'/'supergroup'/'channel']`")
if po.Portal.get_by_mxid(evt.room_id):
if await po.Portal.get_by_mxid(evt.room_id):
return await evt.reply("This is already a portal room.")
if not await user_has_power_level(evt.room_id, evt.az.intent, evt.sender, "bridge"):
@@ -50,8 +50,8 @@ async def create(evt: CommandEvent) -> EventID:
"group": "chat",
}[type]
portal = po.Portal(tgid=TelegramID(0), peer_type=type, mxid=evt.room_id,
title=title, about=about, encrypted=encrypted)
portal = po.Portal(tgid=TelegramID(0), tg_receiver=TelegramID(0), peer_type=type,
mxid=evt.room_id, title=title, about=about, encrypted=encrypted)
invites, errors = await portal.get_telegram_users_in_matrix_room(evt.sender)
if len(errors) > 0:
error_list = "\n".join(f"* [{mxid}](https://matrix.to/#/{mxid})" for mxid in errors)
+11 -10
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2021 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
@@ -13,7 +13,8 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Optional, List, Tuple
from __future__ import annotations
from datetime import timedelta, datetime
import re
@@ -33,7 +34,7 @@ from .util import user_has_power_level
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)
portal = await 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"):
@@ -46,7 +47,7 @@ async def sync_state(evt: CommandEvent) -> EventID:
@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)
portal = await po.Portal.get_by_mxid(evt.room_id)
if not portal:
return await evt.reply("This is not a portal room.")
@@ -73,7 +74,7 @@ async def sync_full(evt: CommandEvent) -> EventID:
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)
portal = await po.Portal.get_by_mxid(evt.room_id)
if not portal:
return await evt.reply("This is not a portal room.")
tgid = portal.tgid
@@ -92,7 +93,7 @@ invite_link_usage = ("**Usage:** `$cmdprefix+sp invite-link [--uses=<amount>] [-
" A number suffixed with d(ay), h(our), m(inute) or s(econd)")
def _parse_flag(args: List[str]) -> Tuple[str, str]:
def _parse_flag(args: list[str]) -> tuple[str, str]:
arg = args.pop(0).lower()
if arg.startswith("--"):
value_start = arg.index("=")
@@ -116,7 +117,7 @@ def _parse_flag(args: List[str]) -> Tuple[str, str]:
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]:
def _parse_delta(value: str) -> timedelta | None:
match = delta_regex.fullmatch(value)
if not match:
return None
@@ -159,7 +160,7 @@ async def invite_link(evt: CommandEvent) -> EventID:
await evt.reply("Invalid format for expiry time delta")
expire = datetime.now() + expire_delta
portal = po.Portal.get_by_mxid(evt.room_id)
portal = await po.Portal.get_by_mxid(evt.room_id)
if not portal:
return await evt.reply("This is not a portal room.")
@@ -178,7 +179,7 @@ async def invite_link(evt: CommandEvent) -> EventID:
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
help_text="Upgrade a normal Telegram group to a supergroup.")
async def upgrade(evt: CommandEvent) -> EventID:
portal = po.Portal.get_by_mxid(evt.room_id)
portal = await 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":
@@ -203,7 +204,7 @@ async def group_name(evt: CommandEvent) -> EventID:
if len(evt.args) == 0:
return await evt.reply("**Usage:** `$cmdprefix+sp group-name <name/->`")
portal = po.Portal.get_by_mxid(evt.room_id)
portal = await 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":
+10 -8
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2021 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
@@ -13,7 +13,9 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Dict, Callable, Optional
from __future__ import annotations
from typing import Callable
from mautrix.types import RoomID, EventID
@@ -22,10 +24,10 @@ from .. import command_handler, CommandEvent, SECTION_PORTAL_MANAGEMENT
from .util import user_has_power_level
async def _get_portal_and_check_permission(evt: CommandEvent) -> Optional[po.Portal]:
async def _get_portal_and_check_permission(evt: CommandEvent) -> po.Portal | None:
room_id = RoomID(evt.args[0]) if len(evt.args) > 0 else evt.room_id
portal = po.Portal.get_by_mxid(room_id)
portal = await po.Portal.get_by_mxid(room_id)
if not portal:
that_this = "This" if room_id == evt.room_id else "That"
await evt.reply(f"{that_this} is not a portal room.")
@@ -44,8 +46,8 @@ async def _get_portal_and_check_permission(evt: CommandEvent) -> Optional[po.Por
def _get_portal_murder_function(action: str, room_id: str, function: Callable, command: str,
completed_message: str) -> Dict:
async def post_confirm(confirm) -> Optional[EventID]:
completed_message: str) -> dict:
async def post_confirm(confirm) -> EventID | None:
confirm.sender.command_status = None
if len(confirm.args) > 0 and confirm.args[0] == f"confirm-{command}":
await function()
@@ -66,7 +68,7 @@ def _get_portal_murder_function(action: str, room_id: str, function: Callable, c
help_text="Remove all users from the current portal room and forget the portal. "
"Only works for group chats; to delete a private chat portal, simply "
"leave the room.")
async def delete_portal(evt: CommandEvent) -> Optional[EventID]:
async def delete_portal(evt: CommandEvent) -> EventID | None:
portal = await _get_portal_and_check_permission(evt)
if not portal:
return None
@@ -87,7 +89,7 @@ async def delete_portal(evt: CommandEvent) -> Optional[EventID]:
@command_handler(needs_auth=False, needs_puppeting=False,
help_section=SECTION_PORTAL_MANAGEMENT,
help_text="Remove puppets from the current portal room and forget the portal.")
async def unbridge(evt: CommandEvent) -> Optional[EventID]:
async def unbridge(evt: CommandEvent) -> EventID | None:
portal = await _get_portal_and_check_permission(evt)
if not portal:
return None
+6 -8
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2021 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
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Tuple, Optional
from __future__ import annotations
from mautrix.errors import MatrixRequestError
from mautrix.appservice import IntentAPI
@@ -22,16 +22,14 @@ from .. import CommandEvent
from ... import user as u
OptStr = Optional[str]
async def get_initial_state(
intent: IntentAPI, room_id: RoomID
) -> Tuple[OptStr, OptStr, Optional[PowerLevelStateEventContent], bool]:
) -> tuple[str | None, str | None, PowerLevelStateEventContent | None, bool]:
state = await intent.get_state(room_id)
title: OptStr = None
about: OptStr = None
levels: Optional[PowerLevelStateEventContent] = None
title: str | None = None
about: str | None = None
levels: PowerLevelStateEventContent | None = None
encrypted: bool = False
for event in state:
try:
@@ -13,10 +13,9 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Optional
from telethon.errors import (UsernameInvalidError, UsernameNotModifiedError, UsernameOccupiedError,
HashInvalidError, AuthKeyError, FirstNameInvalidError, AboutTooLongError)
HashInvalidError, AuthKeyError, FirstNameInvalidError,
AboutTooLongError)
from telethon.tl.types import Authorization
from telethon.tl.functions.account import (UpdateUsernameRequest, GetAuthorizationsRequest,
ResetAuthorizationRequest, UpdateProfileRequest)
@@ -48,10 +47,11 @@ async def username(evt: CommandEvent) -> EventID:
except UsernameOccupiedError:
return await evt.reply("That username is already in use.")
await evt.sender.update_info()
if not evt.sender.username:
if not evt.sender.tg_username:
await evt.reply("Username removed")
else:
await evt.reply(f"Username changed to {evt.sender.username}")
await evt.reply(f"Username changed to {evt.sender.tg_username}")
@command_handler(needs_auth=True,
help_section=SECTION_AUTH,
@@ -71,6 +71,7 @@ async def about(evt: CommandEvent) -> EventID:
return await evt.reply("The provided about section is too long")
return await evt.reply("About section updated")
@command_handler(needs_auth=True, help_section=SECTION_AUTH, help_args="<_new displayname_>",
help_text="Change your Telegram displayname.")
async def displayname(evt: CommandEvent) -> EventID:
+15 -13
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2021 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
@@ -13,11 +13,13 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Any, Dict, Optional
from __future__ import annotations
from typing import Any
import asyncio
import io
from telethon.errors import ( # isort: skip
from telethon.errors import (
AccessTokenExpiredError, AccessTokenInvalidError, FirstNameInvalidError, FloodWaitError,
PasswordHashInvalidError, PhoneCodeExpiredError, PhoneCodeInvalidError,
PhoneNumberAppSignupForbiddenError, PhoneNumberBannedError, PhoneNumberFloodError,
@@ -125,7 +127,7 @@ async def enter_code_register(evt: CommandEvent) -> EventID:
async def login_qr(evt: CommandEvent) -> EventID:
login_as = evt.sender
if len(evt.args) > 0 and evt.sender.is_admin:
login_as = u.User.get_by_mxid(UserID(evt.args[0]))
login_as = await u.User.get_by_mxid(UserID(evt.args[0]))
if not qrcode or not QRLogin:
return await evt.reply("This bridge instance does not support logging in with a QR code.")
if await login_as.is_logged_in():
@@ -133,7 +135,7 @@ async def login_qr(evt: CommandEvent) -> EventID:
await login_as.ensure_started(even_if_no_session=True)
qr_login = QRLogin(login_as.client, ignored_ids=[])
qr_event_id: Optional[EventID] = None
qr_event_id: EventID | None = None
async def upload_qr() -> None:
nonlocal qr_event_id
@@ -184,7 +186,7 @@ async def login_qr(evt: CommandEvent) -> EventID:
async def login(evt: CommandEvent) -> EventID:
override_sender = False
if len(evt.args) > 0 and evt.sender.is_admin:
evt.sender = await u.User.get_by_mxid(UserID(evt.args[0])).ensure_started()
evt.sender = await u.User.get_and_start_by_mxid(UserID(evt.args[0]))
override_sender = True
if await evt.sender.is_logged_in():
return await evt.reply(f"You are already logged in as {evt.sender.human_tg_id}.")
@@ -217,7 +219,7 @@ async def login(evt: CommandEvent) -> EventID:
return await evt.reply("This bridge instance has been configured to not allow logging in.")
async def _request_code(evt: CommandEvent, phone_number: str, next_status: Dict[str, Any]
async def _request_code(evt: CommandEvent, phone_number: str, next_status: dict[str, Any]
) -> EventID:
ok = False
try:
@@ -249,7 +251,7 @@ async def _request_code(evt: CommandEvent, phone_number: str, next_status: Dict[
@command_handler(needs_auth=False)
async def enter_phone_or_token(evt: CommandEvent) -> Optional[EventID]:
async def enter_phone_or_token(evt: CommandEvent) -> EventID | None:
if len(evt.args) == 0:
return await evt.reply("**Usage:** `$cmdprefix+sp enter-phone-or-token <phone-or-token>`")
elif not evt.config.get("bridge.allow_matrix_login", True):
@@ -273,7 +275,7 @@ async def enter_phone_or_token(evt: CommandEvent) -> Optional[EventID]:
@command_handler(needs_auth=False)
async def enter_code(evt: CommandEvent) -> Optional[EventID]:
async def enter_code(evt: CommandEvent) -> EventID | None:
if len(evt.args) == 0:
return await evt.reply("**Usage:** `$cmdprefix+sp enter-code <code>`")
elif not evt.config.get("bridge.allow_matrix_login", True):
@@ -289,7 +291,7 @@ async def enter_code(evt: CommandEvent) -> Optional[EventID]:
@command_handler(needs_auth=False)
async def enter_password(evt: CommandEvent) -> Optional[EventID]:
async def enter_password(evt: CommandEvent) -> EventID | None:
if len(evt.args) == 0:
return await evt.reply("**Usage:** `$cmdprefix+sp enter-password <password>`")
elif not evt.config.get("bridge.allow_matrix_login", True):
@@ -309,7 +311,7 @@ async def enter_password(evt: CommandEvent) -> Optional[EventID]:
return None
async def _sign_in(evt: CommandEvent, login_as: 'u.User' = None, **sign_in_info) -> EventID:
async def _sign_in(evt: CommandEvent, login_as: u.User = None, **sign_in_info) -> EventID:
login_as = login_as or evt.sender
try:
await login_as.ensure_started(even_if_no_session=True)
@@ -330,9 +332,9 @@ async def _sign_in(evt: CommandEvent, login_as: 'u.User' = None, **sign_in_info)
"Please send your password here.")
async def _finish_sign_in(evt: CommandEvent, user: User, login_as: 'u.User' = None) -> EventID:
async def _finish_sign_in(evt: CommandEvent, user: User, login_as: u.User = None) -> EventID:
login_as = login_as or evt.sender
existing_user = u.User.get_by_tgid(TelegramID(user.id))
existing_user = await u.User.get_by_tgid(TelegramID(user.id))
if existing_user and existing_user != login_as:
await existing_user.log_out()
await evt.reply(f"[{existing_user.displayname}]"
+16 -14
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2020 Tulir Asokan
# Copyright (C) 2021 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
@@ -13,7 +13,9 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import List, Optional, Tuple, cast
from __future__ import annotations
from typing import cast
import codecs
import base64
import re
@@ -81,7 +83,7 @@ async def search(evt: CommandEvent) -> EventID:
"Minimum length of remote query is 5 characters.")
return await evt.reply("No results 3:")
reply: List[str] = []
reply: list[str] = []
if remote:
reply += ["**Results from Telegram server:**", ""]
else:
@@ -114,14 +116,14 @@ async def pm(evt: CommandEvent) -> EventID:
return await evt.reply("User not found.")
elif not isinstance(user, TLUser):
return await evt.reply("That doesn't seem to be a user.")
portal = po.Portal.get_by_entity(user, evt.sender.tgid)
portal = await po.Portal.get_by_entity(user, tg_receiver=evt.sender.tgid)
await portal.create_matrix_room(evt.sender, user, [evt.sender.mxid])
displayname, _ = pu.Puppet.get_displayname(user, False)
return await evt.reply(f"Created private chat room with {displayname}")
async def _join(evt: CommandEvent, identifier: str, link_type: str
) -> Tuple[Optional[TypeUpdates], Optional[EventID]]:
) -> tuple[TypeUpdates | None, EventID | None]:
if link_type == "joinchat":
try:
await evt.sender.client(CheckChatInviteRequest(identifier))
@@ -143,7 +145,7 @@ async def _join(evt: CommandEvent, identifier: str, link_type: str
@command_handler(help_section=SECTION_CREATING_PORTALS,
help_args="<_link_>",
help_text="Join a chat with an invite link.")
async def join(evt: CommandEvent) -> Optional[EventID]:
async def join(evt: CommandEvent) -> EventID | None:
if len(evt.args) == 0:
return await evt.reply("**Usage:** `$cmdprefix+sp join <invite link>`")
@@ -171,7 +173,7 @@ async def join(evt: CommandEvent) -> Optional[EventID]:
return None
for chat in updates.chats:
portal = po.Portal.get_by_entity(chat)
portal = await po.Portal.get_by_entity(chat)
if portal.mxid:
await portal.invite_to_matrix([evt.sender.mxid])
return await evt.reply(f"Invited you to portal of {portal.title}")
@@ -219,7 +221,7 @@ class MessageIDError(ValueError):
async def _parse_encoded_msgid(user: AbstractUser, enc_id: str, type_name: str
) -> Tuple[TypeInputPeer, Message]:
) -> tuple[TypeInputPeer, Message]:
try:
enc_id += (4 - len(enc_id) % 4) * "="
enc_id = base64.b64decode(enc_id)
@@ -233,10 +235,10 @@ async def _parse_encoded_msgid(user: AbstractUser, enc_id: str, type_name: str
raise MessageIDError(f"Invalid {type_name} ID (format)") from e
if peer_type == PEER_TYPE_CHAT:
orig_msg = DBMessage.get_one_by_tgid(msg_id, space)
orig_msg = await DBMessage.get_one_by_tgid(msg_id, space)
if not orig_msg:
raise MessageIDError(f"Invalid {type_name} ID (original message not found in db)")
new_msg = DBMessage.get_by_mxid(orig_msg.mxid, orig_msg.mx_room, user.tgid)
new_msg = await DBMessage.get_by_mxid(orig_msg.mxid, orig_msg.mx_room, user.tgid)
if not new_msg:
raise MessageIDError(f"Invalid {type_name} ID (your copy of message not found in db)")
msg_id = new_msg.tgid
@@ -282,7 +284,7 @@ async def play(evt: CommandEvent) -> EventID:
@command_handler(help_section=SECTION_MISC,
help_args="<_poll ID_> <_choice number_>",
help_text="Vote in a Telegram poll.")
async def vote(evt: CommandEvent) -> EventID:
async def vote(evt: CommandEvent) -> EventID | None:
if len(evt.args) < 1:
return await evt.reply("**Usage:** `$cmdprefix+sp vote <poll ID> <choice number>`")
elif not await evt.sender.is_logged_in():
@@ -319,7 +321,7 @@ async def vote(evt: CommandEvent) -> EventID:
options = [msg.media.poll.answers[int(option) - 1].option
for option in evt.args[1:]]
try:
resp = await evt.sender.client(SendVoteRequest(peer=peer, msg_id=msg.id, options=options))
await evt.sender.client(SendVoteRequest(peer=peer, msg_id=msg.id, options=options))
except OptionsTooMuchError:
return await evt.reply("You passed too many options.")
# TODO use response
@@ -332,7 +334,7 @@ async def vote(evt: CommandEvent) -> EventID:
async def random(evt: CommandEvent) -> EventID:
if not evt.is_portal:
return await evt.reply("You can only randomize values in portal rooms")
portal = po.Portal.get_by_mxid(evt.room_id)
portal = await po.Portal.get_by_mxid(evt.room_id)
arg = evt.args[0] if len(evt.args) > 0 else "dice"
emoticon = {
"dart": "\U0001F3AF",
@@ -359,7 +361,7 @@ async def backfill(evt: CommandEvent) -> None:
limit = int(evt.args[0])
except (ValueError, IndexError):
limit = -1
portal = po.Portal.get_by_mxid(evt.room_id)
portal = await po.Portal.get_by_mxid(evt.room_id)
if not evt.config["bridge.backfill.normal_groups"] and portal.peer_type == "chat":
await evt.reply("Backfilling normal groups is disabled in the bridge config")
return