Compare commits

..

10 Commits

Author SHA1 Message Date
Tulir Asokan 570372fa83 Bump version to 0.10.0 2021-06-14 19:45:00 +03:00
Tulir Asokan 5ed09ad783 Fix Telegram->Matrix typing notifications 2021-06-10 15:44:12 +03:00
Tulir Asokan c385aa0b8d Add real-time bridge status push option 2021-06-09 20:04:17 +03:00
Tulir Asokan ec152cbd9d Pass through Telegram gif meta as custom fields 2021-05-24 16:04:53 +03:00
Tulir Asokan b36fc35e04 Don't remove zero-width joiners from middle of displaynames 2021-05-23 16:28:26 +03:00
Tulir Asokan 198e77cae9 Remove commented edge things from dockerfile 2021-05-13 14:56:09 +03:00
Tulir Asokan 9c4beb29a5 Send m.bridge data when bridging existing room to Telegram 2021-05-12 19:21:37 +03:00
Tulir Asokan 6accb530c6 Add option to only bridge mute status and tags when creating portal 2021-04-29 12:09:54 +03:00
Tulir Asokan 1a77ba5fcd Add option to bridge archive, pin and mute status from Telegram 2021-04-20 14:52:19 +03:00
Tulir Asokan 7e9dd8b895 Update mautrix-python 2021-04-16 15:27:56 +03:00
14 changed files with 185 additions and 46 deletions
+1 -6
View File
@@ -2,11 +2,6 @@ FROM dock.mau.dev/tulir/lottieconverter:alpine-3.13
ARG TARGETARCH=amd64
#RUN echo $'\
#@edge http://dl-cdn.alpinelinux.org/alpine/edge/main\n\
#@edge http://dl-cdn.alpinelinux.org/alpine/edge/testing\n\
#@edge http://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories
RUN apk add --no-cache \
python3 py3-pip py3-setuptools py3-wheel \
py3-virtualenv \
@@ -27,7 +22,7 @@ RUN apk add --no-cache \
py3-requests \
#imageio
py3-numpy \
#py3-telethon@edge \ (outdated)
#py3-telethon \ (outdated)
# Optional for socks proxies
py3-pysocks \
# cryptg
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = "0.10.0rc1"
__version__ = "0.10.0"
__author__ = "Tulir Asokan <tulir@maunium.net>"
+38 -11
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
@@ -15,9 +15,9 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Tuple, Optional, Union, Dict, Type, Any, TYPE_CHECKING
from abc import ABC, abstractmethod
import platform
import asyncio
import logging
import platform
import time
from telethon.sessions import Session
@@ -31,7 +31,8 @@ from telethon.tl.types import (
UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, UpdateReadHistoryOutbox,
UpdateShortChatMessage, UpdateShortMessage, UpdateUserName, UpdateUserPhoto, UpdateUserStatus,
UpdateUserTyping, User, UserStatusOffline, UserStatusOnline, UpdateReadHistoryInbox,
UpdateReadChannelInbox, MessageEmpty)
UpdateReadChannelInbox, MessageEmpty, UpdateFolderPeers, UpdatePinnedDialogs,
UpdateNotifySettings, UpdateChannelUserTyping)
from mautrix.types import UserID, PresenceState
from mautrix.errors import MatrixError
@@ -57,6 +58,7 @@ MAX_DELETIONS: int = 10
UpdateMessage = Union[UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage,
UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage]
UpdateMessageContent = Union[UpdateShortMessage, UpdateShortChatMessage, Message, MessageService]
UpdateTyping = Union[UpdateUserTyping, UpdateChatUserTyping, UpdateChannelUserTyping]
UPDATE_TIME = Histogram("bridge_telegram_update", "Time spent processing Telegram updates",
("update_type",))
@@ -235,8 +237,7 @@ class AbstractUser(ABC):
# region Telegram update handling
async def _update(self, update: TypeUpdate) -> None:
asyncio.ensure_future(self._handle_entity_updates(getattr(update, "_entities", {})),
loop=self.loop)
asyncio.create_task(self._handle_entity_updates(getattr(update, "_entities", {})))
if isinstance(update, (UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage,
UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage)):
await self.update_message(update)
@@ -244,7 +245,7 @@ class AbstractUser(ABC):
await self.delete_message(update)
elif isinstance(update, UpdateDeleteChannelMessages):
await self.delete_channel_message(update)
elif isinstance(update, (UpdateChatUserTyping, UpdateUserTyping)):
elif isinstance(update, (UpdateChatUserTyping, UpdateChannelUserTyping, UpdateUserTyping)):
await self.update_typing(update)
elif isinstance(update, UpdateUserStatus):
await self.update_status(update)
@@ -260,9 +261,24 @@ class AbstractUser(ABC):
await self.update_read_receipt(update)
elif isinstance(update, (UpdateReadHistoryInbox, UpdateReadChannelInbox)):
await self.update_own_read_receipt(update)
elif isinstance(update, UpdateFolderPeers):
await self.update_folder_peers(update)
elif isinstance(update, UpdatePinnedDialogs):
await self.update_pinned_dialogs(update)
elif isinstance(update, UpdateNotifySettings):
await self.update_notify_settings(update)
else:
self.log.trace("Unhandled update: %s", update)
async def update_folder_peers(self, update: UpdateFolderPeers) -> None:
pass
async def update_pinned_dialogs(self, update: UpdatePinnedDialogs) -> None:
pass
async def update_notify_settings(self, update: UpdateNotifySettings) -> None:
pass
async def update_pinned_messages(self, update: Union[UpdatePinnedMessages,
UpdatePinnedChannelMessages]) -> None:
if isinstance(update, UpdatePinnedMessages):
@@ -330,16 +346,27 @@ class AbstractUser(ABC):
await portal.set_telegram_admin(TelegramID(update.user_id))
async def update_typing(self, update: Union[UpdateUserTyping, UpdateChatUserTyping]) -> None:
async def update_typing(self, update: UpdateTyping) -> None:
sender = None
if isinstance(update, UpdateUserTyping):
portal = po.Portal.get_by_tgid(TelegramID(update.user_id), self.tgid, "user")
else:
sender = pu.Puppet.get(TelegramID(update.user_id))
elif isinstance(update, UpdateChannelUserTyping):
portal = po.Portal.get_by_tgid(TelegramID(update.channel_id))
elif isinstance(update, UpdateChatUserTyping):
portal = po.Portal.get_by_tgid(TelegramID(update.chat_id))
if not portal or not portal.mxid:
else:
return
if isinstance(update, (UpdateChannelUserTyping, UpdateChatUserTyping)):
# Can typing notifications come from non-user peers?
if not update.from_id.user_id:
return
sender = pu.Puppet.get(TelegramID(update.from_id.user_id))
if not sender or not portal or not portal.mxid:
return
sender = pu.Puppet.get(TelegramID(update.user_id))
await portal.handle_telegram_typing(sender, update)
async def _handle_entity_updates(self, entities: Dict[int, Union[User, Chat, Channel]]
@@ -186,6 +186,7 @@ async def _locked_confirm_bridge(evt: CommandEvent, portal: 'po.Portal', room_id
portal.encrypted) = await get_initial_state(evt.az.intent, evt.room_id)
portal.photo_id = ""
await portal.save()
await portal.update_bridge_info()
asyncio.ensure_future(portal.update_matrix_room(user, entity, direct=False, levels=levels),
loop=evt.loop)
+4
View File
@@ -132,6 +132,10 @@ class Config(BaseBridgeConfig):
copy("bridge.delivery_receipts")
copy("bridge.delivery_error_reports")
copy("bridge.resend_bridge_info")
copy("bridge.mute_bridging")
copy("bridge.pinned_tag")
copy("bridge.archive_tag")
copy("bridge.tag_only_on_create")
copy("bridge.backfill.invite_own_puppet")
copy("bridge.backfill.takeout_limit")
copy("bridge.backfill.initial_limit")
+15
View File
@@ -8,6 +8,12 @@ homeserver:
# Only applies if address starts with https://
verify_ssl: true
asmux: false
# Number of retries for all HTTP requests if the homeserver isn't reachable.
http_retry_count: 4
# The URL to push real-time bridge status to.
# If set, the bridge will make POST requests to this URL whenever a user's Telegram connection state changes.
# The bridge will use the appservice as_token to authorize requests.
status_endpoint: null
# Application service host/registration related details
# Changing these values requires regeneration of the registration.
@@ -271,6 +277,15 @@ bridge:
# This field will automatically be changed back to false after it,
# except if the config file is not writable.
resend_bridge_info: false
# When using double puppeting, should muted chats be muted in Matrix?
mute_bridging: false
# When using double puppeting, should pinned chats be moved to a specific tag in Matrix?
# The favorites tag is `m.favourite`.
pinned_tag: null
# Same as above for archived chats, the low priority tag is `m.lowpriority`.
archive_tag: null
# Whether or not mute status and tags should only be bridged when the portal room is created.
tag_only_on_create: true
# Settings for backfilling messages from Telegram.
backfill:
# Whether or not the Telegram ghosts of logged in Matrix users should be
+1
View File
@@ -109,6 +109,7 @@ class MatrixHandler(BaseMatrixHandler):
if e2be_ok is False:
message += "\n\nWarning: Failed to enable end-to-bridge encryption"
await intent.send_notice(room_id, message)
await portal.update_bridge_info()
else:
await intent.join_room(room_id)
await intent.send_notice(room_id, "This puppet will remain inactive until a "
+1
View File
@@ -170,6 +170,7 @@ class PortalMetadata(BasePortal, ABC):
levels = self._get_base_power_levels(levels, entity)
await self.main_intent.set_power_levels(self.mxid, levels)
await self.handle_matrix_power_levels(source, levels.users, {}, None)
await self.update_bridge_info()
async def invite_telegram(self, source: 'u.User',
puppet: Union[p.Puppet, 'AbstractUser']) -> None:
+17 -6
View File
@@ -34,7 +34,8 @@ from telethon.tl.types import (
MessageMediaPhoto, MessageMediaDice, MessageMediaGame, MessageMediaUnsupported, PeerUser,
PhotoCachedSize, TypeChannelParticipant, TypeChatParticipant, TypeDocumentAttribute,
TypeMessageAction, TypePhotoSize, PhotoSize, UpdateChatUserTyping, UpdateUserTyping,
MessageEntityPre, ChatPhotoEmpty, DocumentAttributeImageSize)
MessageEntityPre, ChatPhotoEmpty, DocumentAttributeImageSize, DocumentAttributeAnimated,
UpdateChannelUserTyping, SendMessageTypingAction)
from mautrix.appservice import IntentAPI
from mautrix.types import (EventID, UserID, ImageInfo, ThumbnailInfo, RelatesTo, MessageType,
@@ -56,16 +57,18 @@ if TYPE_CHECKING:
InviteList = Union[UserID, List[UserID]]
TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant]
UpdateTyping = Union[UpdateUserTyping, UpdateChatUserTyping, UpdateChannelUserTyping]
DocAttrs = NamedTuple("DocAttrs", name=Optional[str], mime_type=Optional[str], is_sticker=bool,
sticker_alt=Optional[str], width=int, height=int)
sticker_alt=Optional[str], width=int, height=int, is_gif=bool)
config: Optional['Config'] = None
class PortalTelegram(BasePortal, ABC):
async def handle_telegram_typing(self, user: p.Puppet,
_: Union[UpdateUserTyping, UpdateChatUserTyping]) -> None:
await user.intent_for(self).set_typing(self.mxid, is_typing=True)
async def handle_telegram_typing(self, user: p.Puppet, update: UpdateTyping) -> None:
is_typing = isinstance(update.action, SendMessageTypingAction)
# Always use the default puppet here to avoid any problems with echoing
await user.default_mxid_intent.set_typing(self.mxid, is_typing=is_typing)
def _get_external_url(self, evt: Message) -> Optional[str]:
if self.peer_type == "channel" and self.username is not None:
@@ -134,6 +137,7 @@ class PortalTelegram(BasePortal, ABC):
@staticmethod
def _parse_telegram_document_attributes(attributes: List[TypeDocumentAttribute]) -> DocAttrs:
name, mime_type, is_sticker, sticker_alt, width, height = None, None, False, None, 0, 0
is_gif = False
for attr in attributes:
if isinstance(attr, DocumentAttributeFilename):
name = name or attr.file_name
@@ -141,11 +145,13 @@ class PortalTelegram(BasePortal, ABC):
elif isinstance(attr, DocumentAttributeSticker):
is_sticker = True
sticker_alt = attr.alt
elif isinstance(attr, DocumentAttributeAnimated):
is_gif = True
elif isinstance(attr, DocumentAttributeVideo):
width, height = attr.w, attr.h
elif isinstance(attr, DocumentAttributeImageSize):
width, height = attr.w, attr.h
return DocAttrs(name, mime_type, is_sticker, sticker_alt, width, height)
return DocAttrs(name, mime_type, is_sticker, sticker_alt, width, height, is_gif)
@staticmethod
def _parse_telegram_document_meta(evt: Message, file: DBTelegramFile, attrs: DocAttrs,
@@ -241,6 +247,11 @@ class PortalTelegram(BasePortal, ABC):
if info.thumbnail_info:
info.thumbnail_info.width = info.width
info.thumbnail_info.height = info.height
if attrs.is_gif:
info["fi.mau.telegram.gif"] = True
info["fi.mau.loop"] = True
info["fi.mau.autoplay"] = True
info["fi.mau.no_audio"] = True
content = MediaMessageEventContent(
body=name or "unnamed file", info=info, relates_to=relates_to,
+3 -1
View File
@@ -209,7 +209,9 @@ class Puppet(BasePuppet):
whitespace = ("\t\n\r\v\f \u00a0\u034f\u180e\u2063\u202f\u205f\u2800\u3000\u3164\ufeff"
"\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b"
"\u200c\u200d\u200e\u200f\ufe0f")
name = "".join(c for c in name.strip(whitespace) if unicodedata.category(c) != 'Cf')
allowed_other_format = ("\u200d", "\u200c")
name = "".join(c for c in name.strip(whitespace) if unicodedata.category(c) != 'Cf'
or c in allowed_other_format)
return name
@classmethod
+98 -13
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
@@ -15,22 +15,23 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import (Awaitable, Dict, List, Iterable, NamedTuple, Optional, Tuple, Any, cast,
TYPE_CHECKING)
from collections import defaultdict
from datetime import datetime, timezone
import logging
import asyncio
from telethon.tl.types import (TypeUpdate, UpdateNewMessage, UpdateNewChannelMessage,
UpdateShortChatMessage, UpdateShortMessage, User as TLUser, Chat,
ChatForbidden)
ChatForbidden, UpdateFolderPeers, UpdatePinnedDialogs,
UpdateNotifySettings, NotifyPeer)
from telethon.tl.custom import Dialog
from telethon.tl.types.contacts import ContactsNotModified
from telethon.tl.functions.contacts import GetContactsRequest, SearchRequest
from telethon.tl.functions.account import UpdateStatusRequest
from mautrix.client import Client
from mautrix.errors import MatrixRequestError
from mautrix.types import UserID, RoomID
from mautrix.bridge import BaseUser
from mautrix.errors import MatrixRequestError, MNotFound
from mautrix.types import UserID, RoomID, PushRuleScope, PushRuleKind, PushActionType, RoomTagInfo
from mautrix.bridge import BaseUser, BridgeState
from mautrix.util.logging import TraceLogger
from mautrix.util.opt_prometheus import Gauge
@@ -50,6 +51,11 @@ SearchResult = NamedTuple('SearchResult', puppet='pu.Puppet', similarity=int)
METRIC_LOGGED_IN = Gauge('bridge_logged_in', 'Users logged into bridge')
METRIC_CONNECTED = Gauge('bridge_connected', 'Users connected to Telegram')
BridgeState.human_readable_errors.update({
"tg-not-connected": "Your Telegram connection failed",
"logged-out": "You're not logged into Telegram",
})
class User(AbstractUser, BaseUser):
log: TraceLogger = logging.getLogger("mau.user")
@@ -72,8 +78,9 @@ class User(AbstractUser, BaseUser):
saved_contacts: int = 0, is_bot: bool = False,
db_portals: Optional[Iterable[Tuple[TelegramID, TelegramID]]] = None,
db_instance: Optional[DBUser] = None) -> None:
super().__init__()
AbstractUser.__init__(self)
self.mxid = mxid
BaseUser.__init__(self)
self.tgid = tgid
self.is_bot = is_bot
self.username = username
@@ -85,12 +92,8 @@ class User(AbstractUser, BaseUser):
self.db_portals = db_portals or []
self._db_instance = db_instance
self._ensure_started_lock = asyncio.Lock()
self.dm_update_lock = asyncio.Lock()
self._metric_value = defaultdict(lambda: False)
self._track_connection_task = None
self.command_status = None
(self.relaybot_whitelisted,
self.whitelisted,
self.puppet_whitelisted,
@@ -102,8 +105,6 @@ class User(AbstractUser, BaseUser):
if tgid:
self.by_tgid[tgid] = self
self.log = self.log.getChild(self.mxid)
@property
def name(self) -> str:
return self.mxid
@@ -217,6 +218,21 @@ class User(AbstractUser, BaseUser):
connected = bool(self.client._sender._transport_connected
if self.client and self.client._sender else False)
self._track_metric(METRIC_CONNECTED, connected)
await self.push_bridge_state(ok=connected, ttl=3600 if connected else 240,
error="tg-not-connected" if not connected else None)
async def fill_bridge_state(self, state: BridgeState) -> None:
await super().fill_bridge_state(state)
state.remote_id = str(self.tgid)
state.remote_name = self.human_tg_id
async def get_bridge_state(self) -> BridgeState:
if not self.client:
return BridgeState(ok=False, error="logged-out")
elif not self.client._sender or not self.client._sender._transport_connected:
return BridgeState(ok=False, error="tg-not-connected")
else:
return BridgeState(ok=True)
async def stop(self) -> None:
await super().stop()
@@ -224,6 +240,7 @@ class User(AbstractUser, BaseUser):
self._track_connection_task.cancel()
self._track_connection_task = None
self._track_metric(METRIC_CONNECTED, False)
await self.push_bridge_state(ok=False, error="tg-not-connected")
async def post_login(self, info: TLUser = None, first_login: bool = False) -> None:
if config["metrics.enabled"] and not self._track_connection_task:
@@ -328,6 +345,7 @@ class User(AbstractUser, BaseUser):
self.delete()
await self.stop()
self._track_metric(METRIC_LOGGED_IN, False)
await self.push_bridge_state(ok=False, error="logged-out")
return True
def _search_local(self, query: str, max_results: int = 5, min_similarity: int = 45
@@ -376,8 +394,70 @@ class User(AbstractUser, BaseUser):
if portal.mxid
}
async def _tag_room(self, puppet: pu.Puppet, portal: po.Portal, tag: str, active: bool
) -> None:
if not tag or not portal or not portal.mxid:
return
tag_info = await puppet.intent.get_room_tag(portal.mxid, tag)
if active and tag_info is None:
tag_info = RoomTagInfo(order=0.5)
tag_info[self.bridge.real_user_content_key] = True
await puppet.intent.set_room_tag(portal.mxid, tag, tag_info)
elif not active and tag_info and tag_info.get(self.bridge.real_user_content_key, False):
await puppet.intent.remove_room_tag(portal.mxid, tag)
@staticmethod
async def _mute_room(puppet: pu.Puppet, portal: po.Portal, mute_until: datetime) -> None:
if not config["bridge.mute_bridging"] or not portal or not portal.mxid:
return
now = datetime.utcnow().replace(tzinfo=timezone.utc)
if mute_until is not None and mute_until > now:
await puppet.intent.set_push_rule(PushRuleScope.GLOBAL, PushRuleKind.ROOM, portal.mxid,
actions=[PushActionType.DONT_NOTIFY])
else:
try:
await puppet.intent.remove_push_rule(PushRuleScope.GLOBAL, PushRuleKind.ROOM,
portal.mxid)
except MNotFound:
pass
async def update_folder_peers(self, update: UpdateFolderPeers) -> None:
if config["bridge.tag_only_on_create"]:
return
puppet = await pu.Puppet.get_by_custom_mxid(self.mxid)
if not puppet or not puppet.is_real_user:
return
for peer in update.folder_peers:
portal = po.Portal.get_by_entity(peer.peer, receiver_id=self.tgid, create=False)
await self._tag_room(puppet, portal, config["bridge.archive_tag"],
peer.folder_id == 1)
async def update_pinned_dialogs(self, update: UpdatePinnedDialogs) -> None:
if config["bridge.tag_only_on_create"]:
return
puppet = await pu.Puppet.get_by_custom_mxid(self.mxid)
if not puppet or not puppet.is_real_user:
return
# TODO bridge unpinning properly
for pinned in update.order:
portal = po.Portal.get_by_entity(pinned.peer, receiver_id=self.tgid, create=False)
await self._tag_room(puppet, portal, config["bridge.pinned_tag"], True)
async def update_notify_settings(self, update: UpdateNotifySettings) -> None:
if config["bridge.tag_only_on_create"]:
return
elif not isinstance(update.peer, NotifyPeer):
# TODO handle global notification setting changes?
return
puppet = await pu.Puppet.get_by_custom_mxid(self.mxid)
if not puppet or not puppet.is_real_user:
return
portal = po.Portal.get_by_entity(update.peer.peer, receiver_id=self.tgid, create=False)
await self._mute_room(puppet, portal, update.notify_settings.mute_until)
async def _sync_dialog(self, portal: po.Portal, dialog: Dialog, should_create: bool,
puppet: Optional[pu.Puppet]) -> None:
was_created = False
if portal.mxid:
try:
await portal.backfill(self, last_id=dialog.message.id)
@@ -390,6 +470,7 @@ class User(AbstractUser, BaseUser):
elif should_create:
try:
await portal.create_matrix_room(self, dialog.entity, invites=[self.mxid])
was_created = True
except Exception:
self.log.exception(f"Error while creating {portal.tgid_log}")
if portal.mxid and puppet and puppet.is_real_user:
@@ -403,6 +484,10 @@ class User(AbstractUser, BaseUser):
dialog.dialog.read_inbox_max_id)
if last_read:
await puppet.intent.mark_read(last_read.mx_room, last_read.mxid)
if was_created or not config["bridge.tag_only_on_create"]:
await self._mute_room(puppet, portal, dialog.dialog.notify_settings.mute_until)
await self._tag_room(puppet, portal, config["bridge.pinned_tag"], dialog.pinned)
await self._tag_room(puppet, portal, config["bridge.archive_tag"], dialog.archived)
async def sync_dialogs(self) -> None:
if self.is_bot:
+2 -5
View File
@@ -30,7 +30,6 @@ from telethon.errors import (AuthBytesInvalidError, AuthKeyInvalidError, Locatio
SecurityError, FileIdInvalidError)
from mautrix.appservice import IntentAPI
from mautrix.util.network_retry import call_with_net_retry
from ..tgclient import MautrixTelegramClient
from ..db import TelegramFile as DBTelegramFile
@@ -145,8 +144,7 @@ async def transfer_thumbnail_to_matrix(client: MautrixTelegramClient, intent: In
if encrypt:
file, decryption_info = encrypt_attachment(file)
upload_mime_type = "application/octet-stream"
content_uri = await call_with_net_retry(intent.upload_media, file, upload_mime_type,
_action="upload media")
content_uri = await intent.upload_media(file, upload_mime_type)
if decryption_info:
decryption_info.url = content_uri
@@ -239,8 +237,7 @@ async def _unlocked_transfer_file_to_matrix(client: MautrixTelegramClient, inten
if encrypt and encrypt_attachment:
file, decryption_info = encrypt_attachment(file)
upload_mime_type = "application/octet-stream"
content_uri = await call_with_net_retry(intent.upload_media, file, upload_mime_type,
_action="upload media")
content_uri = await intent.upload_media(file, upload_mime_type)
if decryption_info:
decryption_info.url = content_uri
+2 -2
View File
@@ -15,13 +15,13 @@ qrcode>=6,<7
moviepy>=1,<2
#/metrics
prometheus_client>=0.6,<0.11
prometheus_client>=0.6,<0.12
#/postgres
psycopg2-binary>=2,<3
#/e2be
asyncpg>=0.20,<0.23
asyncpg>=0.20,<0.24
python-olm>=3,<4
pycryptodome>=3,<4
unpaddedbase64>=1,<2
+1 -1
View File
@@ -5,6 +5,6 @@ python-magic>=0.4,<0.5
commonmark>=0.8,<0.10
aiohttp>=3,<4
yarl>=1,<2
mautrix>=0.8.11,<0.9
mautrix>=0.9.3,<0.10
telethon>=1.20,<1.22
telethon-session-sqlalchemy>=0.2.14,<0.3