Add initial Matrix end-to-bridge encryption support
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"""Add encrypted field for portals
|
||||
|
||||
Revision ID: 24f31fc8a72b
|
||||
Revises: a7c04a56041b
|
||||
Create Date: 2020-03-28 20:14:29.046699
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "24f31fc8a72b"
|
||||
down_revision = "a7c04a56041b"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table("portal") as batch_op:
|
||||
batch_op.add_column(sa.Column("encrypted", sa.Boolean(), nullable=False,
|
||||
server_default=sa.sql.expression.false()))
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table("portal") as batch_op:
|
||||
batch_op.drop_column("encrypted")
|
||||
@@ -118,6 +118,8 @@ class Config(BaseBridgeConfig):
|
||||
copy("bridge.federate_rooms")
|
||||
copy("bridge.animated_sticker.target")
|
||||
copy("bridge.animated_sticker.args")
|
||||
copy("bridge.encryption.allow")
|
||||
copy("bridge.encryption.default")
|
||||
|
||||
copy("bridge.initial_power_level_overrides.group")
|
||||
copy("bridge.initial_power_level_overrides.user")
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Text, func
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Text, func, sql
|
||||
|
||||
from mautrix.types import RoomID
|
||||
from mautrix.util.db import Base
|
||||
@@ -34,6 +34,7 @@ class Portal(Base):
|
||||
|
||||
# Matrix portal information
|
||||
mxid: RoomID = Column(String, unique=True, nullable=True)
|
||||
encrypted: bool = Column(Boolean, nullable=False, server_default=sql.expression.false())
|
||||
|
||||
config: str = Column(Text, nullable=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# mautrix-telegram - A Matrix-Telegram puppeting bridge
|
||||
# Copyright (C) 2019 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from typing import Tuple, Union
|
||||
import logging
|
||||
import asyncio
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from nio import AsyncClient, Event as NioEvent, GroupEncryptionError, LoginError
|
||||
|
||||
from mautrix.appservice import AppService
|
||||
from mautrix.types import (Filter, RoomFilter, EventFilter, RoomEventFilter, StateFilter,
|
||||
EventType, RoomID, Serializable, JSON, MessageEvent, Event)
|
||||
|
||||
from .context import Context
|
||||
|
||||
|
||||
class EncryptionManager:
|
||||
loop: asyncio.AbstractEventLoop
|
||||
log: logging.Logger = logging.getLogger("mau.e2ee")
|
||||
client: AsyncClient
|
||||
az: AppService
|
||||
|
||||
login_shared_secret: bytes
|
||||
|
||||
sync_task: asyncio.Task
|
||||
|
||||
def __init__(self, context: 'Context') -> None:
|
||||
self.loop = context.loop
|
||||
self.az = context.az
|
||||
self.config = context.config
|
||||
lss: str = self.config["bridge.login_shared_secret"]
|
||||
if not lss:
|
||||
raise ValueError("login_shared_secret must be set to enable encryption")
|
||||
self.login_shared_secret = lss.encode("utf-8")
|
||||
self.client = AsyncClient(homeserver=self.config["homeserver.address"],
|
||||
user=self.az.bot_mxid, device_id="Telegram bridge",
|
||||
store_path="nio_store")
|
||||
|
||||
async def encrypt(self, room_id: RoomID, event_type: EventType,
|
||||
content: Union[Serializable, JSON]) -> Tuple[EventType, JSON]:
|
||||
serialized = content.serialize() if isinstance(content, Serializable) else content
|
||||
type_str = str(event_type)
|
||||
retries = 0
|
||||
while True:
|
||||
try:
|
||||
type_str, encrypted = self.client.encrypt(room_id, type_str, serialized)
|
||||
break
|
||||
except GroupEncryptionError:
|
||||
if retries > 3:
|
||||
self.log.error("Got GroupEncryptionError again, giving up")
|
||||
raise
|
||||
retries += 1
|
||||
self.log.debug("Got GroupEncryptionError, sharing group session and trying again")
|
||||
await self.client.share_group_session(room_id, ignore_unverified_devices=True)
|
||||
event_type = EventType.find(type_str)
|
||||
try:
|
||||
encrypted["m.relates_to"] = serialized["m.relates_to"]
|
||||
except KeyError:
|
||||
pass
|
||||
return event_type, encrypted
|
||||
|
||||
def decrypt(self, event: MessageEvent) -> MessageEvent:
|
||||
serialized = event.serialize()
|
||||
event = self.client.decrypt_event(NioEvent.parse_encrypted_event(serialized))
|
||||
try:
|
||||
event.source["content"]["m.relates_to"] = serialized["content"]["m.relates_to"]
|
||||
except KeyError:
|
||||
pass
|
||||
return Event.deserialize(event.source)
|
||||
|
||||
async def start(self) -> None:
|
||||
self.log.debug("Logging in with bridge bot user")
|
||||
password = hmac.new(self.login_shared_secret, self.az.bot_mxid.encode("utf-8"),
|
||||
hashlib.sha512).hexdigest()
|
||||
resp = await self.client.login(password, device_name="Telegram bridge")
|
||||
if isinstance(resp, LoginError):
|
||||
raise resp
|
||||
self.sync_task = self.loop.create_task(self.client.sync_forever(
|
||||
timeout=30000, sync_filter=self._filter.serialize()))
|
||||
self.log.info("End-to-bridge encryption support is enabled")
|
||||
|
||||
def stop(self) -> None:
|
||||
self.sync_task.cancel()
|
||||
|
||||
@property
|
||||
def _filter(self) -> Filter:
|
||||
all_events = EventType.find("*")
|
||||
return Filter(
|
||||
account_data=EventFilter(types=[all_events]),
|
||||
presence=EventFilter(not_types=[all_events]),
|
||||
room=RoomFilter(
|
||||
include_leave=False,
|
||||
state=StateFilter(types=[EventType.ROOM_MEMBER, EventType.ROOM_ENCRYPTION]),
|
||||
timeline=RoomEventFilter(types=[EventType.ROOM_MEMBER, EventType.ROOM_ENCRYPTION]),
|
||||
account_data=RoomEventFilter(not_types=[all_events]),
|
||||
ephemeral=RoomEventFilter(not_types=[all_events]),
|
||||
),
|
||||
)
|
||||
@@ -191,6 +191,17 @@ bridge:
|
||||
height: 256
|
||||
background: "020202" # only for gif
|
||||
fps: 30 # only for webm
|
||||
# End-to-bridge encryption support options. These require matrix-nio to be installed with pip
|
||||
# and login_shared_secret to be configured in order to get a device for the bridge bot.
|
||||
#
|
||||
# Additionally, https://github.com/matrix-org/synapse/pull/5758 is required if using a normal
|
||||
# application service.
|
||||
encryption:
|
||||
# Allow encryption, work in group chat rooms with e2ee enabled
|
||||
allow: false
|
||||
# Default to encryption, force-enable encryption in all portals the bridge creates
|
||||
# This will cause the bridge bot to be in private chats for the encryption to work properly.
|
||||
default: false
|
||||
|
||||
# Overrides for base power levels.
|
||||
initial_power_level_overrides:
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from typing import Dict, Set, Tuple, Union, Iterable, TYPE_CHECKING
|
||||
from typing import Dict, Set, Tuple, Union, Iterable, Optional, TYPE_CHECKING
|
||||
|
||||
from mautrix.bridge import BaseMatrixHandler
|
||||
from mautrix.types import (Event, EventType, RoomID, UserID, EventID, ReceiptEvent, ReceiptType,
|
||||
ReceiptEventContent, PresenceEvent, PresenceState, TypingEvent,
|
||||
MessageEvent, StateEvent, RedactionEvent, RoomNameStateEventContent,
|
||||
RoomAvatarStateEventContent, RoomTopicStateEventContent,
|
||||
MemberStateEventContent)
|
||||
MemberStateEventContent, EncryptedEvent)
|
||||
from mautrix.errors import MatrixError
|
||||
|
||||
from . import user as u, portal as po, puppet as pu, commands as com
|
||||
@@ -37,6 +37,11 @@ except ImportError:
|
||||
Histogram = None
|
||||
EVENT_TIME = None
|
||||
|
||||
try:
|
||||
from .e2ee import EncryptionManager
|
||||
except ImportError:
|
||||
EncryptionManager = None
|
||||
|
||||
RoomMetaStateEventContent = Union[RoomNameStateEventContent, RoomAvatarStateEventContent,
|
||||
RoomTopicStateEventContent]
|
||||
|
||||
@@ -44,14 +49,26 @@ RoomMetaStateEventContent = Union[RoomNameStateEventContent, RoomAvatarStateEven
|
||||
class MatrixHandler(BaseMatrixHandler):
|
||||
bot: 'Bot'
|
||||
commands: 'com.CommandProcessor'
|
||||
e2ee: Optional[EncryptionManager]
|
||||
previously_typing: Dict[RoomID, Set[UserID]]
|
||||
|
||||
def __init__(self, context: 'Context') -> None:
|
||||
super(MatrixHandler, self).__init__(context.az, context.config, loop=context.loop,
|
||||
command_processor=com.CommandProcessor(context))
|
||||
self.e2ee = None
|
||||
if self.config["bridge.encryption.allow"]:
|
||||
if EncryptionManager:
|
||||
self.e2ee = EncryptionManager(context)
|
||||
else:
|
||||
self.log.warning("Encryption enabled in config, but dependencies not installed.")
|
||||
self.bot = context.bot
|
||||
self.previously_typing = {}
|
||||
|
||||
async def init_as_bot(self) -> None:
|
||||
await super().init_as_bot()
|
||||
if self.e2ee:
|
||||
await self.e2ee.start()
|
||||
|
||||
async def get_user(self, user_id: UserID) -> 'u.User':
|
||||
return await u.User.get_by_mxid(user_id).ensure_started()
|
||||
|
||||
@@ -355,7 +372,7 @@ class MatrixHandler(BaseMatrixHandler):
|
||||
self.previously_typing[room_id] = now_typing
|
||||
|
||||
def filter_matrix_event(self, evt: Event) -> bool:
|
||||
if not isinstance(evt, (RedactionEvent, MessageEvent, StateEvent)):
|
||||
if not isinstance(evt, (RedactionEvent, MessageEvent, StateEvent, EncryptedEvent)):
|
||||
return True
|
||||
return evt.sender and (evt.sender == self.az.bot_mxid
|
||||
or pu.Puppet.get_id_from_mxid(evt.sender) is not None)
|
||||
@@ -372,6 +389,8 @@ class MatrixHandler(BaseMatrixHandler):
|
||||
async def handle_event(self, evt: Event) -> None:
|
||||
if evt.type == EventType.ROOM_REDACTION:
|
||||
await self.handle_redaction(evt)
|
||||
elif evt.type == EventType.ROOM_ENCRYPTED and self.e2ee:
|
||||
await self.int_handle_event(self.e2ee.decrypt(evt))
|
||||
|
||||
async def handle_state_event(self, evt: StateEvent) -> None:
|
||||
if evt.type == EventType.ROOM_POWER_LEVELS:
|
||||
@@ -387,6 +406,11 @@ class MatrixHandler(BaseMatrixHandler):
|
||||
await self.handle_room_pin(evt.room_id, evt.sender, new_events, old_events)
|
||||
elif evt.type == EventType.ROOM_TOMBSTONE:
|
||||
await self.handle_room_upgrade(evt.room_id, evt.sender, evt.content.replacement_room)
|
||||
elif evt.type == EventType.ROOM_ENCRYPTION:
|
||||
portal = po.Portal.get_by_mxid(evt.room_id)
|
||||
if portal:
|
||||
portal.encrypted = True
|
||||
portal.save()
|
||||
|
||||
async def log_event_handle_duration(self, evt: Event, duration: float) -> None:
|
||||
if EVENT_TIME:
|
||||
|
||||
@@ -44,6 +44,7 @@ if TYPE_CHECKING:
|
||||
from ..bot import Bot
|
||||
from ..abstract_user import AbstractUser
|
||||
from ..config import Config
|
||||
from ..matrix import MatrixHandler
|
||||
from . import Portal
|
||||
|
||||
TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant]
|
||||
@@ -58,6 +59,7 @@ class BasePortal(ABC):
|
||||
az: AppService = None
|
||||
bot: 'Bot' = None
|
||||
loop: asyncio.AbstractEventLoop = None
|
||||
matrix: 'MatrixHandler' = None
|
||||
|
||||
# Config cache
|
||||
filter_mode: str = None
|
||||
@@ -85,6 +87,7 @@ class BasePortal(ABC):
|
||||
about: Optional[str]
|
||||
photo_id: Optional[str]
|
||||
local_config: Dict[str, Any]
|
||||
encrypted: bool
|
||||
deleted: bool
|
||||
backfilling: bool
|
||||
backfill_leave: Optional[Set[IntentAPI]]
|
||||
@@ -102,7 +105,8 @@ class BasePortal(ABC):
|
||||
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:
|
||||
local_config: Optional[str] = None, encrypted: Optional[bool] = False,
|
||||
db_instance: DBPortal = None) -> None:
|
||||
self.mxid = mxid
|
||||
self.tgid = tgid
|
||||
self.tg_receiver = tg_receiver or tgid
|
||||
@@ -113,6 +117,7 @@ class BasePortal(ABC):
|
||||
self.about = about
|
||||
self.photo_id = photo_id
|
||||
self.local_config = json.loads(local_config or "{}")
|
||||
self.encrypted = encrypted
|
||||
self._db_instance = db_instance
|
||||
self._main_intent = None
|
||||
self.deleted = False
|
||||
@@ -328,12 +333,12 @@ class BasePortal(ABC):
|
||||
return DBPortal(tgid=self.tgid, tg_receiver=self.tg_receiver, peer_type=self.peer_type,
|
||||
mxid=self.mxid, username=self.username, megagroup=self.megagroup,
|
||||
title=self.title, about=self.about, photo_id=self.photo_id,
|
||||
config=json.dumps(self.local_config))
|
||||
config=json.dumps(self.local_config), encrypted=self.encrypted)
|
||||
|
||||
def save(self) -> None:
|
||||
self.db_instance.edit(mxid=self.mxid, username=self.username, title=self.title,
|
||||
about=self.about, photo_id=self.photo_id, megagroup=self.megagroup,
|
||||
config=json.dumps(self.local_config))
|
||||
config=json.dumps(self.local_config), encrypted=self.encrypted)
|
||||
|
||||
def delete(self) -> None:
|
||||
try:
|
||||
@@ -352,10 +357,10 @@ class BasePortal(ABC):
|
||||
@classmethod
|
||||
def from_db(cls, db_portal: DBPortal) -> 'Portal':
|
||||
return cls(tgid=db_portal.tgid, tg_receiver=db_portal.tg_receiver,
|
||||
peer_type=db_portal.peer_type, mxid=db_portal.mxid,
|
||||
username=db_portal.username, megagroup=db_portal.megagroup,
|
||||
title=db_portal.title, about=db_portal.about, photo_id=db_portal.photo_id,
|
||||
local_config=db_portal.config, db_instance=db_portal)
|
||||
peer_type=db_portal.peer_type, mxid=db_portal.mxid, username=db_portal.username,
|
||||
megagroup=db_portal.megagroup, title=db_portal.title, about=db_portal.about,
|
||||
photo_id=db_portal.photo_id, local_config=db_portal.config,
|
||||
encrypted=db_portal.encrypted, db_instance=db_portal)
|
||||
|
||||
# endregion
|
||||
# region Class instance lookup
|
||||
@@ -506,6 +511,7 @@ class BasePortal(ABC):
|
||||
def init(context: Context) -> None:
|
||||
global config
|
||||
BasePortal.az, config, BasePortal.loop, BasePortal.bot = context.core
|
||||
BasePortal.matrix = context.mx
|
||||
BasePortal.max_initial_member_sync = config["bridge.max_initial_member_sync"]
|
||||
BasePortal.sync_channel_members = config["bridge.sync_channel_members"]
|
||||
BasePortal.sync_matrix_state = config["bridge.sync_matrix_state"]
|
||||
|
||||
@@ -362,7 +362,7 @@ class PortalMetadata(BasePortal, ABC):
|
||||
levels.kick = overrides.get("kick", 50)
|
||||
levels.redact = overrides.get("redact", 50)
|
||||
levels.invite = overrides.get("invite", 50 if dbr.invite_users else 0)
|
||||
levels.events[EventType.ROOM_ENCRYPTED] = 99
|
||||
levels.events[EventType.ROOM_ENCRYPTION] = 99
|
||||
levels.events[EventType.ROOM_TOMBSTONE] = 99
|
||||
levels.events[EventType.ROOM_NAME] = 50 if dbr.change_info else 0
|
||||
levels.events[EventType.ROOM_AVATAR] = 50 if dbr.change_info else 0
|
||||
|
||||
@@ -38,7 +38,7 @@ from telethon.tl.types import (
|
||||
from mautrix.appservice import IntentAPI
|
||||
from mautrix.types import (EventID, UserID, ImageInfo, ThumbnailInfo, RelatesTo, MessageType,
|
||||
EventType, MediaMessageEventContent, TextMessageEventContent,
|
||||
LocationMessageEventContent, Format)
|
||||
LocationMessageEventContent, Format, MessageEventContent)
|
||||
|
||||
from ..types import TelegramID
|
||||
from ..db import Message as DBMessage, TelegramFile as DBTelegramFile
|
||||
@@ -71,6 +71,12 @@ 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:
|
||||
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: Dict = None) -> Optional[EventID]:
|
||||
loc, largest_size = self._get_largest_photo_size(evt.media.photo)
|
||||
@@ -85,7 +91,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
prefix_text="Inline image: ")
|
||||
content.external_url = self._get_external_url(evt)
|
||||
await intent.set_typing(self.mxid, is_typing=False)
|
||||
return await intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
return await self._send_message(intent, content, timestamp=evt.date)
|
||||
info = ImageInfo(
|
||||
height=largest_size.h, width=largest_size.w, orientation=0, mimetype=file.mime_type,
|
||||
size=(len(largest_size.bytes) if (isinstance(largest_size, PhotoCachedSize))
|
||||
@@ -95,12 +101,12 @@ class PortalTelegram(BasePortal, ABC):
|
||||
content = MediaMessageEventContent(url=file.mxc, msgtype=MessageType.IMAGE, info=info,
|
||||
body=name, relates_to=relates_to,
|
||||
external_url=self._get_external_url(evt))
|
||||
result = await intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
result = await self._send_message(intent, content, timestamp=evt.date)
|
||||
if evt.message:
|
||||
caption_content = await formatter.telegram_to_matrix(evt, source, self.main_intent,
|
||||
no_reply_fallback=True)
|
||||
caption_content.external_url = content.external_url
|
||||
result = await intent.send_message(self.mxid, caption_content, timestamp=evt.date)
|
||||
result = await self._send_message(intent, caption_content, timestamp=evt.date)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@@ -168,6 +174,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
if document.size > config["bridge.max_document_size"] * 1000 ** 2:
|
||||
name = attrs.name or ""
|
||||
caption = f"\n{evt.message}" if evt.message else ""
|
||||
# TODO encrypt
|
||||
return await intent.send_notice(self.mxid, f"Too large file {name}{caption}")
|
||||
|
||||
thumb_loc, thumb_size = self._get_largest_photo_size(document)
|
||||
@@ -199,7 +206,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
"audio/": MessageType.AUDIO,
|
||||
"image/": MessageType.IMAGE,
|
||||
}.get(info.mimetype[:6], MessageType.FILE))
|
||||
return await intent.send_message_event(self.mxid, event_type, content, timestamp=evt.date)
|
||||
return await self._send_message(intent, content, event_type=event_type, timestamp=evt.date)
|
||||
|
||||
def handle_telegram_location(self, _: 'AbstractUser', intent: IntentAPI, evt: Message,
|
||||
relates_to: dict = None) -> Awaitable[EventID]:
|
||||
@@ -218,7 +225,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
content["format"] = str(Format.HTML)
|
||||
content["formatted_body"] = f"Location: <a href='{url}'>{body}</a>"
|
||||
|
||||
return intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
return self._send_message(intent, content, timestamp=evt.date)
|
||||
|
||||
async def handle_telegram_text(self, source: 'AbstractUser', intent: IntentAPI, is_bot: bool,
|
||||
evt: Message) -> EventID:
|
||||
@@ -228,7 +235,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
if is_bot and self.get_config("bot_messages_as_notices"):
|
||||
content.msgtype = MessageType.NOTICE
|
||||
await intent.set_typing(self.mxid, is_typing=False)
|
||||
return await intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
return await self._send_message(intent, content, timestamp=evt.date)
|
||||
|
||||
async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI,
|
||||
evt: Message, relates_to: dict = None) -> EventID:
|
||||
@@ -241,7 +248,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
content.external_url = self._get_external_url(evt)
|
||||
content["net.maunium.telegram.unsupported"] = True
|
||||
await intent.set_typing(self.mxid, is_typing=False)
|
||||
return await intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
return await self._send_message(intent, content, timestamp=evt.date)
|
||||
|
||||
async def handle_telegram_poll(self, source: 'AbstractUser', intent: IntentAPI, evt: Message,
|
||||
relates_to: RelatesTo) -> EventID:
|
||||
@@ -267,7 +274,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
relates_to=relates_to, external_url=self._get_external_url(evt))
|
||||
|
||||
await intent.set_typing(self.mxid, is_typing=False)
|
||||
return await intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
return await self._send_message(intent, content, timestamp=evt.date)
|
||||
|
||||
@staticmethod
|
||||
def _int_to_bytes(i: int) -> bytes:
|
||||
@@ -309,7 +316,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
content["net.maunium.telegram.game"] = play_id
|
||||
|
||||
await intent.set_typing(self.mxid, is_typing=False)
|
||||
return await intent.send_message(self.mxid, content, timestamp=evt.date)
|
||||
return await self._send_message(intent, content, timestamp=evt.date)
|
||||
|
||||
async def handle_telegram_edit(self, source: 'AbstractUser', sender: p.Puppet, evt: Message
|
||||
) -> None:
|
||||
@@ -353,7 +360,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
|
||||
intent = sender.intent_for(self) if sender else self.main_intent
|
||||
await intent.set_typing(self.mxid, is_typing=False)
|
||||
event_id = await intent.send_message(self.mxid, content)
|
||||
event_id = await self._send_message(intent, content)
|
||||
|
||||
prev_edit_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space, -1) or editing_msg
|
||||
DBMessage(mxid=event_id, mx_room=self.mxid, tg_space=tg_space, tgid=TelegramID(evt.id),
|
||||
@@ -522,6 +529,7 @@ class PortalTelegram(BasePortal, ABC):
|
||||
elif isinstance(action, MessageActionChatMigrateTo):
|
||||
self.peer_type = "channel"
|
||||
self._migrate_and_save_telegram(TelegramID(action.channel_id))
|
||||
# TODO encrypt
|
||||
await sender.intent_for(self).send_emote(self.mxid,
|
||||
"upgraded this group to a supergroup.")
|
||||
elif isinstance(action, MessageActionGameScore):
|
||||
|
||||
@@ -18,3 +18,6 @@ prometheus_client>=0.6,<0.8
|
||||
|
||||
#/postgres
|
||||
psycopg2-binary>=2,<3
|
||||
|
||||
#/e2be
|
||||
matrix-nio[e2e]>=0.9,<0.10
|
||||
|
||||
+1
-1
@@ -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.beta4
|
||||
mautrix==0.5.0.beta5
|
||||
telethon>=1.10,<1.12
|
||||
telethon-session-sqlalchemy>=0.2.14,<0.3
|
||||
|
||||
Reference in New Issue
Block a user