mirror of
https://github.com/mautrix/telegram.git
synced 2026-05-17 07:25:46 +03:00
Add Matrix->Telegram kicking and fix and improve things. Fixes #36
This commit is contained in:
@@ -79,7 +79,7 @@ The bridge does not do this automatically.
|
||||
* [ ] Membership actions
|
||||
* [x] Inviting puppets
|
||||
* [ ] Inviting Matrix users who have logged in to Telegram
|
||||
* [ ] Kicking
|
||||
* [x] Kicking
|
||||
* [ ] Joining (once room aliases have been implemented)
|
||||
* [x] Leaving
|
||||
* [ ] Room metadata changes
|
||||
|
||||
@@ -211,6 +211,14 @@ class IntentAPI:
|
||||
content["info"] = info
|
||||
return self.send_state_event(room_id, "m.room.avatar", content)
|
||||
|
||||
def add_room_alias(self, room_id, alias):
|
||||
self._ensure_registered()
|
||||
self.client.set_room_alias(room_id, alias)
|
||||
|
||||
def remove_room_alias(self, alias):
|
||||
self._ensure_registered()
|
||||
self.client.remove_room_alias(alias)
|
||||
|
||||
def set_room_name(self, room_id, name):
|
||||
self._ensure_joined(room_id)
|
||||
self._ensure_has_power_level_for(room_id, "m.room.name")
|
||||
|
||||
@@ -351,14 +351,14 @@ class CommandHandler:
|
||||
+ f"Use power level 95 instead of 100 for admins.")
|
||||
|
||||
supergroup = type == "supergroup"
|
||||
types = {
|
||||
type = {
|
||||
"supergroup": "channel",
|
||||
"channel": "channel",
|
||||
"chat": "chat",
|
||||
"group": "chat",
|
||||
}
|
||||
}[type]
|
||||
|
||||
portal = po.Portal(tgid=None, mxid=self._room_id, title=title, peer_type=types[type])
|
||||
portal = po.Portal(tgid=None, mxid=self._room_id, title=title, peer_type=type)
|
||||
try:
|
||||
portal.create_telegram_chat(sender, supergroup=supergroup)
|
||||
except ValueError as e:
|
||||
|
||||
@@ -80,13 +80,11 @@ class MatrixParser(HTMLParser):
|
||||
reply = self.reply_regex.search(url)
|
||||
if mention:
|
||||
mxid = mention.group(1)
|
||||
puppet_match = p.Puppet.mxid_regex.search(mxid)
|
||||
if puppet_match:
|
||||
user = p.Puppet.get(puppet_match.group(1), create=False)
|
||||
else:
|
||||
user = u.User.get_by_mxid(mxid, create=False)
|
||||
user = p.Puppet.get_by_mxid(mxid, create=False)
|
||||
if not user:
|
||||
return
|
||||
user = u.User.get_by_mxid(mxid, create=False)
|
||||
if not user:
|
||||
return
|
||||
if user.username:
|
||||
EntityType = MessageEntityMention
|
||||
url = f"@{user.username}"
|
||||
@@ -218,11 +216,12 @@ def telegram_event_to_matrix(evt, source):
|
||||
|
||||
if evt.reply_to_msg_id:
|
||||
msg = DBMessage.query.get((evt.reply_to_msg_id, source.tgid))
|
||||
quote = f"<a href=\"https://matrix.to/#/{msg.mx_room}/{msg.mxid}\">Quote<br></a>"
|
||||
if html:
|
||||
html = quote + html
|
||||
else:
|
||||
html = quote + escape(text)
|
||||
if msg:
|
||||
quote = f"<a href=\"https://matrix.to/#/{msg.mx_room}/{msg.mxid}\">Quote<br></a>"
|
||||
if html:
|
||||
html = quote + html
|
||||
else:
|
||||
html = quote + escape(text)
|
||||
|
||||
if html:
|
||||
html = html.replace("\n", "<br/>")
|
||||
|
||||
@@ -32,16 +32,6 @@ class MatrixHandler:
|
||||
self.az.intent.set_display_name(
|
||||
self.config.get("appservice.bot_displayname", "Telegram bridge bot"))
|
||||
|
||||
def is_puppet(self, mxid):
|
||||
match = Puppet.mxid_regex.match(mxid)
|
||||
return True if match else False
|
||||
|
||||
def get_puppet(self, mxid):
|
||||
match = Puppet.mxid_regex.match(mxid)
|
||||
if not match:
|
||||
return None
|
||||
return Puppet.get(int(match.group(1)))
|
||||
|
||||
def handle_puppet_invite(self, room, puppet, inviter):
|
||||
self.log.debug(f"{inviter} invited puppet for {puppet.tgid} to {room}")
|
||||
if not inviter.logged_in:
|
||||
@@ -98,7 +88,7 @@ class MatrixHandler:
|
||||
elif user == self.az.bot_mxid:
|
||||
self.az.intent.join_room(room)
|
||||
return
|
||||
puppet = self.get_puppet(user)
|
||||
puppet = Puppet.get_by_mxid(user)
|
||||
if puppet:
|
||||
self.handle_puppet_invite(room, puppet, inviter)
|
||||
return
|
||||
@@ -117,6 +107,7 @@ class MatrixHandler:
|
||||
"You are not whitelisted on this Telegram bridge.")
|
||||
return
|
||||
elif not user.logged_in:
|
||||
# TODO[waiting-for-bots] once we have bot support, this won't be needed.
|
||||
portal.main_intent.kick(room, user.mxid,
|
||||
"You are not logged into this Telegram bridge.")
|
||||
return
|
||||
@@ -124,13 +115,22 @@ class MatrixHandler:
|
||||
self.log.debug(f"{user} joined {room}")
|
||||
# TODO join Telegram chat if applicable
|
||||
|
||||
def handle_part(self, room, user):
|
||||
def handle_part(self, room, user, sender):
|
||||
self.log.debug(f"{user} left {room}")
|
||||
user = User.get_by_mxid(user, create=False)
|
||||
|
||||
sender = User.get_by_mxid(sender, create=False)
|
||||
|
||||
portal = Portal.get_by_mxid(room)
|
||||
if user and portal and user.logged_in:
|
||||
portal.leave_matrix(user)
|
||||
# TODO check if the event was a puppet being kicked and handle accordingly.
|
||||
if not portal:
|
||||
return
|
||||
|
||||
puppet = Puppet.get_by_mxid(user)
|
||||
if sender and puppet:
|
||||
portal.leave_matrix(puppet, sender)
|
||||
|
||||
user = User.get_by_mxid(user, create=False)
|
||||
if user and user.logged_in:
|
||||
portal.leave_matrix(user, sender)
|
||||
|
||||
def is_command(self, message):
|
||||
text = message.get("body", "")
|
||||
@@ -185,7 +185,8 @@ class MatrixHandler:
|
||||
portal.handle_matrix_power_levels(sender, new["users"], old["users"])
|
||||
|
||||
def filter_matrix_event(self, event):
|
||||
return event["sender"] == self.az.bot_mxid or self.is_puppet(event["sender"])
|
||||
return (event["sender"] == self.az.bot_mxid
|
||||
or Puppet.get_id_from_mxid(event["sender"]) is not None)
|
||||
|
||||
def handle_event(self, evt):
|
||||
if self.filter_matrix_event(evt):
|
||||
@@ -194,11 +195,11 @@ class MatrixHandler:
|
||||
type = evt["type"]
|
||||
content = evt.get("content", {})
|
||||
if type == "m.room.member":
|
||||
membership = content.get("membership", {})
|
||||
membership = content.get("membership", "")
|
||||
if membership == "invite":
|
||||
self.handle_invite(evt["room_id"], evt["state_key"], evt["sender"])
|
||||
elif membership == "leave":
|
||||
self.handle_part(evt["room_id"], evt["state_key"])
|
||||
self.handle_part(evt["room_id"], evt["state_key"], evt["sender"])
|
||||
elif membership == "join":
|
||||
self.handle_join(evt["room_id"], evt["state_key"])
|
||||
elif type == "m.room.message":
|
||||
|
||||
@@ -19,11 +19,12 @@ from telethon.tl.functions.messages import (GetFullChatRequest, EditChatAdminReq
|
||||
ExportChatInviteRequest, DeleteChatUserRequest)
|
||||
from telethon.tl.functions.channels import (GetParticipantsRequest, CreateChannelRequest,
|
||||
InviteToChannelRequest, ExportInviteRequest,
|
||||
LeaveChannelRequest)
|
||||
LeaveChannelRequest, EditBannedRequest)
|
||||
from telethon.errors.rpc_error_list import ChatAdminRequiredError, LocationInvalidError
|
||||
from telethon.tl.types import *
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
from datetime import datetime
|
||||
import mimetypes
|
||||
import magic
|
||||
from .db import Portal as DBPortal, Message as DBMessage
|
||||
@@ -127,7 +128,17 @@ class Portal:
|
||||
puppet = p.Puppet.get(self.tgid) if direct else None
|
||||
intent = puppet.intent if direct else self.az.intent
|
||||
|
||||
# TODO set room alias if public channel.
|
||||
# TODO fix aliases and enable
|
||||
# if self.peer_type == "channel" and entity.username:
|
||||
# public = True
|
||||
# alias = self._get_room_alias(entity.username)
|
||||
# else:
|
||||
# public = False
|
||||
# # TODO invite link alias?
|
||||
# alias = None
|
||||
|
||||
# room = intent.create_room(alias=alias, is_public=public, invitees=invites, name=title,
|
||||
# is_direct=direct)
|
||||
room = intent.create_room(invitees=invites, name=title, is_direct=direct)
|
||||
if not room:
|
||||
raise Exception(f"Failed to create room for {self.tgid_log}")
|
||||
@@ -136,7 +147,7 @@ class Portal:
|
||||
self.by_mxid[self.mxid] = self
|
||||
self.save()
|
||||
|
||||
power_level_requirement = 0 if self.peer_type == "chat" else 50
|
||||
power_level_requirement = 0 if self.peer_type == "chat" and entity.admins_enabled else 50
|
||||
levels = self.main_intent.get_power_levels(self.mxid)
|
||||
levels["ban"] = 100
|
||||
levels["invite"] = 50
|
||||
@@ -147,6 +158,11 @@ class Portal:
|
||||
self.main_intent.set_power_levels(self.mxid, levels)
|
||||
self.update_after_create(user, entity, direct, puppet)
|
||||
|
||||
def _get_room_alias(self, username=None):
|
||||
username = username or self.username
|
||||
return config.get("bridge.alias_template", "telegram_{groupname}").format(
|
||||
groupname=username)
|
||||
|
||||
def sync_telegram_users(self, source, users=[]):
|
||||
for entity in users:
|
||||
puppet = p.Puppet.get(entity.id)
|
||||
@@ -187,8 +203,12 @@ class Portal:
|
||||
|
||||
if self.peer_type == "channel":
|
||||
if self.username != entity.username:
|
||||
# TODO update room alias
|
||||
# TODO fix aliases and enable
|
||||
# if self.username:
|
||||
# self.main_intent.remove_room_alias(self._get_room_alias())
|
||||
self.username = entity.username
|
||||
# if self.username:
|
||||
# self.main_intent.add_room_alias(self.mxid, self._get_room_alias())
|
||||
changed = True
|
||||
|
||||
changed = self.update_title(entity.title, self.main_intent) or changed
|
||||
@@ -244,7 +264,8 @@ class Portal:
|
||||
elif self.peer_type == "chat":
|
||||
link = user.client(ExportChatInviteRequest(chat_id=self.tgid))
|
||||
elif self.peer_type == "channel":
|
||||
link = user.client(ExportInviteRequest(channel=user.client.get_input_entity(self.peer)))
|
||||
link = user.client(
|
||||
ExportInviteRequest(channel=user.client.get_input_entity(self.peer)))
|
||||
else:
|
||||
raise ValueError(f"Invalid peer type '{self.peer_type}' for invite link.")
|
||||
|
||||
@@ -268,16 +289,28 @@ class Portal:
|
||||
file_name = f"matrix_upload{mimetypes.guess_extension(mime)}"
|
||||
return file_name, None if file_name == body else body
|
||||
|
||||
def leave_matrix(self, user):
|
||||
def leave_matrix(self, user, source):
|
||||
if self.peer_type == "user":
|
||||
self.main_intent.leave_room(self.mxid)
|
||||
self.delete()
|
||||
del self.by_tgid[self.tgid_full]
|
||||
del self.by_mxid[self.mxid]
|
||||
elif source:
|
||||
target = source.client.get_input_entity(PeerUser(user_id=user.tgid))
|
||||
if self.peer_type == "chat":
|
||||
source.client(DeleteChatUserRequest(chat_id=self.tgid, user_id=target))
|
||||
else:
|
||||
channel = source.client.get_input_entity(self.peer)
|
||||
rights = ChannelBannedRights(datetime.fromtimestamp(0), False)
|
||||
# FIXME This should work, but it doesn't :(
|
||||
source.client(EditBannedRequest(channel=channel,
|
||||
user_id=target,
|
||||
banned_rights=rights))
|
||||
elif self.peer_type == "chat":
|
||||
user.client(DeleteChatUserRequest(chat_id=self.tgid, user_id=InputUserSelf()))
|
||||
elif self.peer_type == "channel":
|
||||
user.client(LeaveChannelRequest(channel=user.client.get_input_entity(self.peer)))
|
||||
channel = user.client.get_input_entity(self.peer)
|
||||
user.client(LeaveChannelRequest(channel=channel))
|
||||
|
||||
def handle_matrix_message(self, sender, message, event_id):
|
||||
type = message["msgtype"]
|
||||
@@ -326,10 +359,8 @@ class Portal:
|
||||
|
||||
def handle_matrix_power_levels(self, sender, new_users, old_users):
|
||||
for user, level in new_users.items():
|
||||
puppet_match = p.Puppet.mxid_regex.search(user)
|
||||
if puppet_match:
|
||||
user_id = int(puppet_match.group(1))
|
||||
else:
|
||||
user_id = p.Puppet.get_id_by_mxid(user)
|
||||
if not user_id:
|
||||
mx_user = u.User.get_by_mxid(user, create=False)
|
||||
if not mx_user or not mx_user.tgid:
|
||||
continue
|
||||
@@ -350,9 +381,9 @@ class Portal:
|
||||
mx_user = u.User.get_by_mxid(user, create=False)
|
||||
if mx_user and mx_user.tgid:
|
||||
user_tgids.add(mx_user.tgid)
|
||||
puppet_match = p.Puppet.mxid_regex.match(user)
|
||||
if puppet_match:
|
||||
user_tgids.add(int(puppet_match.group(1)))
|
||||
puppet_id = p.Puppet.get_id_from_mxid(user)
|
||||
if puppet_id:
|
||||
user_tgids.add(puppet_id)
|
||||
return user_tgids
|
||||
|
||||
def create_telegram_chat(self, source, supergroup=False):
|
||||
@@ -363,20 +394,23 @@ class Portal:
|
||||
|
||||
invites = self._get_telegram_users_in_matrix_room()
|
||||
if len(invites) < 2:
|
||||
# TODO when we get the option for a bot, this won't happen when the bot is activated.
|
||||
# TODO[waiting-for-bots] This won't happen when the bot is enabled
|
||||
raise ValueError("Not enough Telegram users to create a chat")
|
||||
|
||||
invites = [source.client.get_input_entity(id) for id in invites]
|
||||
|
||||
if self.peer_type == "chat":
|
||||
updates = source.client(CreateChatRequest(title=self.title, users=invites))
|
||||
entity = updates.chats[0]
|
||||
elif self.peer_type == "channel":
|
||||
updates = source.client(CreateChannelRequest(title=self.title, megagroup=supergroup))
|
||||
# TODO invite people
|
||||
updates = source.client(CreateChannelRequest(title=self.title, about="",
|
||||
megagroup=supergroup))
|
||||
entity = updates.chats[0]
|
||||
source.client(InviteToChannelRequest(channel=source.client.get_input_entity(entity),
|
||||
users=invites))
|
||||
else:
|
||||
raise ValueError("Invalid peer type for Telegram chat creation")
|
||||
|
||||
entity = updates.chats[0]
|
||||
self.tgid = entity.id
|
||||
self.tg_receiver = self.tgid
|
||||
self.update_info(source, entity)
|
||||
@@ -386,9 +420,8 @@ class Portal:
|
||||
if self.peer_type == "chat":
|
||||
source.client(AddChatUserRequest(chat_id=self.tgid, user_id=puppet.tgid, fwd_limit=0))
|
||||
elif self.peer_type == "channel":
|
||||
source.client(InviteToChannelRequest(channel=self.peer,
|
||||
users=[InputUser(user_id=puppet.tgid)],
|
||||
fwd_limit=0))
|
||||
target = source.client.get_input_entity(PeerUser(user_id=puppet.tgid))
|
||||
source.client(InviteToChannelRequest(channel=self.peer, users=[target]))
|
||||
else:
|
||||
raise ValueError("Invalid peer type for Telegram user invite")
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ class Puppet:
|
||||
"first name": info.first_name,
|
||||
"last name": info.last_name,
|
||||
}
|
||||
preferences = config.get("bridge", {}).get("displayname_preference",
|
||||
["full name", "username", "phone"])
|
||||
preferences = config.get("bridge.displayname_preference",
|
||||
["full name", "username", "phone"])
|
||||
for preference in preferences:
|
||||
name = data[preference]
|
||||
if name:
|
||||
@@ -136,6 +136,18 @@ class Puppet:
|
||||
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_by_mxid(cls, mxid, create=True):
|
||||
tgid = cls.get_id_from_mxid(mxid)
|
||||
return cls.get(tgid, create) if tgid else None
|
||||
|
||||
@classmethod
|
||||
def get_id_from_mxid(cls, mxid):
|
||||
match = cls.mxid_regex.match(mxid)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_by_username(cls, username):
|
||||
for _, puppet in cls.cache.items():
|
||||
|
||||
@@ -41,11 +41,9 @@ class User:
|
||||
self.connected = False
|
||||
self.client = None
|
||||
|
||||
bridge_config = config.get("bridge", {})
|
||||
self.is_admin = self.mxid in config.get("bridge.admins", [])
|
||||
|
||||
self.is_admin = self.mxid in bridge_config.get("admins", [])
|
||||
|
||||
whitelist = bridge_config.get("whitelist", None) or [self.mxid]
|
||||
whitelist = config.get("bridge.whitelist", None) or [self.mxid]
|
||||
self.whitelisted = not whitelist or self.mxid in whitelist
|
||||
if not self.whitelisted:
|
||||
homeserver = self.mxid[self.mxid.index(":") + 1:]
|
||||
|
||||
Reference in New Issue
Block a user