Compare commits

...

15 Commits

Author SHA1 Message Date
Tulir Asokan 4b57be3917 Bump version to 0.8.1 2020-06-08 17:45:19 +03:00
Tulir Asokan 9383e5eed2 Allow any 0.5.x version of mautrix-python
Fixes #479
2020-06-08 12:36:18 +03:00
Tulir Asokan a3b4a5e30e Update Docker image to Alpine 3.12 2020-06-06 20:10:14 +03:00
Tulir Asokan 72a45d7d80 Bump version to 0.8.0 2020-06-03 15:37:07 +03:00
Tulir Asokan bcf464428a Bump version to 0.8.0rc5 2020-05-30 13:18:58 +03:00
Tulir Asokan f3b9f4bf73 Bump maximum Telethon version 2020-05-29 15:28:53 +03:00
Tulir Asokan 10e54ed789 Add option to send delivery error notices 2020-05-29 15:28:41 +03:00
Tulir Asokan 35da8df526 Add option to disable removing avatars from Telegram ghosts
There's no way to determine whether an avatar is removed or just hidden
from some users, so avatars are not removed by default.
2020-05-29 15:27:38 +03:00
Tulir Asokan fb1ab220ff Update ROADMAP.md 2020-05-28 12:56:56 +03:00
Tulir Asokan 2dd39fddf0 Try to prevent infinite loop of state changes with double puppeting
Fixes #464
2020-05-27 12:36:51 +03:00
Tulir Asokan 7f69e9f329 Bump mautrix-python version 2020-05-25 14:11:03 +03:00
Tulir Asokan 3f6a4237ad Add option to send read receipt on confirmed delivery to Telegram 2020-05-25 13:25:37 +03:00
Tulir Asokan ee04e8c17f Bump mautrix-python req to rc1 2020-05-22 22:19:36 +03:00
Tulir Asokan 7d75c15027 Actually fix branch/tag condition in CI 2020-05-22 22:19:27 +03:00
Tulir Asokan 312a44d361 Add sponsors section to README.md 2020-05-22 22:00:23 +03:00
15 changed files with 154 additions and 107 deletions
+2 -2
View File
@@ -36,6 +36,6 @@ manifest:
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-amd64
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-arm64
- if [ $CI_COMMIT_BRANCH = "master" ]; then docker manifest create $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-amd64 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-arm64 && docker manifest push $CI_REGISTRY_IMAGE:latest; fi
- if [ $CI_COMMIT_BRANCH != "master" ]; then docker manifest create $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-amd64 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-arm64 && docker manifest push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME; fi
- if [ "$CI_COMMIT_BRANCH" = "master" ]; then docker manifest create $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-amd64 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-arm64 && docker manifest push $CI_REGISTRY_IMAGE:latest; fi
- if [ "$CI_COMMIT_BRANCH" != "master" ]; then docker manifest create $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-amd64 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-arm64 && docker manifest push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME; fi
- docker rmi $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-amd64 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA-arm64
+12 -12
View File
@@ -1,16 +1,18 @@
FROM dock.mau.dev/tulir/lottieconverter:alpine-3.11
FROM dock.mau.dev/tulir/lottieconverter:alpine-3.12
RUN echo "@edge_main http://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories
RUN echo "@edge_testing http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
RUN echo "@edge_community 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 \
py3-pillow \
py3-aiohttp \
py3-magic \
py3-sqlalchemy \
py3-telethon-session-sqlalchemy@edge_testing \
py3-alembic@edge_testing \
py3-python-editor@edge_community \
py3-psycopg2 \
py3-ruamel.yaml \
py3-commonmark@edge_testing \
@@ -22,8 +24,7 @@ RUN apk add --no-cache \
py3-requests \
#imageio
py3-numpy \
#telethon
py3-rsa \
py3-telethon@edge_testing \
# Optional for socks proxies
py3-pysocks \
# cryptg
@@ -35,19 +36,18 @@ RUN apk add --no-cache \
su-exec \
netcat-openbsd \
# olm
olm-dev@edge_community \
olm-dev \
# matrix-nio?
py3-future \
py3-atomicwrites \
py3-pycryptodome@edge_main \
py3-peewee@edge_community \
py3-pyrsistent@edge_community \
py3-pycryptodome \
py3-peewee \
py3-pyrsistent \
py3-jsonschema \
py3-aiofiles \
py3-cachetools@edge_community \
py3-prometheus-client@edge_community \
#py3-aiofiles \ # (too new)
py3-cachetools \
py3-unpaddedbase64 \
py3-pyaes@edge_testing \
py3-h2@edge_testing \
py3-logbook@edge_testing
COPY requirements.txt /opt/mautrix-telegram/requirements.txt
+3
View File
@@ -7,6 +7,9 @@
A Matrix-Telegram hybrid puppeting/relaybot bridge.
## Sponsors
* [Joel Lehtonen / Zouppen](https://github.com/zouppen)
### [Wiki](https://github.com/tulir/mautrix-telegram/wiki)
### [Features & Roadmap](https://github.com/tulir/mautrix-telegram/blob/master/ROADMAP.md)
+3
View File
@@ -29,6 +29,9 @@
* [x] Message deletions
* [x] Message edits
* [ ] Message history
* [x] Manually (`!tg backfill`)
* [ ] Automatically when creating portal
* [ ] Automatically for missed messages
* [x] Avatars
* [x] Presence
* [x] Typing notifications
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = "0.8.0rc3"
__version__ = "0.8.1"
__author__ = "Tulir Asokan <tulir@maunium.net>"
+9 -30
View File
@@ -45,26 +45,18 @@ class Config(BaseBridgeConfig):
]
def do_update(self, helper: ConfigUpdateHelper) -> None:
super().do_update(helper)
copy, copy_dict, base = helper
copy("homeserver.address")
copy("homeserver.domain")
copy("homeserver.verify_ssl")
if "appservice.protocol" in self and "appservice.address" not in self:
protocol, hostname, port = (self["appservice.protocol"], self["appservice.hostname"],
self["appservice.port"])
base["appservice.address"] = f"{protocol}://{hostname}:{port}"
else:
copy("appservice.address")
copy("appservice.tls_cert")
copy("appservice.tls_key")
copy("appservice.hostname")
copy("appservice.port")
copy("appservice.max_body_size")
copy("appservice.database")
if "appservice.debug" in self and "logging" not in self:
level = "DEBUG" if self["appservice.debug"] else "INFO"
base["logging.root.level"] = level
base["logging.loggers.mau.level"] = level
base["logging.loggers.telethon.level"] = level
copy("appservice.public.enabled")
copy("appservice.public.prefix")
@@ -76,16 +68,8 @@ class Config(BaseBridgeConfig):
if base["appservice.provisioning.shared_secret"] == "generate":
base["appservice.provisioning.shared_secret"] = self._new_token()
copy("appservice.id")
copy("appservice.bot_username")
copy("appservice.bot_displayname")
copy("appservice.bot_avatar")
copy("appservice.community_id")
copy("appservice.as_token")
copy("appservice.hs_token")
copy("metrics.enabled")
copy("metrics.listen_port")
@@ -99,6 +83,7 @@ class Config(BaseBridgeConfig):
copy("bridge.displayname_preference")
copy("bridge.displayname_max_length")
copy("bridge.allow_avatar_remove")
copy("bridge.max_initial_member_sync")
copy("bridge.sync_channel_members")
@@ -124,6 +109,8 @@ class Config(BaseBridgeConfig):
copy("bridge.encryption.allow")
copy("bridge.encryption.default")
copy("bridge.private_chat_portal_meta")
copy("bridge.delivery_receipts")
copy("bridge.delivery_error_reports")
copy("bridge.initial_power_level_overrides.group")
copy("bridge.initial_power_level_overrides.user")
@@ -208,14 +195,6 @@ class Config(BaseBridgeConfig):
copy("telegram.proxy.username")
copy("telegram.proxy.password")
if "appservice.debug" in self and "logging" not in self:
level = "DEBUG" if self["appservice.debug"] else "INFO"
base["logging.root.level"] = level
base["logging.loggers.mau.level"] = level
base["logging.loggers.telethon.level"] = level
else:
copy("logging")
def _get_permissions(self, key: str) -> Permissions:
level = self["bridge.permissions"].get(key, "")
admin = level == "admin"
+9
View File
@@ -121,6 +121,10 @@ bridge:
- phone number
# Maximum length of displayname
displayname_max_length: 100
# Remove avatars from Telegram ghost users when removed on Telegram. This is disabled by default
# as there's no way to determine whether an avatar is removed or just hidden from some users. If
# you're on a single-user instance, this should be safe to enable.
allow_avatar_remove: false
# Maximum number of members to sync per portal when starting up. Other members will be
# synced when they send messages. The maximum is 10000, after which the Telegram server
@@ -210,6 +214,11 @@ bridge:
# Whether or not to explicitly set the avatar and room name for private
# chat portal rooms. This will be implicitly enabled if encryption.default is true.
private_chat_portal_meta: false
# Whether or not the bridge should send a read receipt from the bridge bot when a message has
# been sent to Telegram.
delivery_receipts: false
# Whether or not delivery errors should be reported as messages in the Matrix room.
delivery_error_reports: false
# Overrides for base power levels.
initial_power_level_overrides:
+23 -12
View File
@@ -278,7 +278,7 @@ class MatrixHandler(BaseMatrixHandler):
if not portal:
return
await portal.handle_matrix_deletion(sender, evt.redacts)
await portal.handle_matrix_deletion(sender, evt.redacts, evt.event_id)
@staticmethod
async def handle_power_levels(evt: StateEvent) -> None:
@@ -286,11 +286,12 @@ class MatrixHandler(BaseMatrixHandler):
sender = await u.User.get_by_mxid(evt.sender).ensure_started()
if await sender.has_full_access(allow_bot=True) and portal:
await portal.handle_matrix_power_levels(sender, evt.content.users,
evt.unsigned.prev_content.users)
evt.unsigned.prev_content.users,
evt.event_id)
@staticmethod
async def handle_room_meta(evt_type: EventType, room_id: RoomID, sender_mxid: UserID,
content: RoomMetaStateEventContent) -> None:
content: RoomMetaStateEventContent, event_id: EventID) -> None:
portal = po.Portal.get_by_mxid(room_id)
sender = await u.User.get_by_mxid(sender_mxid).ensure_started()
if await sender.has_full_access(allow_bot=True) and portal:
@@ -301,27 +302,29 @@ class MatrixHandler(BaseMatrixHandler):
}[evt_type]
if not isinstance(content, content_type):
return
await handler(sender, content[content_key])
await handler(sender, content[content_key], event_id)
@staticmethod
async def handle_room_pin(room_id: RoomID, sender_mxid: UserID,
new_events: Set[str], old_events: Set[str]) -> None:
new_events: Set[str], old_events: Set[str],
event_id: EventID) -> None:
portal = po.Portal.get_by_mxid(room_id)
sender = await u.User.get_by_mxid(sender_mxid).ensure_started()
if await sender.has_full_access(allow_bot=True) and portal:
events = new_events - old_events
if len(events) > 0:
# New event pinned, set that as pinned in Telegram.
await portal.handle_matrix_pin(sender, EventID(events.pop()))
await portal.handle_matrix_pin(sender, EventID(events.pop()), event_id)
elif len(new_events) == 0:
# All pinned events removed, remove pinned event in Telegram.
await portal.handle_matrix_pin(sender, None)
await portal.handle_matrix_pin(sender, None, event_id)
@staticmethod
async def handle_room_upgrade(room_id: RoomID, sender: UserID, new_room_id: RoomID) -> None:
async def handle_room_upgrade(room_id: RoomID, sender: UserID, new_room_id: RoomID,
event_id: EventID) -> None:
portal = po.Portal.get_by_mxid(room_id)
if portal:
await portal.handle_matrix_upgrade(sender, new_room_id)
await portal.handle_matrix_upgrade(sender, new_room_id, event_id)
async def handle_member_info_change(self, room_id: RoomID, user_id: UserID,
profile: MemberStateEventContent,
@@ -389,6 +392,11 @@ class MatrixHandler(BaseMatrixHandler):
def filter_matrix_event(self, evt: Event) -> bool:
if not isinstance(evt, (RedactionEvent, MessageEvent, StateEvent, EncryptedEvent)):
return True
if evt.content.get("net.maunium.telegram.puppet", False):
puppet = pu.Puppet.get_by_custom_mxid(evt.sender)
if puppet:
self.log.debug("Ignoring puppet-sent event %s", evt.event_id)
return True
return evt.sender and (evt.sender == self.az.bot_mxid
or pu.Puppet.get_id_from_mxid(evt.sender) is not None)
@@ -409,16 +417,19 @@ class MatrixHandler(BaseMatrixHandler):
if evt.type == EventType.ROOM_POWER_LEVELS:
await self.handle_power_levels(evt)
elif evt.type in (EventType.ROOM_NAME, EventType.ROOM_AVATAR, EventType.ROOM_TOPIC):
await self.handle_room_meta(evt.type, evt.room_id, evt.sender, evt.content)
await self.handle_room_meta(evt.type, evt.room_id, evt.sender, evt.content,
evt.event_id)
elif evt.type == EventType.ROOM_PINNED_EVENTS:
new_events = set(evt.content.pinned)
try:
old_events = set(evt.unsigned.prev_content.pinned)
except (KeyError, ValueError, TypeError, AttributeError):
old_events = set()
await self.handle_room_pin(evt.room_id, evt.sender, new_events, old_events)
await self.handle_room_pin(evt.room_id, evt.sender, new_events, old_events,
evt.event_id)
elif evt.type == EventType.ROOM_TOMBSTONE:
await self.handle_room_upgrade(evt.room_id, evt.sender, evt.content.replacement_room)
await self.handle_room_upgrade(evt.room_id, evt.sender, evt.content.replacement_room,
evt.event_id)
elif evt.type == EventType.ROOM_ENCRYPTION:
portal = po.Portal.get_by_mxid(evt.room_id)
if portal:
+15 -4
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2020 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
@@ -30,7 +30,8 @@ from telethon.tl.types import (Channel, ChannelFull, Chat, ChatFull, ChatInviteE
from mautrix.errors import MatrixRequestError, IntentError
from mautrix.appservice import AppService, IntentAPI
from mautrix.types import RoomID, RoomAlias, UserID, EventType, PowerLevelStateEventContent
from mautrix.types import (RoomID, RoomAlias, UserID, EventID, EventType, MessageEventContent,
PowerLevelStateEventContent)
from mautrix.util.simple_template import SimpleTemplate
from mautrix.util.logging import TraceLogger
@@ -135,7 +136,7 @@ class BasePortal(ABC):
if mxid:
self.by_mxid[mxid] = self
# region Propegrties
# region Properties
@property
def tgid_full(self) -> Tuple[TelegramID, TelegramID]:
@@ -460,6 +461,15 @@ class BasePortal(ABC):
type_name if create else None)
# endregion
async def _send_message(self, intent: IntentAPI, content: MessageEventContent,
event_type: EventType = EventType.ROOM_MESSAGE, **kwargs) -> EventID:
if self.encrypted and self.matrix.e2ee:
if intent.api.is_real_user:
content[intent.api.real_user_content_key] = True
event_type, content = await self.matrix.e2ee.encrypt(self.mxid, event_type, content)
return await intent.send_message_event(self.mxid, event_type, content, **kwargs)
# region Abstract methods (cross-called in matrix/metadata/telegram classes)
@abstractmethod
@@ -501,7 +511,8 @@ class BasePortal(ABC):
@abstractmethod
def handle_matrix_power_levels(self, sender: 'u.User', new_levels: Dict[UserID, int],
old_levels: Dict[UserID, int]) -> Awaitable[None]:
old_levels: Dict[UserID, int], event_id: Optional[EventID]
) -> Awaitable[None]:
pass
@abstractmethod
+46 -15
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2020 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
@@ -25,7 +25,8 @@ from telethon.tl.functions.messages import (EditChatPhotoRequest, EditChatTitleR
EditChatAboutRequest)
from telethon.tl.functions.channels import EditPhotoRequest, EditTitleRequest, JoinChannelRequest
from telethon.errors import (ChatNotModifiedError, PhotoExtInvalidError,
PhotoInvalidDimensionsError, PhotoSaveFileInvalidError)
PhotoInvalidDimensionsError, PhotoSaveFileInvalidError,
RPCError)
from telethon.tl.patched import Message, MessageService
from telethon.tl.types import (
DocumentAttributeFilename, DocumentAttributeImageSize, GeoPoint,
@@ -228,6 +229,13 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
message, entities = None, None
return message, entities
async def _send_delivery_receipt(self, event_id: EventID) -> None:
if event_id and config["bridge.delivery_receipts"]:
try:
await self.az.intent.mark_read(self.mxid, event_id)
except Exception:
self.log.exception("Failed to send delivery receipt for %s", event_id)
async def _handle_matrix_text(self, sender_id: TelegramID, event_id: EventID,
space: TelegramID, client: 'MautrixTelegramClient',
content: TextMessageEventContent, reply_to: TelegramID) -> None:
@@ -245,6 +253,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
parse_mode=self._matrix_event_to_entities,
link_preview=lp)
self._add_telegram_message_to_db(event_id, space, 0, response)
await self._send_delivery_receipt(event_id)
async def _handle_matrix_file(self, sender_id: TelegramID, event_id: EventID,
space: TelegramID, client: 'MautrixTelegramClient',
@@ -307,6 +316,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
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)
await self._send_delivery_receipt(event_id)
async def _matrix_document_edit(self, client: 'MautrixTelegramClient',
content: MessageEventContent, space: TelegramID,
@@ -317,6 +327,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
response = await client.edit_message(self.peer, orig_msg.tgid,
caption, file=media)
self._add_telegram_message_to_db(event_id, space, -1, response)
await self._send_delivery_receipt(event_id)
return True
return False
@@ -339,6 +350,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
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)
await self._send_delivery_receipt(event_id)
def _add_telegram_message_to_db(self, event_id: EventID, space: TelegramID,
edit_index: int, response: TypeMessage) -> None:
@@ -354,17 +366,26 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
mxid=event_id,
edit_index=edit_index).insert()
async def _send_bridge_error(self, msg: str) -> None:
if config["bridge.delivery_error_reports"]:
await self._send_message(self.main_intent,
TextMessageEventContent(msgtype=MessageType.NOTICE, body=msg))
async def handle_matrix_message(self, sender: 'u.User', content: MessageEventContent,
event_id: EventID) -> None:
try:
await self._handle_matrix_message(sender, content, event_id)
except RPCError as e:
if config["bridge.delivery_error_reports"]:
await self._send_bridge_error(f"\u26a0 Your message may not have been bridged: {e}")
raise
async def _handle_matrix_message(self, sender: 'u.User', content: MessageEventContent,
event_id: EventID) -> None:
if not content.body or not content.msgtype:
self.log.debug(f"Ignoring message {event_id} in {self.mxid} without body or msgtype")
return
puppet = p.Puppet.get_by_custom_mxid(sender.mxid)
if puppet and content.get("net.maunium.telegram.puppet", False):
self.log.debug("Ignoring puppet-sent message by confirmed puppet user %s", sender.mxid)
return
logged_in = not await sender.needs_relaybot(self)
client = sender.client if logged_in else self.bot.client
sender_id = sender.tgid if logged_in else self.bot.tgid
@@ -405,8 +426,8 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
else:
self.log.trace("Unhandled Matrix event: %s", content)
async def handle_matrix_pin(self, sender: 'u.User',
pinned_message: Optional[EventID]) -> None:
async def handle_matrix_pin(self, sender: 'u.User', pinned_message: Optional[EventID],
pin_event_id: EventID) -> None:
if self.peer_type != "chat" and self.peer_type != "channel":
return
try:
@@ -419,10 +440,12 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
self.log.warning(f"Could not find pinned {pinned_message} in {self.mxid}")
return
await sender.client(UpdatePinnedMessageRequest(peer=self.peer, id=message.tgid))
await self._send_delivery_receipt(pin_event_id)
except ChatNotModifiedError:
pass
async def handle_matrix_deletion(self, deleter: 'u.User', event_id: EventID) -> None:
async def handle_matrix_deletion(self, deleter: 'u.User', event_id: EventID,
redaction_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)
@@ -430,6 +453,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
return
if message.edit_index == 0:
await real_deleter.client.delete_messages(self.peer, [message.tgid])
await self._send_delivery_receipt(redaction_event_id)
else:
self.log.debug(f"Ignoring deletion of edit event {message.mxid} in {message.mx_room}")
@@ -444,7 +468,8 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
pin_messages=moderator, add_admins=admin)
async def handle_matrix_power_levels(self, sender: 'u.User', new_users: Dict[UserID, int],
old_users: Dict[UserID, int]) -> None:
old_users: Dict[UserID, int], event_id: Optional[EventID]
) -> None:
# TODO handle all power level changes and bridge exact admin rights to supergroups/channels
for user, level in new_users.items():
if not user or user == self.main_intent.mxid or user == sender.mxid:
@@ -460,15 +485,16 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
if user not in old_users or level != old_users[user]:
await self._update_telegram_power_level(sender, user_id, level)
async def handle_matrix_about(self, sender: 'u.User', about: str) -> None:
async def handle_matrix_about(self, sender: 'u.User', about: str, event_id: EventID) -> None:
if self.peer_type not in ("chat", "channel"):
return
peer = await self.get_input_entity(sender)
await sender.client(EditChatAboutRequest(peer=peer, about=about))
self.about = about
self.save()
await self._send_delivery_receipt(event_id)
async def handle_matrix_title(self, sender: 'u.User', title: str) -> None:
async def handle_matrix_title(self, sender: 'u.User', title: str, event_id: EventID) -> None:
if self.peer_type not in ("chat", "channel"):
return
@@ -480,8 +506,10 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
self.dedup.register_outgoing_actions(response)
self.title = title
self.save()
await self._send_delivery_receipt(event_id)
async def handle_matrix_avatar(self, sender: 'u.User', url: ContentURI) -> None:
async def handle_matrix_avatar(self, sender: 'u.User', url: ContentURI, event_id: EventID
) -> None:
if self.peer_type not in ("chat", "channel"):
# Invalid peer type
return
@@ -507,8 +535,10 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
self.photo_id = f"{size.location.volume_id}-{size.location.local_id}"
self.save()
break
await self._send_delivery_receipt(event_id)
async def handle_matrix_upgrade(self, sender: UserID, new_room: RoomID) -> None:
async def handle_matrix_upgrade(self, sender: UserID, new_room: RoomID, event_id: EventID
) -> None:
_, server = self.main_intent.parse_user_id(sender)
old_room = self.mxid
self.migrate_and_save_matrix(new_room)
@@ -535,6 +565,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC):
return
await self.update_matrix_room(user, entity, direct=self.peer_type == "user")
self.log.info(f"{sender} upgraded room from {old_room} to {self.mxid}")
await self._send_delivery_receipt(event_id)
def migrate_and_save_matrix(self, new_id: RoomID) -> None:
try:
+23 -17
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2020 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
@@ -30,8 +30,9 @@ from telethon.tl.types import (
from mautrix.errors import MForbidden
from mautrix.types import (RoomID, UserID, RoomCreatePreset, EventType, Membership, Member,
PowerLevelStateEventContent)
from mautrix.appservice import IntentAPI
PowerLevelStateEventContent, RoomTopicStateEventContent,
RoomNameStateEventContent, RoomAvatarStateEventContent,
StateEventContent)
from ..types import TelegramID
from ..context import Context
@@ -155,7 +156,7 @@ class PortalMetadata(BasePortal, ABC):
if levels.get_user_level(self.main_intent.mxid) == 100:
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, {})
await self.handle_matrix_power_levels(source, levels.users, {}, None)
async def invite_telegram(self, source: 'u.User',
puppet: Union[p.Puppet, 'AbstractUser']) -> None:
@@ -638,15 +639,18 @@ class PortalMetadata(BasePortal, ABC):
self.save()
return True
async def _try_use_intent(self, sender: Optional['p.Puppet'],
action: Callable[[IntentAPI], Awaitable[None]]) -> None:
async def _try_set_state(self, sender: Optional['p.Puppet'], evt_type: EventType,
content: StateEventContent) -> None:
if sender:
try:
await action(sender.intent_for(self))
intent = sender.intent_for(self)
if sender.is_real_user:
content[self.az.real_user_content_key] = True
await intent.send_state_event(self.mxid, evt_type, content)
except MForbidden:
await action(self.main_intent)
await self.main_intent.send_state_event(self.mxid, evt_type, content)
else:
await action(self.main_intent)
await self.main_intent.send_state_event(self.mxid, evt_type, content)
async def _update_about(self, about: str, sender: Optional['p.Puppet'] = None,
save: bool = False) -> bool:
@@ -654,8 +658,8 @@ class PortalMetadata(BasePortal, ABC):
return False
self.about = about
await self._try_use_intent(sender,
lambda intent: intent.set_room_topic(self.mxid, self.about))
await self._try_set_state(sender, EventType.ROOM_TOPIC,
RoomTopicStateEventContent(topic=self.about))
if save:
self.save()
return True
@@ -666,8 +670,8 @@ class PortalMetadata(BasePortal, ABC):
return False
self.title = title
await self._try_use_intent(sender,
lambda intent: intent.set_room_name(self.mxid, self.title))
await self._try_set_state(sender, EventType.ROOM_NAME,
RoomNameStateEventContent(name=self.title))
if save:
self.save()
return True
@@ -691,18 +695,20 @@ class PortalMetadata(BasePortal, ABC):
loc = None
else:
raise ValueError(f"Unknown photo type {type(photo)}")
if self.peer_type == "user" and not photo_id and not config["bridge.allow_avatar_remove"]:
return False
if self.photo_id != photo_id:
if not photo_id:
await self._try_use_intent(sender,
lambda intent: intent.set_room_avatar(self.mxid, None))
await self._try_set_state(sender, EventType.ROOM_AVATAR,
RoomAvatarStateEventContent(url=None))
self.photo_id = ""
if save:
self.save()
return True
file = await util.transfer_file_to_matrix(user.client, self.main_intent, loc)
if file:
await self._try_use_intent(sender, lambda intent: intent.set_room_avatar(self.mxid,
file.mxc))
await self._try_set_state(sender, EventType.ROOM_AVATAR,
RoomAvatarStateEventContent(url=file.mxc))
self.photo_id = photo_id
if save:
self.save()
+1 -9
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2020 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
@@ -71,14 +71,6 @@ class PortalTelegram(BasePortal, ABC):
return f"https://t.me/c/{self.tgid}/{evt.id}"
return None
async def _send_message(self, intent: IntentAPI, content: MessageEventContent,
event_type: EventType = EventType.ROOM_MESSAGE, **kwargs) -> EventID:
if self.encrypted and self.matrix.e2ee:
if intent.api.is_real_user:
content[intent.api.real_user_content_key] = True
event_type, content = await self.matrix.e2ee.encrypt(self.mxid, event_type, content)
return await intent.send_message_event(self.mxid, event_type, content, **kwargs)
async def handle_telegram_photo(self, source: 'AbstractUser', intent: IntentAPI, evt: Message,
relates_to: RelatesTo = None) -> Optional[EventID]:
loc, largest_size = self._get_largest_photo_size(evt.media.photo)
+3 -1
View File
@@ -1,5 +1,5 @@
# mautrix-telegram - A Matrix-Telegram puppeting bridge
# Copyright (C) 2019 Tulir Asokan
# Copyright (C) 2020 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
@@ -300,6 +300,8 @@ class Puppet(CustomPuppetMixin):
else:
self.log.warning(f"Unknown user profile photo type: {type(photo)}")
return False
if not photo_id and not config["bridge.allow_avatar_remove"]:
return False
if self.photo_id != photo_id:
if not photo_id:
self.photo_id = ""
+2 -2
View File
@@ -14,10 +14,10 @@ pillow>=4.3,<8
moviepy>=1,<2
#/metrics
prometheus_client>=0.6,<0.8
prometheus_client>=0.6,<0.9
#/postgres
psycopg2-binary>=2,<3
#/e2be
matrix-nio[e2e]>=0.9,<0.11
matrix-nio[e2e]>=0.9,<0.13
+2 -2
View File
@@ -4,6 +4,6 @@ ruamel.yaml>=0.15.35,<0.17
python-magic>=0.4,<0.5
commonmark>=0.8,<0.10
aiohttp>=3,<4
mautrix==0.5.0.beta16
telethon>=1.13,<1.14
mautrix>=0.5.2,<0.6
telethon>=1.13,<1.15
telethon-session-sqlalchemy>=0.2.14,<0.3