Bridge group chat portal metadata from Matrix to Telegram. Fixes #33
WARNING: Portal table schema changed. Run the following SQL before updating:
ALTER TABLE portal ADD COLUMN about VARCHAR
This commit is contained in:
@@ -70,19 +70,20 @@ The bridge does not do this automatically.
|
|||||||
* [x] Images
|
* [x] Images
|
||||||
* [x] Files
|
* [x] Files
|
||||||
* [x] Message redactions
|
* [x] Message redactions
|
||||||
* [ ] Presence (may not be possible)
|
* [ ] † Presence
|
||||||
* [ ] Typing notifications (may not be possible)
|
* [ ] † Typing notifications
|
||||||
* [ ] Pinning messages
|
* [ ] Pinning messages
|
||||||
* [ ] Power level
|
* [ ] Power level
|
||||||
* [x] Normal chats
|
* [x] Normal chats
|
||||||
* [ ] Supergroups/channels
|
* [ ] Supergroups/channels (currently only creator level bridged)
|
||||||
* [ ] Membership actions
|
* [ ] Membership actions
|
||||||
* [x] Inviting puppets
|
* [x] Inviting puppets
|
||||||
* [ ] Inviting Matrix users who have logged in to Telegram
|
* [ ] Inviting Matrix users who have logged in to Telegram
|
||||||
* [x] Kicking
|
* [x] Kicking
|
||||||
* [ ] Joining (once room aliases have been implemented)
|
* [ ] Joining (once room aliases have been implemented)
|
||||||
* [x] Leaving
|
* [x] Leaving
|
||||||
* [ ] Room metadata changes
|
* [x] Room metadata changes (name, topic, avatar)
|
||||||
|
* [x] Initial room metadata
|
||||||
* Telegram → Matrix
|
* Telegram → Matrix
|
||||||
* [x] Plaintext messages
|
* [x] Plaintext messages
|
||||||
* [x] Formatted messages
|
* [x] Formatted messages
|
||||||
@@ -108,9 +109,12 @@ The bridge does not do this automatically.
|
|||||||
* [x] Inviting
|
* [x] Inviting
|
||||||
* [x] Kicking
|
* [x] Kicking
|
||||||
* [x] Joining/leaving
|
* [x] Joining/leaving
|
||||||
* [x] Chat metadata changes
|
* [ ] Chat metadata changes
|
||||||
* [ ] Public channel username changes
|
* [x] Title
|
||||||
* [x] Initial chat metadata
|
* [x] Avatar
|
||||||
|
* [ ] † About text
|
||||||
|
* [ ] † Public channel username
|
||||||
|
* [x] Initial chat metadata (about text missing)
|
||||||
* [x] Supergroup upgrade
|
* [x] Supergroup upgrade
|
||||||
* Misc
|
* Misc
|
||||||
* [x] Automatic portal creation
|
* [x] Automatic portal creation
|
||||||
@@ -131,5 +135,9 @@ The bridge does not do this automatically.
|
|||||||
* [x] Joining chats with invite links (`join`)
|
* [x] Joining chats with invite links (`join`)
|
||||||
* [x] Creating a Telegram chat for an existing Matrix room (`create`)
|
* [x] Creating a Telegram chat for an existing Matrix room (`create`)
|
||||||
* [ ] Upgrading the chat of a portal room into a supergroup (`upgrade`)
|
* [ ] Upgrading the chat of a portal room into a supergroup (`upgrade`)
|
||||||
|
* [ ] Change public/private status of supergroup/channel (`setpublic`)
|
||||||
|
* [ ] Change username of supergroup/channel (`groupname`)
|
||||||
* [x] Getting the Telegram invite link to a Matrix room (`invitelink`)
|
* [x] Getting the Telegram invite link to a Matrix room (`invitelink`)
|
||||||
* [x] Clean up and forget a portal room (`deleteportal`)
|
* [x] Clean up and forget a portal room (`deleteportal`)
|
||||||
|
|
||||||
|
† Information not automatically sent from source, i.e. implementation may not be possible
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ class CommandHandler:
|
|||||||
self.reply(f"Created private chat room with {pu.Puppet.get_displayname(user, False)}")
|
self.reply(f"Created private chat room with {pu.Puppet.get_displayname(user, False)}")
|
||||||
|
|
||||||
@command_handler
|
@command_handler
|
||||||
def invite_link(self, sender, args):
|
def invitelink(self, sender, args):
|
||||||
if not sender.logged_in:
|
if not sender.logged_in:
|
||||||
return self.reply("This command requires you to be logged in.")
|
return self.reply("This command requires you to be logged in.")
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ class CommandHandler:
|
|||||||
return self.reply("You don't have the permission to create an invite link.")
|
return self.reply("You don't have the permission to create an invite link.")
|
||||||
|
|
||||||
@command_handler
|
@command_handler
|
||||||
def delete_portal(self, sender, args):
|
def deleteportal(self, sender, args):
|
||||||
if not sender.logged_in:
|
if not sender.logged_in:
|
||||||
return self.reply("This command requires you to be logged in.")
|
return self.reply("This command requires you to be logged in.")
|
||||||
elif not sender.is_admin:
|
elif not sender.is_admin:
|
||||||
@@ -331,10 +331,13 @@ class CommandHandler:
|
|||||||
|
|
||||||
state = self.az.intent.get_room_state(self._room_id)
|
state = self.az.intent.get_room_state(self._room_id)
|
||||||
title = None
|
title = None
|
||||||
|
about = None
|
||||||
levels = None
|
levels = None
|
||||||
for event in state:
|
for event in state:
|
||||||
if event["type"] == "m.room.name":
|
if event["type"] == "m.room.name":
|
||||||
title = event["content"]["name"]
|
title = event["content"]["name"]
|
||||||
|
elif event["type"] == "m.room.topic":
|
||||||
|
about = event["content"]["topic"]
|
||||||
elif event["type"] == "m.room.power_levels":
|
elif event["type"] == "m.room.power_levels":
|
||||||
levels = event["content"]
|
levels = event["content"]
|
||||||
if not title:
|
if not title:
|
||||||
@@ -359,7 +362,7 @@ class CommandHandler:
|
|||||||
"group": "chat",
|
"group": "chat",
|
||||||
}[type]
|
}[type]
|
||||||
|
|
||||||
portal = po.Portal(tgid=None, mxid=self._room_id, title=title, peer_type=type)
|
portal = po.Portal(tgid=None, mxid=self._room_id, title=title, about=about, peer_type=type)
|
||||||
try:
|
try:
|
||||||
portal.create_telegram_chat(sender, supergroup=supergroup)
|
portal.create_telegram_chat(sender, supergroup=supergroup)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -370,6 +373,14 @@ class CommandHandler:
|
|||||||
def upgrade(self, sender, args):
|
def upgrade(self, sender, args):
|
||||||
self.reply("Not yet implemented.")
|
self.reply("Not yet implemented.")
|
||||||
|
|
||||||
|
@command_handler
|
||||||
|
def setpublic(self, sender, args):
|
||||||
|
self.reply("Not yet implemented.")
|
||||||
|
|
||||||
|
@command_handler
|
||||||
|
def groupname(self, sender, args):
|
||||||
|
self.reply("Not yet implemented.")
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Command-related commands
|
# region Command-related commands
|
||||||
@command_handler
|
@command_handler
|
||||||
@@ -416,9 +427,12 @@ _**Telegram actions**: commands for using the bridge to interact with Telegram._
|
|||||||
**create** [_type_] - Create a Telegram chat of the given type for the current Matrix room.
|
**create** [_type_] - Create a Telegram chat of the given type for the current Matrix room.
|
||||||
The type is either `group`, `supergroup` or `channel` (defaults to `group`).
|
The type is either `group`, `supergroup` or `channel` (defaults to `group`).
|
||||||
**upgrade** - Upgrade a normal Telegram group to a supergroup.
|
**upgrade** - Upgrade a normal Telegram group to a supergroup.
|
||||||
**invite_link** - Get a Telegram invite link to the current chat.
|
**invitelink** - Get a Telegram invite link to the current chat.
|
||||||
**delete_portal** - Forget the current portal room. Only works for group chats; to delete a private
|
**deleteportal** - Forget the current portal room. Only works for group chats; to delete a private
|
||||||
chat portal, simply leave the room.
|
chat portal, simply leave the room.
|
||||||
|
**setpublic** <_yes/no_> - Change whether or not a supergroup/channel is public.
|
||||||
|
**groupname** <_name_> - Change the username of a supergroup/channel.
|
||||||
|
To disable, use `setpublic no`.
|
||||||
"""
|
"""
|
||||||
return self.reply(management_status + help)
|
return self.reply(management_status + help)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class Portal(Base):
|
|||||||
# Telegram chat metadata
|
# Telegram chat metadata
|
||||||
username = Column(String, nullable=True)
|
username = Column(String, nullable=True)
|
||||||
title = Column(String, nullable=True)
|
title = Column(String, nullable=True)
|
||||||
|
about = Column(String, nullable=True)
|
||||||
photo_id = Column(String, nullable=True)
|
photo_id = Column(String, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,20 @@ class MatrixHandler:
|
|||||||
sender = User.get_by_mxid(sender)
|
sender = User.get_by_mxid(sender)
|
||||||
portal.handle_matrix_power_levels(sender, new["users"], old["users"])
|
portal.handle_matrix_power_levels(sender, new["users"], old["users"])
|
||||||
|
|
||||||
|
def handle_room_meta(self, type, room, sender, content):
|
||||||
|
portal = Portal.get_by_mxid(room)
|
||||||
|
sender = User.get_by_mxid(sender)
|
||||||
|
if sender.has_full_access and portal:
|
||||||
|
handler, content_key = {
|
||||||
|
"m.room.name": (portal.handle_matrix_title, "name"),
|
||||||
|
"m.room.topic": (portal.handle_matrix_about, "topic"),
|
||||||
|
"m.room.avatar": (portal.handle_matrix_avatar, "url"),
|
||||||
|
}[type]
|
||||||
|
if content_key not in content:
|
||||||
|
# FIXME handle
|
||||||
|
pass
|
||||||
|
handler(sender, content[content_key])
|
||||||
|
|
||||||
def filter_matrix_event(self, event):
|
def filter_matrix_event(self, event):
|
||||||
return (event["sender"] == self.az.bot_mxid
|
return (event["sender"] == self.az.bot_mxid
|
||||||
or Puppet.get_id_from_mxid(event["sender"]) is not None)
|
or Puppet.get_id_from_mxid(event["sender"]) is not None)
|
||||||
@@ -209,3 +223,5 @@ class MatrixHandler:
|
|||||||
elif type == "m.room.power_levels":
|
elif type == "m.room.power_levels":
|
||||||
self.handle_power_levels(evt["room_id"], evt["sender"], evt["content"],
|
self.handle_power_levels(evt["room_id"], evt["sender"], evt["content"],
|
||||||
evt["prev_content"])
|
evt["prev_content"])
|
||||||
|
elif type == "m.room.name" or type == "m.room.avatar" or type == "m.room.topic":
|
||||||
|
self.handle_room_meta(type, evt["room_id"], evt["sender"], evt["content"])
|
||||||
|
|||||||
+95
-34
@@ -14,13 +14,9 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
from telethon.tl.functions.messages import (GetFullChatRequest, EditChatAdminRequest,
|
from telethon.tl.functions.messages import *
|
||||||
CreateChatRequest, AddChatUserRequest,
|
from telethon.tl.functions.channels import *
|
||||||
ExportChatInviteRequest, DeleteChatUserRequest)
|
from telethon.errors.rpc_error_list import *
|
||||||
from telethon.tl.functions.channels import (GetParticipantsRequest, CreateChannelRequest,
|
|
||||||
InviteToChannelRequest, ExportInviteRequest,
|
|
||||||
LeaveChannelRequest, EditBannedRequest)
|
|
||||||
from telethon.errors.rpc_error_list import ChatAdminRequiredError, LocationInvalidError
|
|
||||||
from telethon.tl.types import *
|
from telethon.tl.types import *
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -43,13 +39,14 @@ class Portal:
|
|||||||
by_tgid = {}
|
by_tgid = {}
|
||||||
|
|
||||||
def __init__(self, tgid, peer_type, tg_receiver=None, mxid=None, username=None, title=None,
|
def __init__(self, tgid, peer_type, tg_receiver=None, mxid=None, username=None, title=None,
|
||||||
photo_id=None):
|
about=None, photo_id=None):
|
||||||
self.mxid = mxid
|
self.mxid = mxid
|
||||||
self.tgid = tgid
|
self.tgid = tgid
|
||||||
self.tg_receiver = tg_receiver or tgid
|
self.tg_receiver = tg_receiver or tgid
|
||||||
self.peer_type = peer_type
|
self.peer_type = peer_type
|
||||||
self.username = username
|
self.username = username
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.about = about
|
||||||
self.photo_id = photo_id
|
self.photo_id = photo_id
|
||||||
self._main_intent = None
|
self._main_intent = None
|
||||||
|
|
||||||
@@ -77,6 +74,9 @@ class Portal:
|
|||||||
elif self.peer_type == "channel":
|
elif self.peer_type == "channel":
|
||||||
return PeerChannel(channel_id=self.tgid)
|
return PeerChannel(channel_id=self.tgid)
|
||||||
|
|
||||||
|
def get_input_entity(self, user):
|
||||||
|
return user.client.get_input_entity(self.peer)
|
||||||
|
|
||||||
# region Matrix room info updating
|
# region Matrix room info updating
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -202,35 +202,49 @@ class Portal:
|
|||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
if self.peer_type == "channel":
|
if self.peer_type == "channel":
|
||||||
if self.username != entity.username:
|
changed = self.update_username(entity.username) or changed
|
||||||
# TODO fix aliases and enable
|
# TODO update about text
|
||||||
# if self.username:
|
# changed = self.update_about(entity.about) or changed
|
||||||
# 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
|
changed = self.update_title(entity.title) or changed
|
||||||
|
|
||||||
if isinstance(entity.photo, ChatPhoto):
|
if isinstance(entity.photo, ChatPhoto):
|
||||||
changed = self.update_avatar(user, entity.photo.photo_big, self.main_intent) or changed
|
changed = self.update_avatar(user, entity.photo.photo_big) or changed
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def update_title(self, title, intent=None):
|
def update_username(self, username):
|
||||||
|
if self.username != username:
|
||||||
|
# TODO fix aliases and enable
|
||||||
|
# if self.username:
|
||||||
|
# self.main_intent.remove_room_alias(self._get_room_alias())
|
||||||
|
self.username = username
|
||||||
|
# if self.username:
|
||||||
|
# self.main_intent.add_room_alias(self.mxid, self._get_room_alias())
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update_about(self, about):
|
||||||
|
if self.about != about:
|
||||||
|
self.about = about
|
||||||
|
self.main_intent.set_room_topic(self.mxid, self.about)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update_title(self, title):
|
||||||
if self.title != title:
|
if self.title != title:
|
||||||
self.title = title
|
self.title = title
|
||||||
self.main_intent.set_room_name(self.mxid, self.title)
|
self.main_intent.set_room_name(self.mxid, self.title)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_largest_photo_size(self, photo):
|
@staticmethod
|
||||||
|
def _get_largest_photo_size(photo):
|
||||||
return max(photo.sizes, key=(lambda photo: (
|
return max(photo.sizes, key=(lambda photo: (
|
||||||
len(photo.bytes) if isinstance(photo, PhotoCachedSize) else photo.size)))
|
len(photo.bytes) if isinstance(photo, PhotoCachedSize) else photo.size)))
|
||||||
|
|
||||||
def update_avatar(self, user, photo, intent=None):
|
def update_avatar(self, user, photo):
|
||||||
photo_id = f"{photo.volume_id}-{photo.local_id}"
|
photo_id = f"{photo.volume_id}-{photo.local_id}"
|
||||||
if self.photo_id != photo_id:
|
if self.photo_id != photo_id:
|
||||||
try:
|
try:
|
||||||
@@ -265,7 +279,7 @@ class Portal:
|
|||||||
link = user.client(ExportChatInviteRequest(chat_id=self.tgid))
|
link = user.client(ExportChatInviteRequest(chat_id=self.tgid))
|
||||||
elif self.peer_type == "channel":
|
elif self.peer_type == "channel":
|
||||||
link = user.client(
|
link = user.client(
|
||||||
ExportInviteRequest(channel=user.client.get_input_entity(self.peer)))
|
ExportInviteRequest(channel=self.get_input_entity(user)))
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid peer type '{self.peer_type}' for invite link.")
|
raise ValueError(f"Invalid peer type '{self.peer_type}' for invite link.")
|
||||||
|
|
||||||
@@ -296,11 +310,11 @@ class Portal:
|
|||||||
del self.by_tgid[self.tgid_full]
|
del self.by_tgid[self.tgid_full]
|
||||||
del self.by_mxid[self.mxid]
|
del self.by_mxid[self.mxid]
|
||||||
elif source:
|
elif source:
|
||||||
target = source.client.get_input_entity(PeerUser(user_id=user.tgid))
|
target = user.get_input_entity(source)
|
||||||
if self.peer_type == "chat":
|
if self.peer_type == "chat":
|
||||||
source.client(DeleteChatUserRequest(chat_id=self.tgid, user_id=target))
|
source.client(DeleteChatUserRequest(chat_id=self.tgid, user_id=target))
|
||||||
else:
|
else:
|
||||||
channel = source.client.get_input_entity(self.peer)
|
channel = self.get_input_entity(source)
|
||||||
rights = ChannelBannedRights(datetime.fromtimestamp(0), True)
|
rights = ChannelBannedRights(datetime.fromtimestamp(0), True)
|
||||||
source.client(EditBannedRequest(channel=channel,
|
source.client(EditBannedRequest(channel=channel,
|
||||||
user_id=target,
|
user_id=target,
|
||||||
@@ -308,7 +322,7 @@ class Portal:
|
|||||||
elif self.peer_type == "chat":
|
elif self.peer_type == "chat":
|
||||||
user.client(DeleteChatUserRequest(chat_id=self.tgid, user_id=InputUserSelf()))
|
user.client(DeleteChatUserRequest(chat_id=self.tgid, user_id=InputUserSelf()))
|
||||||
elif self.peer_type == "channel":
|
elif self.peer_type == "channel":
|
||||||
channel = user.client.get_input_entity(self.peer)
|
channel = self.get_input_entity(user)
|
||||||
user.client(LeaveChannelRequest(channel=channel))
|
user.client(LeaveChannelRequest(channel=channel))
|
||||||
|
|
||||||
def handle_matrix_message(self, sender, message, event_id):
|
def handle_matrix_message(self, sender, message, event_id):
|
||||||
@@ -358,7 +372,7 @@ class Portal:
|
|||||||
|
|
||||||
def handle_matrix_power_levels(self, sender, new_users, old_users):
|
def handle_matrix_power_levels(self, sender, new_users, old_users):
|
||||||
for user, level in new_users.items():
|
for user, level in new_users.items():
|
||||||
user_id = p.Puppet.get_id_by_mxid(user)
|
user_id = p.Puppet.get_id_from_mxid(user)
|
||||||
if not user_id:
|
if not user_id:
|
||||||
mx_user = u.User.get_by_mxid(user, create=False)
|
mx_user = u.User.get_by_mxid(user, create=False)
|
||||||
if not mx_user or not mx_user.tgid:
|
if not mx_user or not mx_user.tgid:
|
||||||
@@ -368,6 +382,52 @@ class Portal:
|
|||||||
sender.client(
|
sender.client(
|
||||||
EditChatAdminRequest(chat_id=self.tgid, user_id=user_id, is_admin=level >= 50))
|
EditChatAdminRequest(chat_id=self.tgid, user_id=user_id, is_admin=level >= 50))
|
||||||
|
|
||||||
|
def handle_matrix_about(self, sender, about):
|
||||||
|
if self.peer_type not in {"channel"}:
|
||||||
|
return
|
||||||
|
channel = self.get_input_entity(sender)
|
||||||
|
sender.client(EditAboutRequest(channel=channel, about=about))
|
||||||
|
self.about = about
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def handle_matrix_title(self, sender, title):
|
||||||
|
if self.peer_type not in {"chat", "channel"}:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.peer_type == "chat":
|
||||||
|
sender.client(EditChatTitleRequest(chat_id=self.tgid, title=title))
|
||||||
|
else:
|
||||||
|
channel = self.get_input_entity(sender)
|
||||||
|
sender.client(EditTitleRequest(channel=channel, title=title))
|
||||||
|
self.title = title
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def handle_matrix_avatar(self, sender, url):
|
||||||
|
if self.peer_type not in {"chat", "channel"}:
|
||||||
|
# Invalid peer type
|
||||||
|
return
|
||||||
|
|
||||||
|
file = self.main_intent.download_file(url)
|
||||||
|
mime = magic.from_buffer(file, mime=True)
|
||||||
|
ext = mimetypes.guess_extension(mime)
|
||||||
|
uploaded = sender.client.upload_file(file, file_name=f"avatar{ext}")
|
||||||
|
photo = InputChatUploadedPhoto(file=uploaded)
|
||||||
|
|
||||||
|
if self.peer_type == "chat":
|
||||||
|
updates = sender.client(EditChatPhotoRequest(chat_id=self.tgid, photo=photo))
|
||||||
|
else:
|
||||||
|
channel = self.get_input_entity(sender)
|
||||||
|
updates = sender.client(EditPhotoRequest(channel=channel, photo=photo))
|
||||||
|
for update in updates.updates:
|
||||||
|
is_photo_update = (isinstance(update, UpdateNewMessage)
|
||||||
|
and isinstance(update.message, MessageService)
|
||||||
|
and isinstance(update.message.action, MessageActionChatEditPhoto))
|
||||||
|
if is_photo_update:
|
||||||
|
loc = self._get_largest_photo_size(update.message.action.photo).location
|
||||||
|
self.photo_id = f"{loc.volume_id}-{loc.local_id}"
|
||||||
|
self.save()
|
||||||
|
break
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Telegram chat info updating
|
# region Telegram chat info updating
|
||||||
|
|
||||||
@@ -402,7 +462,7 @@ class Portal:
|
|||||||
updates = source.client(CreateChatRequest(title=self.title, users=invites))
|
updates = source.client(CreateChatRequest(title=self.title, users=invites))
|
||||||
entity = updates.chats[0]
|
entity = updates.chats[0]
|
||||||
elif self.peer_type == "channel":
|
elif self.peer_type == "channel":
|
||||||
updates = source.client(CreateChannelRequest(title=self.title, about="",
|
updates = source.client(CreateChannelRequest(title=self.title, about=self.about or "",
|
||||||
megagroup=supergroup))
|
megagroup=supergroup))
|
||||||
entity = updates.chats[0]
|
entity = updates.chats[0]
|
||||||
source.client(InviteToChannelRequest(channel=source.client.get_input_entity(entity),
|
source.client(InviteToChannelRequest(channel=source.client.get_input_entity(entity),
|
||||||
@@ -419,7 +479,7 @@ class Portal:
|
|||||||
if self.peer_type == "chat":
|
if self.peer_type == "chat":
|
||||||
source.client(AddChatUserRequest(chat_id=self.tgid, user_id=puppet.tgid, fwd_limit=0))
|
source.client(AddChatUserRequest(chat_id=self.tgid, user_id=puppet.tgid, fwd_limit=0))
|
||||||
elif self.peer_type == "channel":
|
elif self.peer_type == "channel":
|
||||||
target = source.client.get_input_entity(PeerUser(user_id=puppet.tgid))
|
target = puppet.get_input_entity(source)
|
||||||
source.client(InviteToChannelRequest(channel=self.peer, users=[target]))
|
source.client(InviteToChannelRequest(channel=self.peer, users=[target]))
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid peer type for Telegram user invite")
|
raise ValueError("Invalid peer type for Telegram user invite")
|
||||||
@@ -432,7 +492,7 @@ class Portal:
|
|||||||
user.intent.set_typing(self.mxid, is_typing=True)
|
user.intent.set_typing(self.mxid, is_typing=True)
|
||||||
|
|
||||||
def handle_telegram_photo(self, source, sender, media):
|
def handle_telegram_photo(self, source, sender, media):
|
||||||
largest_size = self.get_largest_photo_size(media.photo)
|
largest_size = self._get_largest_photo_size(media.photo)
|
||||||
file = source.download_file(largest_size.location)
|
file = source.download_file(largest_size.location)
|
||||||
mime_type = magic.from_buffer(file, mime=True)
|
mime_type = magic.from_buffer(file, mime=True)
|
||||||
uploaded = sender.intent.upload_file(file, mime_type)
|
uploaded = sender.intent.upload_file(file, mime_type)
|
||||||
@@ -554,12 +614,13 @@ class Portal:
|
|||||||
if not isinstance(action, create_and_continue):
|
if not isinstance(action, create_and_continue):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# TODO figure out how to see changes to about text / channel username
|
||||||
if isinstance(action, MessageActionChatEditTitle):
|
if isinstance(action, MessageActionChatEditTitle):
|
||||||
if self.update_title(action.title, self.main_intent):
|
if self.update_title(action.title):
|
||||||
self.save()
|
self.save()
|
||||||
elif isinstance(action, MessageActionChatEditPhoto):
|
elif isinstance(action, MessageActionChatEditPhoto):
|
||||||
largest_size = self.get_largest_photo_size(action.photo)
|
largest_size = self._get_largest_photo_size(action.photo)
|
||||||
if self.update_avatar(source, largest_size.location, self.main_intent):
|
if self.update_avatar(source, largest_size.location):
|
||||||
self.save()
|
self.save()
|
||||||
elif isinstance(action, MessageActionChatAddUser):
|
elif isinstance(action, MessageActionChatAddUser):
|
||||||
for user_id in action.users:
|
for user_id in action.users:
|
||||||
@@ -619,7 +680,7 @@ class Portal:
|
|||||||
return self.db.merge(
|
return self.db.merge(
|
||||||
DBPortal(tgid=self.tgid, tg_receiver=self.tg_receiver, peer_type=self.peer_type,
|
DBPortal(tgid=self.tgid, tg_receiver=self.tg_receiver, peer_type=self.peer_type,
|
||||||
mxid=self.mxid, username=self.username, title=self.title,
|
mxid=self.mxid, username=self.username, title=self.title,
|
||||||
photo_id=self.photo_id))
|
about=self.about, photo_id=self.photo_id))
|
||||||
|
|
||||||
def migrate_and_save(self, new_id):
|
def migrate_and_save(self, new_id):
|
||||||
existing = DBPortal.query.get(self.tgid_full)
|
existing = DBPortal.query.get(self.tgid_full)
|
||||||
@@ -643,7 +704,7 @@ class Portal:
|
|||||||
return Portal(tgid=db_portal.tgid, tg_receiver=db_portal.tg_receiver,
|
return Portal(tgid=db_portal.tgid, tg_receiver=db_portal.tg_receiver,
|
||||||
peer_type=db_portal.peer_type, mxid=db_portal.mxid,
|
peer_type=db_portal.peer_type, mxid=db_portal.mxid,
|
||||||
username=db_portal.username, title=db_portal.title,
|
username=db_portal.username, title=db_portal.title,
|
||||||
photo_id=db_portal.photo_id)
|
about=db_portal.about, photo_id=db_portal.photo_id)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Class instance lookup
|
# region Class instance lookup
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
import re
|
import re
|
||||||
from telethon.tl.types import UserProfilePhoto
|
from telethon.tl.types import UserProfilePhoto, PeerUser
|
||||||
from telethon.errors.rpc_error_list import LocationInvalidError
|
from telethon.errors.rpc_error_list import LocationInvalidError
|
||||||
from .db import Puppet as DBPuppet
|
from .db import Puppet as DBPuppet
|
||||||
|
|
||||||
@@ -47,6 +47,9 @@ class Puppet:
|
|||||||
def tgid(self):
|
def tgid(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
def get_input_entity(self, user):
|
||||||
|
return user.client.get_input_entity(PeerUser(user_id=self.tgid))
|
||||||
|
|
||||||
def to_db(self):
|
def to_db(self):
|
||||||
return self.db.merge(
|
return self.db.merge(
|
||||||
DBPuppet(id=self.id, username=self.username, displayname=self.displayname,
|
DBPuppet(id=self.id, username=self.username, displayname=self.displayname,
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ class User:
|
|||||||
def has_full_access(self):
|
def has_full_access(self):
|
||||||
return self.logged_in and self.whitelisted
|
return self.logged_in and self.whitelisted
|
||||||
|
|
||||||
|
def get_input_entity(self, user):
|
||||||
|
return user.client.get_input_entity(InputUser(user_id=self.tgid))
|
||||||
|
|
||||||
# region Database conversion
|
# region Database conversion
|
||||||
|
|
||||||
def to_db(self):
|
def to_db(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user