Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d033042ee1 | |||
| 2270f4fe40 | |||
| 6d208b37a5 | |||
| 55ebaef6e3 | |||
| 215f077cf0 | |||
| 4e4f409f87 | |||
| 4d145f4716 | |||
| b833a41a88 | |||
| 768d51c4ae | |||
| f7db298fda | |||
| 4f2118c7ee | |||
| 4f0770b92d | |||
| 1fb8a7a0a5 | |||
| f79ab283f3 | |||
| 23ec691128 | |||
| 59213ebeae | |||
| 36b2f6af2e | |||
| b2249f7756 | |||
| 212023d296 | |||
| 4b03134620 | |||
| 806eea53eb | |||
| 4ca3ee58ac | |||
| 8b003f1187 | |||
| c06a2b2473 | |||
| f2194c6f33 | |||
| b5c294a558 | |||
| c6b6ec048e | |||
| fb461109c1 | |||
| 0411affc88 | |||
| dfe22800dd | |||
| 7868b05ed3 | |||
| 0474f81044 | |||
| ed471a6623 |
@@ -14,6 +14,7 @@ __pycache__
|
||||
/registration.yaml
|
||||
*.log*
|
||||
*.db
|
||||
*.db-*
|
||||
/*.pickle
|
||||
*.bak
|
||||
/*.session
|
||||
|
||||
@@ -1,3 +1,36 @@
|
||||
# v0.14.2 (2023-09-19)
|
||||
|
||||
* **Security:** Updated Pillow to 10.0.1.
|
||||
* Added support for double puppeting with arbitrary `as_token`s.
|
||||
See [docs](https://docs.mau.fi/bridges/general/double-puppeting.html#appservice-method-new) for more info.
|
||||
* Added support for sending webm and tgs files as stickers.
|
||||
* Updated to Telegram API layer 161.
|
||||
* Fixed cached usernames for Telegram users being cleared incorrectly, leading
|
||||
to mentions not being bridged as usernames.
|
||||
* Fixed reaction bridging failing if the server running the bridge was rebooted
|
||||
less than 12 hours ago.
|
||||
|
||||
# v0.14.1 (2023-06-26)
|
||||
|
||||
### Added
|
||||
* Added option to delete megolm sessions that were received before the
|
||||
automatic ratcheting options were introduced.
|
||||
* Added config option to use IPv6 for Telegram connection
|
||||
(thanks to [@exciler] in [#920]).
|
||||
|
||||
### Improved
|
||||
* Dropped support for Python 3.8.
|
||||
* Updated Docker image to Alpine 3.18.
|
||||
* Added timeout for forward backfills to prevent it from getting stuck
|
||||
permanently.
|
||||
|
||||
### Fixed
|
||||
* Fixed `bridge.filter.users` config option not being read correctly.
|
||||
* Fixed proxy support to use python-socks instead of pysocks.
|
||||
|
||||
[@exciler]: https://github.com/exciler
|
||||
[#920]: https://github.com/mautrix/telegram/pull/920
|
||||
|
||||
# v0.14.0 (2023-05-26)
|
||||
|
||||
### Added
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
FROM dock.mau.dev/tulir/lottieconverter:alpine-3.17
|
||||
FROM dock.mau.dev/tulir/lottieconverter:alpine-3.18
|
||||
|
||||
RUN apk add --no-cache \
|
||||
python3 py3-pip py3-setuptools py3-wheel \
|
||||
py3-pillow \
|
||||
#py3-pillow \
|
||||
py3-aiohttp \
|
||||
py3-magic \
|
||||
py3-ruamel.yaml \
|
||||
@@ -14,8 +14,6 @@ RUN apk add --no-cache \
|
||||
py3-idna \
|
||||
py3-rsa \
|
||||
#py3-telethon \ (outdated)
|
||||
# Optional for socks proxies
|
||||
py3-pysocks \
|
||||
py3-pyaes \
|
||||
# cryptg
|
||||
py3-cffi \
|
||||
@@ -34,7 +32,9 @@ RUN apk add --no-cache \
|
||||
bash \
|
||||
curl \
|
||||
jq \
|
||||
yq
|
||||
yq \
|
||||
# Temporarily install pillow from edge repo to get up-to-date version
|
||||
&& apk add --no-cache py3-pillow --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
|
||||
|
||||
COPY requirements.txt /opt/mautrix-telegram/requirements.txt
|
||||
COPY optional-requirements.txt /opt/mautrix-telegram/optional-requirements.txt
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__version__ = "0.14.0"
|
||||
__version__ = "0.14.2"
|
||||
__author__ = "Tulir Asokan <tulir@maunium.net>"
|
||||
|
||||
@@ -104,8 +104,6 @@ class TelegramBridge(Bridge):
|
||||
self.log.info("Finished re-sending bridge info state events")
|
||||
|
||||
def prepare_stop(self) -> None:
|
||||
for puppet in Puppet.by_custom_mxid.values():
|
||||
puppet.stop()
|
||||
self.add_shutdown_actions(user.stop() for user in User.by_tgid.values())
|
||||
if self.bot:
|
||||
self.add_shutdown_actions(self.bot.stop())
|
||||
|
||||
@@ -208,6 +208,8 @@ class AbstractUser(ABC):
|
||||
sysversion = self.config["telegram.device_info.system_version"]
|
||||
appversion = self.config["telegram.device_info.app_version"]
|
||||
connection, proxy = self._proxy_settings
|
||||
if proxy:
|
||||
self.log.debug(f"Using proxy setting: {proxy}")
|
||||
|
||||
assert isinstance(session, Session)
|
||||
|
||||
@@ -235,6 +237,7 @@ class AbstractUser(ABC):
|
||||
loop=self.loop,
|
||||
base_logger=base_logger,
|
||||
update_error_callback=self._telethon_update_error_callback,
|
||||
use_ipv6=self.config["telegram.connection.use_ipv6"],
|
||||
)
|
||||
self.client.add_event_handler(self._update_catch)
|
||||
|
||||
@@ -708,6 +711,22 @@ class AbstractUser(ABC):
|
||||
self.log.debug("Ignoring relaybot-sent message %s to %s", update.id, portal.tgid_log)
|
||||
return
|
||||
|
||||
task = self._call_portal_message_handler(update, original_update, portal, sender)
|
||||
if portal.backfill_lock.locked:
|
||||
self.log.debug(
|
||||
f"{portal.tgid_log} is backfill locked, moving incoming message to async task"
|
||||
)
|
||||
background_task.create(task)
|
||||
else:
|
||||
await task
|
||||
|
||||
async def _call_portal_message_handler(
|
||||
self,
|
||||
update: UpdateMessageContent,
|
||||
original_update: UpdateMessage,
|
||||
portal: po.Portal,
|
||||
sender: pu.Puppet,
|
||||
) -> None:
|
||||
await portal.backfill_lock.wait(f"update {update.id}")
|
||||
|
||||
if isinstance(update, MessageService):
|
||||
|
||||
@@ -39,8 +39,6 @@ async def clear_db_cache(evt: CommandEvent) -> EventID:
|
||||
await evt.reply("Cleared portal cache")
|
||||
elif section == "puppet":
|
||||
pu.Puppet.by_tgid = {}
|
||||
for puppet in pu.Puppet.by_custom_mxid.values():
|
||||
puppet.stop()
|
||||
pu.Puppet.by_custom_mxid = {}
|
||||
await asyncio.gather(
|
||||
*[puppet.try_start() async for puppet in pu.Puppet.all_with_custom_mxid()]
|
||||
@@ -69,8 +67,6 @@ async def reload_user(evt: CommandEvent) -> EventID:
|
||||
if not user:
|
||||
return await evt.reply("User not found")
|
||||
puppet = await pu.Puppet.get_by_custom_mxid(mxid)
|
||||
if puppet:
|
||||
puppet.stop()
|
||||
await user.stop()
|
||||
del u.User.by_tgid[user.tgid]
|
||||
del u.User.by_mxid[user.mxid]
|
||||
|
||||
@@ -194,6 +194,7 @@ class Config(BaseBridgeConfig):
|
||||
copy("bridge.backfill.forward_limits.sync.normal_group")
|
||||
copy("bridge.backfill.forward_limits.sync.supergroup")
|
||||
copy("bridge.backfill.forward_limits.sync.channel")
|
||||
copy("bridge.backfill.forward_timeout")
|
||||
copy("bridge.backfill.incremental.messages_per_batch")
|
||||
copy("bridge.backfill.incremental.post_batch_delay")
|
||||
copy("bridge.backfill.incremental.max_batches.user")
|
||||
@@ -268,6 +269,7 @@ class Config(BaseBridgeConfig):
|
||||
copy("telegram.connection.retry_delay")
|
||||
copy("telegram.connection.flood_sleep_threshold")
|
||||
copy("telegram.connection.request_retries")
|
||||
copy("telegram.connection.use_ipv6")
|
||||
|
||||
copy("telegram.device_info.device_model")
|
||||
copy("telegram.device_info.system_version")
|
||||
|
||||
@@ -40,7 +40,7 @@ appservice:
|
||||
|
||||
# The full URI to the database. SQLite and Postgres are supported.
|
||||
# Format examples:
|
||||
# SQLite: sqlite:///filename.db
|
||||
# SQLite: sqlite:filename.db
|
||||
# Postgres: postgres://username:password@hostname/dbname
|
||||
database: postgres://username:password@hostname/dbname
|
||||
# Additional arguments for asyncpg.create_pool() or sqlite3.connect()
|
||||
@@ -291,6 +291,10 @@ bridge:
|
||||
delete_on_device_delete: false
|
||||
# Periodically delete megolm sessions when 2x max_age has passed since receiving the session.
|
||||
periodically_delete_expired: false
|
||||
# Delete inbound megolm sessions that don't have the received_at field used for
|
||||
# automatic ratcheting and expired session deletion. This is meant as a migration
|
||||
# to delete old keys prior to the bridge update.
|
||||
delete_outdated_inbound: false
|
||||
# What level of device verification should be required from users?
|
||||
#
|
||||
# Valid levels:
|
||||
@@ -326,6 +330,10 @@ bridge:
|
||||
# default.
|
||||
messages: 100
|
||||
|
||||
# Disable rotating keys when a user's devices change?
|
||||
# You should not enable this option unless you understand all the implications.
|
||||
disable_device_change_key_rotation: false
|
||||
|
||||
# Whether to explicitly set the avatar and room name for private chat portal rooms.
|
||||
# If set to `default`, this will be enabled in encrypted rooms and disabled in unencrypted rooms.
|
||||
# If set to `always`, all DM rooms will have explicit names and avatars set.
|
||||
@@ -412,6 +420,8 @@ bridge:
|
||||
normal_group: 100
|
||||
supergroup: 100
|
||||
channel: 100
|
||||
# Timeout for forward backfills in seconds. If you have a high limit, you'll have to increase this too.
|
||||
forward_timeout: 900
|
||||
|
||||
# Settings for incremental backfill of history. These only apply when using MSC2716.
|
||||
incremental:
|
||||
@@ -604,6 +614,8 @@ telegram:
|
||||
# is not recommended, since some requests can always trigger a call fail (such as searching
|
||||
# for messages).
|
||||
request_retries: 5
|
||||
# Use IPv6 for Telethon connection
|
||||
use_ipv6: false
|
||||
|
||||
# Device info sent to Telegram.
|
||||
device_info:
|
||||
|
||||
+52
-11
@@ -99,6 +99,7 @@ from telethon.tl.types import (
|
||||
DocumentAttributeAudio,
|
||||
DocumentAttributeFilename,
|
||||
DocumentAttributeImageSize,
|
||||
DocumentAttributeSticker,
|
||||
DocumentAttributeVideo,
|
||||
GeoPoint,
|
||||
InputChannel,
|
||||
@@ -110,6 +111,7 @@ from telethon.tl.types import (
|
||||
InputPeerChat,
|
||||
InputPeerPhotoFileLocation,
|
||||
InputPeerUser,
|
||||
InputStickerSetEmpty,
|
||||
InputUser,
|
||||
MessageActionChannelCreate,
|
||||
MessageActionChatAddUser,
|
||||
@@ -488,7 +490,7 @@ class Portal(DBPortal, BasePortal):
|
||||
cls.private_chat_portal_meta = cls.config["bridge.private_chat_portal_meta"]
|
||||
cls.filter_mode = cls.config["bridge.filter.mode"]
|
||||
cls.filter_list = cls.config["bridge.filter.list"]
|
||||
cls.filter_users = cls.config["bridge.filter.filter_users"]
|
||||
cls.filter_users = cls.config["bridge.filter.users"]
|
||||
cls.hs_domain = cls.config["homeserver.domain"]
|
||||
cls.backfill_msc2716 = cls.config["bridge.backfill.msc2716"]
|
||||
cls.backfill_enable = cls.config["bridge.backfill.enable"]
|
||||
@@ -1841,6 +1843,7 @@ class Portal(DBPortal, BasePortal):
|
||||
max_image_size = self.config["bridge.image_as_file_size"] * 1000**2
|
||||
max_image_pixels = self.config["bridge.image_as_file_pixels"]
|
||||
|
||||
attributes = []
|
||||
if self.config["bridge.parallel_file_transfer"] and content.url:
|
||||
file_handle, file_size = await util.parallel_transfer_to_telegram(
|
||||
client, self.main_intent, content.url, sender_id
|
||||
@@ -1860,21 +1863,27 @@ class Portal(DBPortal, BasePortal):
|
||||
file = await self.main_intent.download_media(content.url)
|
||||
|
||||
if content.msgtype == MessageType.STICKER:
|
||||
if mime != "image/gif":
|
||||
mime, file, w, h = util.convert_image(
|
||||
file, source_mime=mime, target_type="webp"
|
||||
)
|
||||
else:
|
||||
if mime == "image/gif":
|
||||
# Remove sticker description
|
||||
file_name = "sticker.gif"
|
||||
else:
|
||||
if mime not in ("video/webm", "application/x-tgsticker"):
|
||||
mime, file, w, h = util.convert_image(
|
||||
file, source_mime=mime, target_type="webp"
|
||||
)
|
||||
attributes.append(
|
||||
DocumentAttributeSticker(
|
||||
alt=content.body, stickerset=InputStickerSetEmpty()
|
||||
)
|
||||
)
|
||||
|
||||
file_handle = await client.upload_file(file)
|
||||
file_size = len(file)
|
||||
|
||||
file_handle.name = file_name
|
||||
force_document = file_size >= max_image_size
|
||||
attributes.append(DocumentAttributeFilename(file_name=file_name))
|
||||
|
||||
attributes = [DocumentAttributeFilename(file_name=file_name)]
|
||||
if content.msgtype == MessageType.VIDEO:
|
||||
attributes.append(
|
||||
DocumentAttributeVideo(
|
||||
@@ -2047,7 +2056,7 @@ class Portal(DBPortal, BasePortal):
|
||||
response: TypeMessage,
|
||||
msgtype: MessageType | None = None,
|
||||
) -> None:
|
||||
self.log.trace("Handled Matrix message: %s", response)
|
||||
self.log.trace("Raw event handling response for %s: %s", event_id, response)
|
||||
event_hash, _ = self.dedup.check(response, (event_id, space), force_hash=edit_index != 0)
|
||||
if edit_index < 0:
|
||||
prev_edit = await DBMessage.get_one_by_tgid(TelegramID(response.id), space, -1)
|
||||
@@ -2077,6 +2086,9 @@ class Portal(DBPortal, BasePortal):
|
||||
seconds=response.ttl_period,
|
||||
expires_at=int(response.date.timestamp()) + response.ttl_period,
|
||||
)
|
||||
self.log.debug(
|
||||
f"Handled Matrix message {event_id} -> {response.id} (edit index {edit_index})"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _error_to_human_message(err: Exception) -> str | None:
|
||||
@@ -2420,6 +2432,10 @@ class Portal(DBPortal, BasePortal):
|
||||
await deleter.client(
|
||||
SendReactionRequest(peer=self.peer, msg_id=msg.tgid, reaction=new_reactions)
|
||||
)
|
||||
self.log.debug(
|
||||
f"Handled Matrix deletion of reaction {event_id} to {msg.tgid} "
|
||||
f"(new reaction count: {len(new_reactions) if new_reactions else 0})"
|
||||
)
|
||||
|
||||
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
|
||||
@@ -2440,6 +2456,7 @@ class Portal(DBPortal, BasePortal):
|
||||
else:
|
||||
await message.mark_redacted()
|
||||
await real_deleter.client.delete_messages(self.peer, [message.tgid])
|
||||
self.log.debug(f"Handled Matrix redaction of {event_id} / {message.tgid}")
|
||||
|
||||
async def handle_matrix_reaction(
|
||||
self, user: u.User, target_event_id: EventID, emoji: str, reaction_event_id: EventID
|
||||
@@ -2546,9 +2563,15 @@ class Portal(DBPortal, BasePortal):
|
||||
SendReactionRequest(peer=self.peer, msg_id=msg.tgid, reaction=new_tg_reactions)
|
||||
)
|
||||
puppet = await user.get_puppet()
|
||||
removed = 0
|
||||
for db_reaction in reactions_to_remove:
|
||||
removed += 1
|
||||
await db_reaction.delete()
|
||||
await puppet.intent_for(self).redact(db_reaction.mx_room, db_reaction.mxid)
|
||||
self.log.debug(
|
||||
f"Handled Matrix reaction {reaction_event_id} to {msg.tgid} "
|
||||
f"(new reaction count: {len(new_tg_reactions)}, removed {removed} old reactions)"
|
||||
)
|
||||
await DBReaction(
|
||||
mxid=reaction_event_id,
|
||||
mx_room=self.mxid,
|
||||
@@ -2862,10 +2885,15 @@ class Portal(DBPortal, BasePortal):
|
||||
)
|
||||
if limit == 0:
|
||||
return "Limit is zero, not backfilling"
|
||||
timeout = self.config["bridge.backfill.forward_timeout"]
|
||||
with self.backfill_lock:
|
||||
output = await self.backfill(
|
||||
task = self.backfill(
|
||||
source, client, forward=True, forward_limit=limit, last_tgid=last_tgid
|
||||
)
|
||||
if timeout > 0:
|
||||
output = await asyncio.wait_for(task, timeout=timeout)
|
||||
else:
|
||||
output = await task
|
||||
self.log.debug(f"Forward backfill complete, status: {output}")
|
||||
return output
|
||||
|
||||
@@ -3129,9 +3157,16 @@ class Portal(DBPortal, BasePortal):
|
||||
anchor_id = 2**31 - 1
|
||||
minmax = {}
|
||||
self.log.debug(f"Iterating messages through {source.tgid} with {limit=}, {minmax}")
|
||||
delay_warn_handle = self.loop.call_later(
|
||||
5 * 60, lambda: self.log.warning("Iterating messages is taking long")
|
||||
)
|
||||
# Iterate messages newest to oldest and collect the results
|
||||
async for msg in client.iter_messages(entity, limit=limit, **minmax):
|
||||
message_count += 1
|
||||
if message_count == 1:
|
||||
self.log.debug(f"Backfill iter: got first message {msg.id}")
|
||||
elif message_count % 50 == 0:
|
||||
self.log.debug(f"Backfill iter: got {message_count} messages so far (at {msg.id})")
|
||||
if (forward and msg.id <= anchor_id) or (not forward and msg.id >= anchor_id):
|
||||
continue
|
||||
elif isinstance(msg, MessageService):
|
||||
@@ -3156,6 +3191,7 @@ class Portal(DBPortal, BasePortal):
|
||||
events.append(await self._wrap_batch_msg(intent, msg, converted, caption=True))
|
||||
intents.append(intent)
|
||||
metas.append(None)
|
||||
delay_warn_handle.cancel()
|
||||
if len(events) == 0:
|
||||
self.log.debug(
|
||||
f"Didn't get any events to send out of {message_count} messages fetched "
|
||||
@@ -3214,9 +3250,13 @@ class Portal(DBPortal, BasePortal):
|
||||
for item in counts:
|
||||
if item.count == 2:
|
||||
reactions += [
|
||||
MessagePeerReaction(reaction=item.reaction, peer_id=PeerUser(self.tgid)),
|
||||
MessagePeerReaction(
|
||||
reaction=item.reaction, peer_id=PeerUser(self.tg_receiver)
|
||||
reaction=item.reaction, peer_id=PeerUser(self.tgid), date=None
|
||||
),
|
||||
MessagePeerReaction(
|
||||
reaction=item.reaction,
|
||||
peer_id=PeerUser(self.tg_receiver),
|
||||
date=None,
|
||||
),
|
||||
]
|
||||
elif item.count == 1:
|
||||
@@ -3224,6 +3264,7 @@ class Portal(DBPortal, BasePortal):
|
||||
MessagePeerReaction(
|
||||
reaction=item.reaction,
|
||||
peer_id=PeerUser(self.tg_receiver if item.chosen_order else self.tgid),
|
||||
date=None,
|
||||
)
|
||||
)
|
||||
return reactions
|
||||
|
||||
@@ -34,6 +34,8 @@ from telethon.tl.types import (
|
||||
DocumentAttributeVideo,
|
||||
Game,
|
||||
InputPhotoFileLocation,
|
||||
InputStickerSetID,
|
||||
InputStickerSetShortName,
|
||||
Message,
|
||||
MessageEntityPre,
|
||||
MessageMediaContact,
|
||||
@@ -42,11 +44,14 @@ from telethon.tl.types import (
|
||||
MessageMediaGame,
|
||||
MessageMediaGeo,
|
||||
MessageMediaGeoLive,
|
||||
MessageMediaInvoice,
|
||||
MessageMediaPhoto,
|
||||
MessageMediaPoll,
|
||||
MessageMediaStory,
|
||||
MessageMediaUnsupported,
|
||||
MessageMediaVenue,
|
||||
MessageMediaWebPage,
|
||||
MessageReplyStoryHeader,
|
||||
PeerChannel,
|
||||
PeerUser,
|
||||
Photo,
|
||||
@@ -104,6 +109,7 @@ class DocAttrs(NamedTuple):
|
||||
mime_type: str | None
|
||||
is_sticker: bool
|
||||
sticker_alt: str | None
|
||||
sticker_pack_ref: dict | None
|
||||
width: int
|
||||
height: int
|
||||
is_gif: bool
|
||||
@@ -142,6 +148,8 @@ class TelegramMessageConverter:
|
||||
MessageMediaUnsupported: self._convert_unsupported,
|
||||
MessageMediaGame: self._convert_game,
|
||||
MessageMediaContact: self._convert_contact,
|
||||
MessageMediaStory: self._convert_story,
|
||||
MessageMediaInvoice: self._convert_invoice,
|
||||
}
|
||||
self._allowed_media = tuple(self._media_converters.keys())
|
||||
|
||||
@@ -252,6 +260,8 @@ class TelegramMessageConverter:
|
||||
) -> None:
|
||||
if not evt.reply_to:
|
||||
return
|
||||
elif isinstance(evt.reply_to, MessageReplyStoryHeader):
|
||||
return
|
||||
space = (
|
||||
evt.peer_id.channel_id
|
||||
if isinstance(evt, Message) and isinstance(evt.peer_id, PeerChannel)
|
||||
@@ -700,10 +710,33 @@ class TelegramMessageConverter:
|
||||
)
|
||||
return ConvertedMessage(content=content)
|
||||
|
||||
@staticmethod
|
||||
async def _convert_story(
|
||||
source: au.AbstractUser, evt: Message, client: MautrixTelegramClient, **_
|
||||
) -> ConvertedMessage:
|
||||
content = await formatter.telegram_to_matrix(
|
||||
evt, source, client, override_text="Stories are not yet supported"
|
||||
)
|
||||
content.msgtype = MessageType.NOTICE
|
||||
content["fi.mau.telegram.unsupported"] = True
|
||||
return ConvertedMessage(content=content)
|
||||
|
||||
@staticmethod
|
||||
async def _convert_invoice(
|
||||
source: au.AbstractUser, evt: Message, client: MautrixTelegramClient, **_
|
||||
) -> ConvertedMessage:
|
||||
content = await formatter.telegram_to_matrix(
|
||||
evt, source, client, override_text="Invoices are not yet supported"
|
||||
)
|
||||
content.msgtype = MessageType.NOTICE
|
||||
content["fi.mau.telegram.unsupported"] = True
|
||||
return ConvertedMessage(content=content)
|
||||
|
||||
|
||||
def _parse_document_attributes(attributes: list[TypeDocumentAttribute]) -> DocAttrs:
|
||||
name, mime_type, is_sticker, sticker_alt, width, height = None, None, False, None, 0, 0
|
||||
is_gif, is_audio, is_voice, duration, waveform = False, False, False, 0, bytes()
|
||||
sticker_pack_ref = None
|
||||
for attr in attributes:
|
||||
if isinstance(attr, DocumentAttributeFilename):
|
||||
name = name or attr.file_name
|
||||
@@ -711,6 +744,13 @@ def _parse_document_attributes(attributes: list[TypeDocumentAttribute]) -> DocAt
|
||||
elif isinstance(attr, DocumentAttributeSticker):
|
||||
is_sticker = True
|
||||
sticker_alt = attr.alt
|
||||
if isinstance(attr.stickerset, InputStickerSetID):
|
||||
sticker_pack_ref = {
|
||||
"id": str(attr.stickerset.id),
|
||||
"access_hash": str(attr.stickerset.access_hash),
|
||||
}
|
||||
elif isinstance(attr.stickerset, InputStickerSetShortName):
|
||||
sticker_pack_ref = {"short_name": attr.stickerset.short_name}
|
||||
elif isinstance(attr, DocumentAttributeAnimated):
|
||||
is_gif = True
|
||||
elif isinstance(attr, DocumentAttributeVideo):
|
||||
@@ -728,6 +768,7 @@ def _parse_document_attributes(attributes: list[TypeDocumentAttribute]) -> DocAt
|
||||
mime_type,
|
||||
is_sticker,
|
||||
sticker_alt,
|
||||
sticker_pack_ref,
|
||||
width,
|
||||
height,
|
||||
is_gif,
|
||||
@@ -760,6 +801,13 @@ def _parse_document_meta(
|
||||
mime_type = file.mime_type or document.mime_type
|
||||
info = ImageInfo(size=file.size, mimetype=mime_type)
|
||||
|
||||
if attrs.is_sticker:
|
||||
info["fi.mau.telegram.sticker"] = {
|
||||
"alt": attrs.sticker_alt,
|
||||
"id": str(document.id),
|
||||
"pack": attrs.sticker_pack_ref,
|
||||
}
|
||||
|
||||
if attrs.mime_type and not file.was_converted:
|
||||
file.mime_type = attrs.mime_type or file.mime_type
|
||||
if file.width and file.height:
|
||||
|
||||
@@ -269,11 +269,14 @@ class Puppet(DBPuppet, BasePuppet):
|
||||
is_bot != self.is_bot or is_channel != self.is_channel or is_premium != self.is_premium
|
||||
)
|
||||
|
||||
self.is_bot = is_bot
|
||||
if is_bot is not None:
|
||||
self.is_bot = is_bot
|
||||
self.is_channel = is_channel
|
||||
self.is_premium = is_premium
|
||||
if is_premium is not None:
|
||||
self.is_premium = is_premium
|
||||
|
||||
if self.username != info.username:
|
||||
if self.username != info.username and (info.username or not info.min):
|
||||
self.log.debug(f"Updating username {self.username} -> {info.username}")
|
||||
self.username = info.username
|
||||
changed = True
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from telethon.tl.patched import Message
|
||||
from telethon.tl.types import (
|
||||
InputMediaUploadedDocument,
|
||||
InputMediaUploadedPhoto,
|
||||
InputReplyToMessage,
|
||||
TypeDocumentAttribute,
|
||||
TypeInputMedia,
|
||||
TypeInputPeer,
|
||||
@@ -67,6 +68,10 @@ class MautrixTelegramClient(TelegramClient):
|
||||
entity = await self.get_input_entity(entity)
|
||||
reply_to = utils.get_message_id(reply_to)
|
||||
request = SendMediaRequest(
|
||||
entity, media, message=caption or "", entities=entities or [], reply_to_msg_id=reply_to
|
||||
entity,
|
||||
media,
|
||||
message=caption or "",
|
||||
entities=entities or [],
|
||||
reply_to=InputReplyToMessage(reply_to_msg_id=reply_to) if reply_to else None,
|
||||
)
|
||||
return self._get_response_message(request, await self(request), entity)
|
||||
|
||||
@@ -617,8 +617,11 @@ class User(DBUser, AbstractUser, BaseUser):
|
||||
await self.stop()
|
||||
await sess.delete()
|
||||
|
||||
# Drop LOGGED_OUT states if the user was already logged out previously
|
||||
# and doesn't have a remote ID anymore
|
||||
# TODO send a management room notice for non-manual logouts?
|
||||
await self.push_bridge_state(state, error=error, message=message)
|
||||
if self.tgid or state != BridgeStateEvent.LOGGED_OUT:
|
||||
await self.push_bridge_state(state, error=error, message=message)
|
||||
if delete:
|
||||
await self.delete()
|
||||
self.by_mxid.pop(self.mxid, None)
|
||||
@@ -974,11 +977,18 @@ class User(DBUser, AbstractUser, BaseUser):
|
||||
self.log.debug("Contact syncing complete")
|
||||
return contacts
|
||||
|
||||
@property
|
||||
def _available_reactions_up_to_date(self) -> bool:
|
||||
return (
|
||||
bool(self._available_emoji_reactions)
|
||||
and self._available_emoji_reactions_fetched + 12 * 60 * 60 > time.monotonic()
|
||||
)
|
||||
|
||||
async def get_available_reactions(self) -> set[str]:
|
||||
if self._available_emoji_reactions_fetched + 12 * 60 * 60 > time.monotonic():
|
||||
if self._available_reactions_up_to_date:
|
||||
return self._available_emoji_reactions
|
||||
async with self._available_emoji_reactions_lock:
|
||||
if self._available_emoji_reactions_fetched + 12 * 60 * 60 > time.monotonic():
|
||||
if self._available_reactions_up_to_date:
|
||||
return self._available_emoji_reactions
|
||||
self.log.debug("Fetching available emoji reactions")
|
||||
available_reactions = await self.client(
|
||||
@@ -988,13 +998,18 @@ class User(DBUser, AbstractUser, BaseUser):
|
||||
self._available_emoji_reactions = {
|
||||
react.reaction
|
||||
for react in available_reactions.reactions
|
||||
if self.is_premium or not react.premium
|
||||
if not react.inactive and (self.is_premium or not react.premium)
|
||||
}
|
||||
self._available_emoji_reactions_hash = available_reactions.hash
|
||||
self._available_emoji_reactions_fetched = time.monotonic()
|
||||
self.log.debug(
|
||||
"Got available emoji reactions: %s", self._available_emoji_reactions
|
||||
)
|
||||
elif self._available_emoji_reactions is None:
|
||||
self.log.warning(
|
||||
f"Got {available_reactions} in response to available reactions request"
|
||||
" even though nothing is cached"
|
||||
)
|
||||
return self._available_emoji_reactions
|
||||
|
||||
def tl_to_json(self) -> Any:
|
||||
|
||||
@@ -7,14 +7,14 @@ aiodns
|
||||
brotli
|
||||
|
||||
#/qr_login
|
||||
pillow>=4,<10
|
||||
pillow>=10.0.1,<11
|
||||
qrcode>=6,<8
|
||||
|
||||
#/formattednumbers
|
||||
phonenumbers>=8,<9
|
||||
|
||||
#/metrics
|
||||
prometheus_client>=0.6,<0.17
|
||||
prometheus_client>=0.6,<0.18
|
||||
|
||||
#/e2be
|
||||
python-olm>=3,<4
|
||||
@@ -23,3 +23,6 @@ unpaddedbase64>=1,<3
|
||||
|
||||
#/sqlite
|
||||
aiosqlite>=0.16,<0.20
|
||||
|
||||
#/proxy
|
||||
python-socks[asyncio]
|
||||
|
||||
+3
-3
@@ -3,8 +3,8 @@ python-magic>=0.4,<0.5
|
||||
commonmark>=0.8,<0.10
|
||||
aiohttp>=3,<4
|
||||
yarl>=1,<2
|
||||
mautrix>=0.19.14,<0.20
|
||||
tulir-telethon==1.28.0a9
|
||||
asyncpg>=0.20,<0.28
|
||||
mautrix>=0.20.2,<0.21
|
||||
tulir-telethon==1.30.0a2
|
||||
asyncpg>=0.20,<0.29
|
||||
mako>=1,<2
|
||||
setuptools
|
||||
|
||||
@@ -51,7 +51,7 @@ setuptools.setup(
|
||||
|
||||
install_requires=install_requires,
|
||||
extras_require=extras_require,
|
||||
python_requires="~=3.8",
|
||||
python_requires="~=3.9",
|
||||
|
||||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
@@ -60,9 +60,9 @@ setuptools.setup(
|
||||
"Framework :: AsyncIO",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
],
|
||||
package_data={"mautrix_telegram": [
|
||||
"web/public/*.mako", "web/public/*.png", "web/public/*.css",
|
||||
|
||||
Reference in New Issue
Block a user