More migrations to mautrix-python

This commit is contained in:
Tulir Asokan
2019-07-19 00:17:57 +03:00
parent 8d4a9dc231
commit eef498d47a
6 changed files with 172 additions and 138 deletions
+14 -9
View File
@@ -24,17 +24,19 @@ from telethon.tl.patched import MessageService, Message
from telethon.tl.types import (
Channel, ChannelForbidden, Chat, ChatForbidden, MessageActionChannelMigrateFrom, PeerUser,
TypeUpdate, UpdateChannelPinnedMessage, UpdateChatPinnedMessage, UpdateChatParticipantAdmin,
UpdateChatParticipants, UpdateChatUserTyping, UpdateDeleteChannelMessages, UpdateDeleteMessages,
UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, UpdateNewMessage,
UpdateChatParticipants, UpdateChatUserTyping, UpdateDeleteChannelMessages, UpdateNewMessage,
UpdateDeleteMessages, UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage,
UpdateReadHistoryOutbox, UpdateShortChatMessage, UpdateShortMessage, UpdateUserName,
UpdateUserPhoto, UpdateUserStatus, UpdateUserTyping, User, UserStatusOffline, UserStatusOnline)
from mautrix_appservice import MatrixRequestError, AppService
from mautrix.types import UserID
from mautrix.errors import MatrixError
from mautrix.appservice import AppService
from alchemysession import AlchemySessionContainer
from . import portal as po, puppet as pu, __version__
from .db import Message as DBMessage
from .types import TelegramID, MatrixUserID
from .types import TelegramID
from .tgclient import MautrixTelegramClient
if TYPE_CHECKING:
@@ -69,7 +71,7 @@ class AbstractUser(ABC):
ignore_incoming_bot_events: bool = True
client: Optional[MautrixTelegramClient]
mxid: Optional[MatrixUserID]
mxid: Optional[UserID]
tgid: Optional[TelegramID]
username: Optional['str']
@@ -140,8 +142,10 @@ class AbstractUser(ABC):
api_hash=config["telegram.api_hash"],
app_version=__version__ if appversion == "auto" else appversion,
system_version=MautrixTelegramClient.__version__ if sysversion == "auto" else sysversion,
device_model=f"{platform.system()} {platform.release()}" if device == "auto" else device,
system_version=(MautrixTelegramClient.__version__
if sysversion == "auto" else sysversion),
device_model=(f"{platform.system()} {platform.release()}"
if device == "auto" else device),
timeout=config["telegram.connection.timeout"],
connection_retries=config["telegram.connection.retries"],
@@ -197,7 +201,8 @@ class AbstractUser(ABC):
raise NotImplementedError()
async def is_logged_in(self) -> bool:
return self.client and self.client.is_connected() and await self.client.is_user_authorized()
return (self.client and self.client.is_connected()
and await self.client.is_user_authorized())
async def has_full_access(self, allow_bot: bool = False) -> bool:
return (self.puppet_whitelisted
@@ -368,7 +373,7 @@ class AbstractUser(ABC):
return
try:
await portal.main_intent.redact(message.mx_room, message.mxid)
except MatrixRequestError:
except MatrixError:
pass
async def delete_message(self, update: UpdateDeleteMessages) -> None:
+6 -5
View File
@@ -27,7 +27,8 @@ from telethon.tl.functions.messages import GetChatsRequest, GetFullChatRequest
from telethon.tl.functions.channels import GetChannelsRequest, GetParticipantRequest
from telethon.errors import ChannelInvalidError, ChannelPrivateError
from .types import MatrixUserID
from mautrix.types import UserID
from .abstract_user import AbstractUser
from .db import BotChat
from .types import TelegramID
@@ -50,7 +51,7 @@ class Bot(AbstractUser):
tg_whitelist: List[int]
whitelist_group_admins: bool
_me_info: Optional[User]
_me_mxid: Optional[MatrixUserID]
_me_mxid: Optional[UserID]
def __init__(self, token: str) -> None:
super().__init__()
@@ -70,7 +71,7 @@ class Bot(AbstractUser):
self._me_info = None
self._me_mxid = None
async def get_me(self, use_cache: bool = True) -> Tuple[User, MatrixUserID]:
async def get_me(self, use_cache: bool = True) -> Tuple[User, UserID]:
if not use_cache or not self._me_mxid:
self._me_info = await self.client.get_me()
self._me_mxid = pu.Puppet.get_mxid_from_id(TelegramID(self._me_info.id))
@@ -186,7 +187,7 @@ class Bot(AbstractUser):
"Portal is not public. Use `/invite <mxid>` to get an invite.")
async def handle_command_invite(self, portal: po.Portal, reply: ReplyFunc,
mxid_input: MatrixUserID) -> Message:
mxid_input: UserID) -> Message:
if len(mxid_input) == 0:
return await reply("Usage: `/invite <mxid>`")
elif not portal.mxid:
@@ -194,7 +195,7 @@ class Bot(AbstractUser):
"Create one with /portal first.")
if not self.mxid_regex.match(mxid_input):
return await reply("That doesn't look like a Matrix ID.")
user = await u.User.get_by_mxid(MatrixUserID(mxid_input)).ensure_started()
user = await u.User.get_by_mxid(mxid_input).ensure_started()
if not user.relaybot_whitelisted:
return await reply("That user is not whitelisted to use the bridge.")
elif await user.is_logged_in():
-4
View File
@@ -34,10 +34,6 @@ class Config(BaseBridgeConfig):
except KeyError:
return super().__getitem__(key)
@staticmethod
def _new_token() -> str:
return "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(64))
def do_update(self, helper: ConfigUpdateHelper) -> None:
copy, copy_dict, base = helper
+3 -1
View File
@@ -16,6 +16,8 @@
from sqlalchemy import Column, ForeignKey, Integer, BigInteger, String, Boolean
from typing import Optional
from mautrix.types import ContentURI
from .base import Base
@@ -23,7 +25,7 @@ class TelegramFile(Base):
__tablename__ = "telegram_file"
id = Column(String, primary_key=True)
mxc = Column(String)
mxc: ContentURI = Column(String)
mime_type = Column(String)
was_converted = Column(Boolean)
timestamp = Column(BigInteger)
+148 -118
View File
@@ -13,11 +13,11 @@
#
# 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 Awaitable, Dict, List, Optional, Pattern, Tuple, Union, cast, TYPE_CHECKING, Any
from collections import deque
from datetime import datetime
from string import Template
from typing import (Awaitable, Dict, List, Optional, Pattern, Tuple, Union, Any, Deque, cast,
TYPE_CHECKING)
from html import escape as escape_html
from collections import deque
from string import Template
import asyncio
import random
import mimetypes
@@ -37,7 +37,7 @@ from telethon.tl.functions.messages import (
EditChatPhotoRequest, EditChatTitleRequest, ExportChatInviteRequest, GetFullChatRequest,
UpdatePinnedMessageRequest, MigrateChatRequest, SetTypingRequest, EditChatAboutRequest)
from telethon.tl.functions.channels import (
CreateChannelRequest, EditAdminRequest, EditBannedRequest, EditPhotoRequest, EditTitleRequest,
CreateChannelRequest, EditAdminRequest, EditPhotoRequest, EditTitleRequest,
GetParticipantsRequest, InviteToChannelRequest, JoinChannelRequest, LeaveChannelRequest,
UpdateUsernameRequest)
from telethon.tl.functions.messages import ReadHistoryRequest as ReadMessageHistoryRequest
@@ -63,9 +63,12 @@ from telethon.tl.types import (
TypePhotoSize, TypeUpdates, TypeUser, PhotoSize, TypeUserFull, UpdateChatUserTyping,
UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping, User, UserFull, MessageEntityPre,
InputMediaUploadedDocument, InputPeerPhotoFileLocation)
from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI
from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID
from mautrix.errors import MatrixRequestError, IntentError
from mautrix.appservice import AppService, IntentAPI
from mautrix.types import EventID, RoomID, UserID, RoomCreatePreset, ContentURI, MessageType
from .types import TelegramID
from .context import Context
from .db import Portal as DBPortal, Message as DBMessage, TelegramFile as DBTelegramFile
from .util import ignore_coro, sane_mimetypes
@@ -77,70 +80,96 @@ if TYPE_CHECKING:
from .config import Config
from .tgclient import MautrixTelegramClient
config = None # type: Config
config: Optional[Config] = None
TypeMessage = Union[Message, MessageService]
TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant]
DedupMXID = Tuple[MatrixEventID, TelegramID]
InviteList = Union[MatrixUserID, List[MatrixUserID]]
DedupMXID = Tuple[EventID, TelegramID]
InviteList = Union[UserID, List[UserID]]
class Portal:
base_log = logging.getLogger("mau.portal") # type: logging.Logger
az = None # type: AppService
bot = None # type: Bot
loop = None # type: asyncio.AbstractEventLoop
base_log: logging.Logger = logging.getLogger("mau.portal")
az: AppService = None
bot: Bot = None
loop: asyncio.AbstractEventLoop = None
# Config cache
filter_mode = None # type: str
filter_list = None # type: List[str]
filter_mode: str = None
filter_list: List[str] = None
public_portals = False # type: bool
max_initial_member_sync = -1 # type: int
sync_channel_members = True # type: bool
sync_matrix_state = True # type: bool
public_portals: bool = False
max_initial_member_sync: int = -1
sync_channel_members: bool = True
sync_matrix_state: bool = True
dedup_pre_db_check = False # type: bool
dedup_cache_queue_length = 20 # type: int
dedup_pre_db_check: bool = False
dedup_cache_queue_length: int = 20
alias_template = None # type: str
mx_alias_regex = None # type: Pattern
hs_domain = None # type: str
alias_template: str = None
mx_alias_regex: Pattern = None
hs_domain: str = None
# Instance cache
by_mxid = {} # type: Dict[MatrixRoomID, Portal]
by_tgid = {} # type: Dict[Tuple[TelegramID, TelegramID], Portal]
by_mxid: Dict[RoomID, 'Portal'] = {}
by_tgid: Dict[Tuple[TelegramID, TelegramID], 'Portal'] = {}
mxid: Optional[RoomID]
tgid: TelegramID
tg_receiver: TelegramID
peer_type: str
username: str
megagroup: bool
title: Optional[str]
about: Optional[str]
photo_id: Optional[str]
local_config: Dict[str, Any]
deleted: bool
log: logging.Logger
_db_instance: DBPortal
_main_intent: Optional[IntentAPI]
_room_create_lock: asyncio.Lock
_temp_pinned_message_id: Optional[TelegramID]
_temp_pinned_message_id_space: Optional[TelegramID]
_temp_pinned_message_sender: Optional['p.Puppet']
_dedup: Deque[str]
_dedup_mxid: Dict[str, DedupMXID]
_dedup_action: Deque[str]
_send_locks: Dict[int, asyncio.Lock]
def __init__(self, tgid: TelegramID, peer_type: str, tg_receiver: Optional[TelegramID] = None,
mxid: Optional[MatrixRoomID] = None, username: Optional[str] = None,
mxid: Optional[RoomID] = None, username: Optional[str] = None,
megagroup: Optional[bool] = False, title: Optional[str] = None,
about: Optional[str] = None, photo_id: Optional[str] = None,
local_config: Optional[str] = None, db_instance: DBPortal = None) -> None:
self.mxid = mxid # type: Optional[MatrixRoomID]
self.tgid = tgid # type: TelegramID
self.tg_receiver = tg_receiver or tgid # type: TelegramID
self.peer_type = peer_type # type: str
self.username = username # type: str
self.megagroup = megagroup # type: bool
self.title = title # type: Optional[str]
self.about = about # type: str
self.photo_id = photo_id # type: str
self.local_config = json.loads(local_config or "{}") # type: Dict[str, Any]
self._db_instance = db_instance # type: DBPortal
self.deleted = False # type: bool
self.mxid = mxid
self.tgid = tgid
self.tg_receiver = tg_receiver or tgid
self.peer_type = peer_type
self.username = username
self.megagroup = megagroup
self.title = title
self.about = about
self.photo_id = photo_id
self.local_config = json.loads(local_config or "{}")
self._db_instance = db_instance
self.deleted = False
self.log = self.base_log.getChild(self.tgid_log) if self.tgid else self.base_log
self._main_intent = None # type: IntentAPI
self._room_create_lock = asyncio.Lock() # type: asyncio.Lock
self._temp_pinned_message_id = None # type: Optional[int]
self._temp_pinned_message_id_space = None # type: Optional[TelegramID]
self._temp_pinned_message_sender = None # type: Optional[p.Puppet]
self._main_intent = None
self._room_create_lock = asyncio.Lock()
self._temp_pinned_message_id = None
self._temp_pinned_message_id_space = None
self._temp_pinned_message_sender = None
self._dedup = deque() # type: deque
self._dedup_mxid = {} # type: Dict[str, DedupMXID]
self._dedup_action = deque() # type: deque
self._dedup = deque()
self._dedup_mxid = {}
self._dedup_action = deque()
self._send_locks = {} # type: Dict[int, asyncio.Lock]
self._send_locks = {}
if tgid:
self.by_tgid[self.tgid_full] = self
@@ -260,7 +289,7 @@ class Portal:
try:
found_mxid = self._dedup_mxid[evt_hash]
except KeyError:
return MatrixEventID("None"), TelegramID(0)
return EventID("None"), TelegramID(0)
if found_mxid != expected_mxid:
return found_mxid
@@ -304,13 +333,11 @@ class Portal:
# region Matrix room info updating
async def invite_to_matrix(self, users: InviteList) -> None:
if isinstance(users, str):
await self.main_intent.invite(self.mxid, users, check_cache=True)
elif isinstance(users, list):
if isinstance(users, list):
for user in users:
await self.main_intent.invite(self.mxid, user, check_cache=True)
await self.main_intent.invite_user(self.mxid, user, check_cache=True)
else:
raise ValueError("Invalid invite identifier given to invite_matrix()")
await self.main_intent.invite_user(self.mxid, users, check_cache=True)
async def update_matrix_room(self, user: 'AbstractUser', entity: Union[TypeChat, User],
direct: bool, puppet: p.Puppet = None, levels: Dict = None,
@@ -348,7 +375,7 @@ class Portal:
return await self._create_matrix_room(user, entity, invites)
async def _create_matrix_room(self, user: 'AbstractUser', entity: TypeChat, invites: InviteList
) -> Optional[MatrixRoomID]:
) -> Optional[RoomID]:
direct = self.peer_type == "user"
if self.mxid:
@@ -375,11 +402,11 @@ class Portal:
self.megagroup = entity.megagroup
if self.peer_type == "channel" and entity.username:
public = Portal.public_portals
preset = RoomCreatePreset.PUBLIC
alias = self._get_alias_localpart(entity.username)
self.username = entity.username
else:
public = False
preset = RoomCreatePreset.PRIVATE
# TODO invite link alias?
alias = None
@@ -402,13 +429,13 @@ class Portal:
"content": {"groups": [config["appservice.community_id"]]},
})
room_id = await self.main_intent.create_room(alias=alias, is_public=public,
room_id = await self.main_intent.create_room(alias_localpart=alias, preset=preset,
is_direct=direct, invitees=invites or [],
name=self.title, initial_state=initial_state)
if not room_id:
raise Exception(f"Failed to create room")
self.mxid = MatrixRoomID(room_id)
self.mxid = RoomID(room_id)
self.by_mxid[self.mxid] = self
self.save()
self.az.state_store.set_power_levels(self.mxid, power_levels)
@@ -510,7 +537,7 @@ class Portal:
and Portal.max_initial_member_sync == -1
and (self.megagroup or self.peer_type != "channel"))
if trust_member_list:
joined_mxids = cast(List[MatrixUserID],
joined_mxids = cast(List[UserID],
await self.main_intent.get_room_members(self.mxid))
for user_mxid in joined_mxids:
if user_mxid == self.az.bot_mxid:
@@ -519,7 +546,7 @@ class Portal:
if puppet_id and puppet_id not in allowed_tgids:
if self.bot and puppet_id == self.bot.tgid:
self.bot.remove_chat(self.tgid)
await self.main_intent.kick(self.mxid, user_mxid,
await self.main_intent.kick_user(self.mxid, user_mxid,
"User had left this Telegram chat.")
continue
mx_user = u.User.get_by_mxid(user_mxid, create=False)
@@ -527,7 +554,7 @@ class Portal:
mx_user.unregister_portal(self)
if mx_user and not self.has_bot and mx_user.tgid not in allowed_tgids:
await self.main_intent.kick(self.mxid, mx_user.mxid,
await self.main_intent.kick_user(self.mxid, mx_user.mxid,
"You had left this Telegram chat.")
continue
@@ -551,12 +578,12 @@ class Portal:
if sender and sender.tgid != puppet.tgid
else "Left Telegram chat")
if sender and sender.tgid != puppet.tgid:
await self.main_intent.kick(self.mxid, puppet.mxid, kick_message)
await self.main_intent.kick_user(self.mxid, puppet.mxid, kick_message)
else:
await puppet.intent.leave_room(self.mxid)
if user:
user.unregister_portal(self)
await self.main_intent.kick(self.mxid, user.mxid, kick_message)
await self.main_intent.kick_user(self.mxid, user.mxid, kick_message)
async def update_info(self, user: 'AbstractUser', entity: TypeChat = None) -> None:
if self.peer_type == "user":
@@ -625,7 +652,8 @@ class Portal:
return None, None
if isinstance(photo, Document) and not photo.thumbs:
return None, None
largest = max(photo.sizes if isinstance(photo, Photo) else photo.thumbs,
largest = max(photo.thumbs if isinstance(photo, Document) else photo.sizes,
key=(lambda photo2: (len(photo2.bytes)
if not isinstance(photo2, PhotoSize)
else photo2.size)))
@@ -663,7 +691,7 @@ class Portal:
raise ValueError(f"Unknown photo type {type(photo)}")
if self.photo_id != photo_id:
if not photo_id:
await self.main_intent.set_room_avatar(self.mxid, "")
await self.main_intent.set_room_avatar(self.mxid, ContentURI(""))
self.photo_id = ""
if save:
self.save()
@@ -739,7 +767,7 @@ class Portal:
authenticated = [] # type: List[u.User]
has_bot = self.has_bot
for member_str in members:
member = MatrixUserID(member_str)
member = UserID(member_str)
if p.Puppet.get_id_from_mxid(member) or member == self.main_intent.mxid:
continue
user = await u.User.get_by_mxid(member).ensure_started() # type: u.User
@@ -749,20 +777,20 @@ class Portal:
return authenticated
@staticmethod
async def cleanup_room(intent: IntentAPI, room_id: str, message: str = "Portal deleted",
async def cleanup_room(intent: IntentAPI, room_id: RoomID, message: str = "Portal deleted",
puppets_only: bool = False) -> None:
try:
members = await intent.get_room_members(room_id)
except MatrixRequestError:
members = []
for user in members:
puppet = p.Puppet.get_by_mxid(MatrixUserID(user), create=False)
puppet = p.Puppet.get_by_mxid(UserID(user), create=False)
if user != intent.mxid and (not puppets_only or puppet):
try:
if puppet:
await puppet.intent.leave_room(room_id)
else:
await intent.kick(room_id, user, message)
await intent.kick_user(room_id, user, message)
except (MatrixRequestError, IntentError):
pass
await intent.leave_room(room_id)
@@ -816,7 +844,7 @@ class Portal:
}
async def name_change_matrix(self, user: 'u.User', displayname: str, prev_displayname: str,
event_id: MatrixEventID) -> None:
event_id: EventID) -> None:
async with self.require_send_lock(self.bot.tgid):
message = await self._get_state_change_message(
"name_change", user,
@@ -830,6 +858,7 @@ class Portal:
self.is_duplicate(response, (event_id, space))
async def get_displayname(self, user: 'u.User') -> str:
# FIXME mautrix4
return (await self.main_intent.get_displayname(self.mxid, user.mxid)
or user.mxid)
@@ -851,7 +880,7 @@ class Portal:
return user.client(SetTypingRequest(
self.peer, action() if typing else SendMessageCancelAction()))
async def mark_read(self, user: 'u.User', event_id: MatrixEventID) -> None:
async def mark_read(self, user: 'u.User', event_id: EventID) -> None:
if user.is_bot:
return
space = self.tgid if self.peer_type == "channel" else user.tgid
@@ -886,7 +915,7 @@ class Portal:
if not ban:
await source.client.edit_permissions(channel, target, view_messages=True)
async def leave_matrix(self, user: 'u.User', event_id: MatrixEventID) -> None:
async def leave_matrix(self, user: 'u.User', event_id: EventID) -> None:
if await user.needs_relaybot(self):
if not self.has_bot:
return
@@ -915,7 +944,7 @@ class Portal:
channel = await self.get_input_entity(user)
await user.client(LeaveChannelRequest(channel=channel))
async def join_matrix(self, user: 'u.User', event_id: MatrixEventID) -> None:
async def join_matrix(self, user: 'u.User', event_id: EventID) -> None:
if await user.needs_relaybot(self):
async with self.require_send_lock(self.bot.tgid):
message = await self._get_state_change_message("join", user)
@@ -993,7 +1022,7 @@ class Portal:
except KeyError:
return None
async def _handle_matrix_text(self, sender_id: TelegramID, event_id: MatrixEventID,
async def _handle_matrix_text(self, sender_id: TelegramID, event_id: EventID,
space: TelegramID, client: 'MautrixTelegramClient',
message: Dict, reply_to: TelegramID) -> None:
lock = self.require_send_lock(sender_id)
@@ -1015,18 +1044,18 @@ class Portal:
link_preview=lp)
self._add_telegram_message_to_db(event_id, space, 0, response)
async def _handle_matrix_file(self, msgtype: str, sender_id: TelegramID,
event_id: MatrixEventID, space: TelegramID,
async def _handle_matrix_file(self, msgtype: MessageType, sender_id: TelegramID,
event_id: EventID, space: TelegramID,
client: 'MautrixTelegramClient', message: dict,
reply_to: TelegramID) -> None:
file = await self.main_intent.download_file(message["url"])
file = await self.main_intent.download_media(message["url"])
info = message.get("info", {})
mime = info.get("mimetype", None)
w, h = None, None
if msgtype == "m.sticker":
if msgtype == MessageType.STICKER:
if mime != "image/gif":
mime, file, w, h = util.convert_image(file, source_mime=mime, target_type="webp")
else:
@@ -1048,14 +1077,8 @@ class Portal:
max_image_size=config["bridge.image_as_file_size"] * 1000 ** 2)
lock = self.require_send_lock(sender_id)
async with lock:
relates_to = message.get("m.relates_to", None) or {}
if relates_to.get("rel_type", None) == "m.replace":
orig_msg = DBMessage.get_by_mxid(relates_to.get("event_id", ""), self.mxid, space)
if orig_msg:
response = await client.edit_message(self.peer, orig_msg.tgid,
caption, file=media)
self._add_telegram_message_to_db(event_id, space, -1, response)
return
if await self._matrix_document_edit(client, message, space, caption, media, event_id):
return
try:
response = await client.send_media(self.peer, media, reply_to=reply_to,
caption=caption)
@@ -1066,7 +1089,20 @@ class Portal:
caption=caption)
self._add_telegram_message_to_db(event_id, space, 0, response)
async def _handle_matrix_location(self, sender_id: TelegramID, event_id: MatrixEventID,
async def _matrix_document_edit(self, client: 'MautrixTelegramClient', message: dict,
space: TelegramID, caption: str, media: Any, event_id: EventID
) -> bool:
relates_to = message.get("m.relates_to", None) or {}
if relates_to.get("rel_type", None) == "m.replace":
orig_msg = DBMessage.get_by_mxid(relates_to.get("event_id", ""), self.mxid, space)
if orig_msg:
response = await client.edit_message(self.peer, orig_msg.tgid,
caption, file=media)
self._add_telegram_message_to_db(event_id, space, -1, response)
return True
return False
async def _handle_matrix_location(self, sender_id: TelegramID, event_id: EventID,
space: TelegramID, client: 'MautrixTelegramClient',
message: Dict[str, Any], reply_to: TelegramID) -> None:
try:
@@ -1080,19 +1116,13 @@ class Portal:
lock = self.require_send_lock(sender_id)
async with lock:
relates_to = message.get("m.relates_to", None) or {}
if relates_to.get("rel_type", None) == "m.replace":
orig_msg = DBMessage.get_by_mxid(relates_to.get("event_id", ""), self.mxid, space)
if orig_msg:
response = await client.edit_message(self.peer, orig_msg.tgid,
caption, file=media)
self._add_telegram_message_to_db(event_id, space, -1, response)
return
if await self._matrix_document_edit(client, message, space, caption, media, event_id):
return
response = await client.send_media(self.peer, media, reply_to=reply_to,
caption=caption, entities=entities)
self._add_telegram_message_to_db(event_id, space, 0, response)
def _add_telegram_message_to_db(self, event_id: MatrixEventID, space: TelegramID,
def _add_telegram_message_to_db(self, event_id: EventID, space: TelegramID,
edit_index: int, response: TypeMessage) -> None:
self.log.debug("Handled Matrix message: %s", response)
self.is_duplicate(response, (event_id, space), force_hash=edit_index != 0)
@@ -1107,7 +1137,7 @@ class Portal:
edit_index=edit_index).insert()
async def handle_matrix_message(self, sender: 'u.User', message: Dict[str, Any],
event_id: MatrixEventID) -> None:
event_id: EventID) -> None:
if "body" not in message or "msgtype" not in message:
self.log.debug(f"Ignoring message {event_id} in {self.mxid} without body or msgtype")
return
@@ -1146,7 +1176,7 @@ class Portal:
self.log.debug(f"Unhandled Matrix event: {message}")
async def handle_matrix_pin(self, sender: 'u.User',
pinned_message: Optional[MatrixEventID]) -> None:
pinned_message: Optional[EventID]) -> None:
if self.peer_type != "chat" and self.peer_type != "channel":
return
try:
@@ -1162,7 +1192,7 @@ class Portal:
except ChatNotModifiedError:
pass
async def handle_matrix_deletion(self, deleter: 'u.User', event_id: MatrixEventID) -> None:
async def handle_matrix_deletion(self, deleter: 'u.User', event_id: EventID) -> None:
real_deleter = deleter if not await deleter.needs_relaybot(self) else self.bot
space = self.tgid if self.peer_type == "channel" else real_deleter.tgid
message = DBMessage.get_by_mxid(event_id, self.mxid, space)
@@ -1190,7 +1220,7 @@ class Portal:
user_id=user_id, admin_rights=rights))
async def handle_matrix_power_levels(self, sender: 'u.User',
new_users: Dict[MatrixUserID, int],
new_users: Dict[UserID, int],
old_users: Dict[str, int]) -> None:
# TODO handle all power level changes and bridge exact admin rights to supergroups/channels
for user, level in new_users.items():
@@ -1228,12 +1258,12 @@ class Portal:
self.title = title
self.save()
async def handle_matrix_avatar(self, sender: 'u.User', url: str) -> None:
async def handle_matrix_avatar(self, sender: 'u.User', url: ContentURI) -> None:
if self.peer_type not in ("chat", "channel"):
# Invalid peer type
return
file = await self.main_intent.download_file(url)
file = await self.main_intent.download_media(url)
mime = magic.from_buffer(file, mime=True)
ext = sane_mimetypes.guess_extension(mime)
uploaded = await sender.client.upload_file(file, file_name=f"avatar{ext}", use_cache=False)
@@ -1255,7 +1285,7 @@ class Portal:
self.save()
break
async def handle_matrix_upgrade(self, new_room: MatrixRoomID) -> None:
async def handle_matrix_upgrade(self, new_room: RoomID) -> None:
old_room = self.mxid
self.migrate_and_save_matrix(new_room)
await self.main_intent.join_room(new_room)
@@ -1267,7 +1297,7 @@ class Portal:
if not entity:
user_mxids = await self.main_intent.get_room_members(self.mxid)
for user_str in user_mxids:
user_id = MatrixUserID(user_str)
user_id = UserID(user_str)
if user_id == self.az.bot_mxid:
continue
user = u.User.get_by_mxid(user_id, create=False)
@@ -1299,7 +1329,7 @@ class Portal:
user_tgids = set()
user_mxids = await self.main_intent.get_room_members(self.mxid, ("join", "invite"))
for user_str in user_mxids:
user = MatrixUserID(user_str)
user = UserID(user_str)
if user == self.az.bot_mxid:
continue
mx_user = u.User.get_by_mxid(user, create=False)
@@ -1405,7 +1435,7 @@ class Portal:
return None
async def handle_telegram_photo(self, source: 'AbstractUser', intent: IntentAPI, evt: Message,
relates_to: Dict = None) -> Optional[Dict]:
relates_to: Dict = None) -> Optional[EventID]:
loc, largest_size = self._get_largest_photo_size(evt.media.photo)
file = await util.transfer_file_to_matrix(source.client, intent, loc)
if not file:
@@ -1503,7 +1533,7 @@ class Portal:
return info, name
async def handle_telegram_document(self, source: 'AbstractUser', intent: IntentAPI,
evt: Message, relates_to: dict = None) -> Optional[Dict]:
evt: Message, relates_to: dict = None) -> Optional[EventID]:
document = evt.media.document
attrs = self._parse_telegram_document_attributes(document.attributes)
@@ -1552,7 +1582,7 @@ class Portal:
return await intent.send_file(**kwargs)
def handle_telegram_location(self, _: 'AbstractUser', intent: IntentAPI, evt: Message,
relates_to: dict = None) -> Awaitable[dict]:
relates_to: dict = None) -> Awaitable[EventID]:
location = evt.media.geo
long = location.long
lat = location.lat
@@ -1580,7 +1610,7 @@ class Portal:
}, timestamp=evt.date, external_url=self.get_external_url(evt))
async def handle_telegram_text(self, source: 'AbstractUser', intent: IntentAPI, is_bot: bool,
evt: Message) -> dict:
evt: Message) -> EventID:
self.log.debug(f"Sending {evt.message} to {self.mxid} by {intent.mxid}")
text, html, relates_to = await formatter.telegram_to_matrix(evt, source, self.main_intent)
await intent.set_typing(self.mxid, is_typing=False)
@@ -1590,7 +1620,7 @@ class Portal:
external_url=self.get_external_url(evt))
async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI,
evt: Message, relates_to: dict = None) -> dict:
evt: Message, relates_to: dict = None) -> EventID:
override_text = ("This message is not supported on your version of Mautrix-Telegram. "
"Please check https://github.com/tulir/mautrix-telegram or ask your "
"bridge administrator about possible updates.")
@@ -1607,7 +1637,7 @@ class Portal:
}, timestamp=evt.date, external_url=self.get_external_url(evt))
async def handle_telegram_poll(self, source: 'AbstractUser', intent: IntentAPI, evt: Message,
relates_to: dict) -> dict:
relates_to: dict) -> EventID:
poll = evt.media.poll # type: Poll
poll_id = self._encode_msgid(source, evt)
@@ -1658,7 +1688,7 @@ class Portal:
return base64.b64encode(play_id).decode("utf-8").rstrip("=")
async def handle_telegram_game(self, source: 'AbstractUser', intent: IntentAPI,
evt: Message, relates_to: dict = None):
evt: Message, relates_to: dict = None) -> EventID:
game = evt.media.game
play_id = self._encode_msgid(source, evt)
command = f"!tg play {play_id}"
@@ -1693,7 +1723,7 @@ class Portal:
tg_space = self.tgid if self.peer_type == "channel" else source.tgid
temporary_identifier = MatrixEventID(
temporary_identifier = EventID(
f"${random.randint(1000000000000, 9999999999999)}TGBRIDGEDITEMP")
duplicate_found = self.is_duplicate(evt, (temporary_identifier, tg_space), force_hash=True)
if duplicate_found:
@@ -1758,7 +1788,7 @@ class Portal:
tg_space = self.tgid if self.peer_type == "channel" else source.tgid
temporary_identifier = MatrixEventID(
temporary_identifier = EventID(
f"${random.randint(1000000000000, 9999999999999)}TGBRIDGETEMP")
duplicate_found = self.is_duplicate(evt, (temporary_identifier, tg_space))
if duplicate_found:
@@ -2034,7 +2064,7 @@ class Portal:
self.log = self.base_log.getChild(str(self.tgid))
self.log.info(f"Telegram chat upgraded from {old_id}")
def migrate_and_save_matrix(self, new_id: MatrixRoomID) -> None:
def migrate_and_save_matrix(self, new_id: RoomID) -> None:
try:
del self.by_mxid[self.mxid]
except KeyError:
@@ -2073,7 +2103,7 @@ class Portal:
# region Class instance lookup
@classmethod
def get_by_mxid(cls, mxid: MatrixRoomID) -> Optional['Portal']:
def get_by_mxid(cls, mxid: RoomID) -> Optional['Portal']:
try:
return cls.by_mxid[mxid]
except KeyError:
+1 -1
View File
@@ -1,4 +1,4 @@
cryptg
Pillow
moviepy
prometheus-client
prometheus_client