mirror of
https://github.com/mautrix/telegram.git
synced 2026-05-17 07:25:46 +03:00
Add support for joining chats and initiating private chats
This commit is contained in:
@@ -105,18 +105,17 @@ does not do this automatically.~~
|
||||
* [ ] Public channel username changes
|
||||
* [x] Initial chat metadata
|
||||
* [x] Supergroup upgrade
|
||||
* Initiating chats
|
||||
* Misc
|
||||
* [x] Automatic portal creation for groups/channels at startup
|
||||
* [x] Automatic portal creation for groups/channels when receiving invite/message
|
||||
* [ ] Private chat creation by inviting Telegram user to new room
|
||||
* [ ] Searching for Telegram users using management commands
|
||||
* Misc
|
||||
* [ ] Use optional bot to relay messages for unauthenticated Matrix users
|
||||
* [ ] Joining public channels/supergroups using room aliases
|
||||
* Commands
|
||||
* [x] Logging in and out (`login` + code entering, `logout`)
|
||||
* [ ] Registering (`register`)
|
||||
* [ ] Searching for users (`search`)
|
||||
* [ ] Starting private chats (`pm`)
|
||||
* [x] Searching for users (`search`)
|
||||
* [x] Starting private chats (`pm`)
|
||||
* [x] Joining chats with invite links (`join`)
|
||||
* [ ] Creating a Telegram chat for an existing Matrix room (`create`)
|
||||
* [ ] Upgrading the chat of a portal room into a supergroup (`upgrade`)
|
||||
|
||||
@@ -187,9 +187,10 @@ class IntentAPI:
|
||||
# region Room actions
|
||||
|
||||
def create_room(self, alias=None, is_public=False, name=None, topic=None, is_direct=False,
|
||||
invitees=()):
|
||||
invitees=(), initial_state=[]):
|
||||
self._ensure_registered()
|
||||
return self.client.create_room(alias, is_public, name, topic, is_direct, invitees)
|
||||
return self.client.create_room(alias, is_public, name, topic, is_direct, invitees,
|
||||
initial_state)
|
||||
|
||||
def invite(self, room_id, user_id):
|
||||
self._ensure_joined(room_id)
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
from contextlib import contextmanager
|
||||
import markdown
|
||||
from telethon.errors import *
|
||||
from telethon.tl.types import *
|
||||
from telethon.tl.functions.contacts import SearchRequest
|
||||
from telethon.tl.functions.messages import ImportChatInviteRequest, CheckChatInviteRequest
|
||||
from telethon.tl.functions.channels import JoinChannelRequest
|
||||
from . import puppet as pu, portal as po
|
||||
|
||||
command_handlers = {}
|
||||
|
||||
@@ -37,7 +42,12 @@ class CommandHandler:
|
||||
|
||||
def handle(self, room, sender, command, args, is_management, is_portal):
|
||||
with self.handler(sender, room, command, args, is_management, is_portal) as handle_command:
|
||||
handle_command(self, sender, args)
|
||||
try:
|
||||
handle_command(self, sender, args)
|
||||
except:
|
||||
self.reply("Fatal error while handling command. Check logs for more details.")
|
||||
self.log.exception(f"Fatal error handling command "
|
||||
f"'$cmdprefix {command} {''.join(args)}' from {sender.mxid}")
|
||||
|
||||
@contextmanager
|
||||
def handler(self, sender, room, command, args, is_management, is_portal):
|
||||
@@ -62,9 +72,9 @@ class CommandHandler:
|
||||
raise AttributeError("the reply function can only be used from within"
|
||||
"the `CommandHandler.run` context manager")
|
||||
|
||||
message = message.replace("$cmdprefix", self.command_prefix)
|
||||
message = message.replace("$cmdprefix+sp ",
|
||||
"" if self._is_management else f"{self.command_prefix} ")
|
||||
message = message.replace("$cmdprefix", self.command_prefix)
|
||||
html = None
|
||||
if render_markdown:
|
||||
html = markdown.markdown(message, safe_mode="escape" if allow_html else False)
|
||||
@@ -165,7 +175,7 @@ class CommandHandler:
|
||||
return self.reply("Incorrect password.")
|
||||
except:
|
||||
self.log.exception()
|
||||
return self.reply("Unhandled exception while sending password."
|
||||
return self.reply("Unhandled exception while sending password. "
|
||||
"Check console for more details.")
|
||||
|
||||
@command_handler
|
||||
@@ -181,11 +191,83 @@ class CommandHandler:
|
||||
|
||||
@command_handler
|
||||
def search(self, sender, args):
|
||||
self.reply("Not yet implemented.")
|
||||
if len(args) == 0:
|
||||
return self.reply("**Usage:** `$cmdprefix+sp search [-r|--remote] <query>")
|
||||
elif not sender.tgid:
|
||||
return self.reply("This command requires you to be logged in.")
|
||||
force_remote = False
|
||||
if args[0] in {"-r", "--remote"}:
|
||||
args.pop(0)
|
||||
query = " ".join(args)
|
||||
if len(query) < 5:
|
||||
return self.reply("Minimum length of query for remote search is 5 characters.")
|
||||
found = sender.client(SearchRequest(q=query, limit=10))
|
||||
print(found)
|
||||
# reply = ["**People:**", ""]
|
||||
reply = ["**Results from Telegram server:**", ""]
|
||||
for result in found.users:
|
||||
puppet = pu.Puppet.get(result.id)
|
||||
puppet.update_info(sender, result)
|
||||
reply.append(
|
||||
f"* [{puppet.displayname}](https://matrix.to/#/{puppet.mxid}): {puppet.id}")
|
||||
# reply.extend(("", "**Chats:**", ""))
|
||||
# for result in found.chats:
|
||||
# reply.append(f"* {result.title}")
|
||||
return self.reply("\n".join(reply))
|
||||
|
||||
@command_handler
|
||||
def pm(self, sender, args):
|
||||
self.reply("Not yet implemented.")
|
||||
if len(args) == 0:
|
||||
return self.reply("**Usage:** `$cmdprefix+sp pm <user identifier>")
|
||||
elif not sender.tgid:
|
||||
return self.reply("This command requires you to be logged in.")
|
||||
|
||||
user = sender.client.get_entity(args[0])
|
||||
if not user:
|
||||
return self.reply("User not found.")
|
||||
elif not isinstance(user, User):
|
||||
return self.reply("That doesn't seem to be a user.")
|
||||
print(user)
|
||||
|
||||
def _strip_prefix(self, value, prefixes):
|
||||
for prefix in prefixes:
|
||||
if value.startswith(prefix):
|
||||
return value[len(prefix):]
|
||||
return value
|
||||
|
||||
@command_handler
|
||||
def join(self, sender, args):
|
||||
if len(args) == 0:
|
||||
return self.reply("**Usage:** `$cmdprefix+sp join <invite link>")
|
||||
elif not sender.tgid:
|
||||
return self.reply("This command requires you to be logged in.")
|
||||
|
||||
regex = re.compile(r"(?:https?://)?t(?:elegram)?\.(?:dog|me)(?:joinchat/)?/(.+)")
|
||||
arg = regex.match(args[0])
|
||||
if not arg:
|
||||
return self.reply("That doesn't look like a Telegram invite link.")
|
||||
arg = arg.group(1)
|
||||
if arg.startswith("joinchat/"):
|
||||
invite_hash = arg[len("joinchat/"):]
|
||||
try:
|
||||
check = sender.client(CheckChatInviteRequest(invite_hash))
|
||||
print(check)
|
||||
except InviteHashInvalidError:
|
||||
return self.reply("Invalid invite link.")
|
||||
except InviteHashExpiredError:
|
||||
return self.reply("Invite link expired.")
|
||||
try:
|
||||
updates = sender.client(ImportChatInviteRequest(invite_hash))
|
||||
except UserAlreadyParticipantError:
|
||||
return self.reply("You are already in that chat.")
|
||||
else:
|
||||
channel = sender.client.get_entity(arg)
|
||||
if not channel:
|
||||
return self.reply("Channel/supergroup not found.")
|
||||
updates = sender.client(JoinChannelRequest(channel))
|
||||
for chat in updates.chats:
|
||||
portal = po.Portal.get_by_entity(chat)
|
||||
portal.create_room(sender, chat, [sender.mxid])
|
||||
|
||||
@command_handler
|
||||
def create(self, sender, args):
|
||||
@@ -235,9 +317,12 @@ _**Telegram actions**: commands for using the bridge to interact with Telegram._
|
||||
**logout** - Log out from Telegram.
|
||||
**ping** - Check if you're logged into Telegram.
|
||||
**search** [_-r|--remote_] <_query_> - Search your contacts or the Telegram servers for users.
|
||||
**pm** <_id_> - Open a private chat with the given Telegram user ID.
|
||||
**create** <_group/channel_> [_room ID_] - Create a Telegram chat of the given type for a Matrix room.
|
||||
If the room ID is not specified, a chat for the current room is created.
|
||||
**pm** <_identifier_> - Open a private chat with the given Telegram user. The identifier is either
|
||||
the internal user ID, the username or the phone number.
|
||||
**join** <_link_> - Join a chat with an invite link.
|
||||
**create** <_group/channel_> [_room ID_] - Create a Telegram chat of the given type for a Matrix
|
||||
room. If the room ID is not specified, a chat for the
|
||||
current room is created.
|
||||
**upgrade** - Upgrade a normal Telegram group to a supergroup.
|
||||
"""
|
||||
return self.reply(management_status + help)
|
||||
|
||||
@@ -98,28 +98,25 @@ class Portal:
|
||||
puppet = p.Puppet.get(self.tgid) if direct else None
|
||||
intent = puppet.intent if direct else self.az.intent
|
||||
|
||||
power_level_requirement = 0 if self.peer_type == "chat" else 50
|
||||
initial_power_levels = {
|
||||
"ban": 100,
|
||||
"events": {
|
||||
"m.room.name": power_level_requirement,
|
||||
"m.room.avatar": power_level_requirement,
|
||||
"m.room.topic": 50,
|
||||
"m.room.power_levels": 50,
|
||||
"invite": power_level_requirement,
|
||||
},
|
||||
"users_default": 0,
|
||||
}
|
||||
|
||||
# TODO set room alias if public channel.
|
||||
room = intent.create_room(invitees=invites, name=title, is_direct=direct,
|
||||
initial_state=[initial_power_levels])
|
||||
room = intent.create_room(invitees=invites, name=title, is_direct=direct)
|
||||
if not room:
|
||||
raise Exception(f"Failed to create room for {self.tgid}")
|
||||
|
||||
self.mxid = room["room_id"]
|
||||
self.by_mxid[self.mxid] = self
|
||||
self.save()
|
||||
|
||||
power_level_requirement = 0 if self.peer_type == "chat" else 50
|
||||
levels = self.main_intent.get_power_levels(self.mxid)
|
||||
levels["ban"] = 100
|
||||
levels["invite"] = 50
|
||||
levels["events"]["m.room.name"] = power_level_requirement
|
||||
levels["events"]["m.room.avatar"] = power_level_requirement
|
||||
levels["events"]["m.room.topic"] = 50 if self.peer_type == "channel" else 100
|
||||
levels["events"]["m.room.power_levels"] = 95
|
||||
self.main_intent.set_power_levels(self.mxid, levels)
|
||||
|
||||
if not direct:
|
||||
self.update_info(user, entity)
|
||||
users, participants = self.get_users(user, entity)
|
||||
@@ -397,11 +394,11 @@ class Portal:
|
||||
|
||||
def handle_telegram_action(self, source, sender, action):
|
||||
if not self.mxid:
|
||||
create_and_exit = [MessageActionChatCreate, MessageActionChannelCreate]
|
||||
create_and_continue = [MessageActionChatAddUser, MessageActionChatJoinedByLink]
|
||||
create_and_exit = (MessageActionChatCreate, MessageActionChannelCreate)
|
||||
create_and_continue = (MessageActionChatAddUser, MessageActionChatJoinedByLink)
|
||||
if isinstance(action, create_and_exit + create_and_continue):
|
||||
self.create_room(source, invites=[source.mxid])
|
||||
if isinstance(action, create_and_exit):
|
||||
if not isinstance(action, create_and_continue):
|
||||
return
|
||||
|
||||
if isinstance(action, MessageActionChatEditTitle):
|
||||
|
||||
@@ -29,7 +29,8 @@ class Puppet:
|
||||
def __init__(self, id=None, username=None, displayname=None, photo_id=None):
|
||||
self.id = id
|
||||
|
||||
self.localpart = config.get("bridge.username_template", "telegram_{userid}").format(userid=self.id)
|
||||
self.localpart = config.get("bridge.username_template", "telegram_{userid}").format(
|
||||
userid=self.id)
|
||||
hs = config["homeserver"]["domain"]
|
||||
self.mxid = f"@{self.localpart}:{hs}"
|
||||
self.username = username
|
||||
@@ -75,7 +76,8 @@ class Puppet:
|
||||
|
||||
if not format:
|
||||
return name
|
||||
return config.get("bridge.displayname_template", "{displayname} (Telegram)").format(displayname=name)
|
||||
return config.get("bridge.displayname_template", "{displayname} (Telegram)").format(
|
||||
displayname=name)
|
||||
|
||||
def update_info(self, source, info):
|
||||
changed = False
|
||||
|
||||
@@ -97,9 +97,11 @@ class User:
|
||||
self.connected = False
|
||||
if self.tgid:
|
||||
try:
|
||||
del self.tgid[self.tgid]
|
||||
del self.by_tgid[self.tgid]
|
||||
except KeyError:
|
||||
pass
|
||||
self.tgid = None
|
||||
self.save()
|
||||
return self.client.log_out()
|
||||
|
||||
def send_message(self, entity, message, reply_to=None, entities=None, link_preview=True):
|
||||
|
||||
Reference in New Issue
Block a user