Handle incoming messages from bot
This commit is contained in:
@@ -17,10 +17,12 @@
|
|||||||
import platform
|
import platform
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .tgclient import MautrixTelegramClient
|
|
||||||
from . import __version__
|
|
||||||
from telethon.tl.types import *
|
from telethon.tl.types import *
|
||||||
|
|
||||||
|
from .tgclient import MautrixTelegramClient
|
||||||
|
from .db import Message as DBMessage
|
||||||
|
from . import portal as po, puppet as pu, __version__
|
||||||
|
|
||||||
config = None
|
config = None
|
||||||
|
|
||||||
|
|
||||||
@@ -50,14 +52,15 @@ class AbstractUser:
|
|||||||
self.client.add_update_handler(self._update_catch)
|
self.client.add_update_handler(self._update_catch)
|
||||||
|
|
||||||
async def update(self, update):
|
async def update(self, update):
|
||||||
raise NotImplementedError()
|
return False
|
||||||
|
|
||||||
async def post_login(self):
|
async def post_login(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def _update_catch(self, update):
|
async def _update_catch(self, update):
|
||||||
try:
|
try:
|
||||||
await self.update(update)
|
if not await self.update(update):
|
||||||
|
await self._update(update)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log.exception("Failed to handle Telegram update")
|
self.log.exception("Failed to handle Telegram update")
|
||||||
|
|
||||||
@@ -95,6 +98,134 @@ class AbstractUser:
|
|||||||
self.client = None
|
self.client = None
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
|
# region Telegram update handling
|
||||||
|
|
||||||
|
async def _update(self, update):
|
||||||
|
if isinstance(update,
|
||||||
|
(UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage,
|
||||||
|
UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage)):
|
||||||
|
await self.update_message(update)
|
||||||
|
elif isinstance(update, (UpdateChatUserTyping, UpdateUserTyping)):
|
||||||
|
await self.update_typing(update)
|
||||||
|
elif isinstance(update, UpdateUserStatus):
|
||||||
|
await self.update_status(update)
|
||||||
|
elif isinstance(update, (UpdateChatAdmins, UpdateChatParticipantAdmin)):
|
||||||
|
await self.update_admin(update)
|
||||||
|
elif isinstance(update, UpdateChatParticipants):
|
||||||
|
portal = po.Portal.get_by_tgid(update.participants.chat_id)
|
||||||
|
if portal and portal.mxid:
|
||||||
|
await portal.update_telegram_participants(update.participants.participants)
|
||||||
|
elif isinstance(update, UpdateChannelPinnedMessage):
|
||||||
|
portal = po.Portal.get_by_tgid(update.channel_id)
|
||||||
|
if portal and portal.mxid:
|
||||||
|
await portal.update_telegram_pin(self, update.id)
|
||||||
|
elif isinstance(update, (UpdateUserName, UpdateUserPhoto)):
|
||||||
|
await self.update_others_info(update)
|
||||||
|
elif isinstance(update, UpdateReadHistoryOutbox):
|
||||||
|
await self.update_read_receipt(update)
|
||||||
|
else:
|
||||||
|
self.log.debug("Unhandled update: %s", update)
|
||||||
|
|
||||||
|
async def update_read_receipt(self, update):
|
||||||
|
if not isinstance(update.peer, PeerUser):
|
||||||
|
self.log.debug("Unexpected read receipt peer: %s", update.peer)
|
||||||
|
return
|
||||||
|
|
||||||
|
portal = po.Portal.get_by_tgid(update.peer.user_id, self.tgid)
|
||||||
|
if not portal or not portal.mxid:
|
||||||
|
return
|
||||||
|
|
||||||
|
# We check that these are user read receipts, so tg_space is always the user ID.
|
||||||
|
message = DBMessage.query.get((update.max_id, self.tgid))
|
||||||
|
if not message:
|
||||||
|
return
|
||||||
|
|
||||||
|
puppet = pu.Puppet.get(update.peer.user_id)
|
||||||
|
await puppet.intent.mark_read(portal.mxid, message.mxid)
|
||||||
|
|
||||||
|
async def update_admin(self, update):
|
||||||
|
portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat")
|
||||||
|
if isinstance(update, UpdateChatAdmins):
|
||||||
|
await portal.set_telegram_admins_enabled(update.enabled)
|
||||||
|
elif isinstance(update, UpdateChatParticipantAdmin):
|
||||||
|
await portal.set_telegram_admin(update.user_id)
|
||||||
|
else:
|
||||||
|
self.log.warninng("Unexpected admin status update: %s", update)
|
||||||
|
|
||||||
|
async def update_typing(self, update):
|
||||||
|
if isinstance(update, UpdateUserTyping):
|
||||||
|
portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user")
|
||||||
|
else:
|
||||||
|
portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat")
|
||||||
|
sender = pu.Puppet.get(update.user_id)
|
||||||
|
await portal.handle_telegram_typing(sender, update)
|
||||||
|
|
||||||
|
async def update_others_info(self, update):
|
||||||
|
puppet = pu.Puppet.get(update.user_id)
|
||||||
|
if isinstance(update, UpdateUserName):
|
||||||
|
if await puppet.update_displayname(self, update):
|
||||||
|
puppet.save()
|
||||||
|
elif isinstance(update, UpdateUserPhoto):
|
||||||
|
if await puppet.update_avatar(self, update.photo.photo_big):
|
||||||
|
puppet.save()
|
||||||
|
else:
|
||||||
|
self.log.warninng("Unexpected other user info update: %s", update)
|
||||||
|
|
||||||
|
async def update_status(self, update):
|
||||||
|
puppet = pu.Puppet.get(update.user_id)
|
||||||
|
if isinstance(update.status, UserStatusOnline):
|
||||||
|
await puppet.intent.set_presence("online")
|
||||||
|
elif isinstance(update.status, UserStatusOffline):
|
||||||
|
await puppet.intent.set_presence("offline")
|
||||||
|
else:
|
||||||
|
self.log.warning("Unexpected user status update: %s", update)
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_message_details(self, update):
|
||||||
|
if isinstance(update, UpdateShortChatMessage):
|
||||||
|
portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat")
|
||||||
|
sender = pu.Puppet.get(update.from_id)
|
||||||
|
elif isinstance(update, UpdateShortMessage):
|
||||||
|
portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user")
|
||||||
|
sender = pu.Puppet.get(self.tgid if update.out else update.user_id)
|
||||||
|
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage,
|
||||||
|
UpdateEditMessage, UpdateEditChannelMessage)):
|
||||||
|
update = update.message
|
||||||
|
if isinstance(update.to_id, PeerUser) and not update.out:
|
||||||
|
portal = po.Portal.get_by_tgid(update.from_id, peer_type="user",
|
||||||
|
tg_receiver=self.tgid)
|
||||||
|
else:
|
||||||
|
portal = po.Portal.get_by_entity(update.to_id, receiver_id=self.tgid)
|
||||||
|
sender = pu.Puppet.get(update.from_id) if update.from_id else None
|
||||||
|
else:
|
||||||
|
self.log.warning(
|
||||||
|
f"Unexpected message type in User#get_message_details: {type(update)}")
|
||||||
|
return update, None, None
|
||||||
|
return update, sender, portal
|
||||||
|
|
||||||
|
def update_message(self, original_update):
|
||||||
|
update, sender, portal = self.get_message_details(original_update)
|
||||||
|
|
||||||
|
if isinstance(update, MessageService):
|
||||||
|
if isinstance(update.action, MessageActionChannelMigrateFrom):
|
||||||
|
self.log.debug(f"Ignoring action %s to %s by %d", update.action,
|
||||||
|
portal.tgid_log,
|
||||||
|
sender.id)
|
||||||
|
return
|
||||||
|
self.log.debug("Handling action %s to %s by %d", update.action, portal.tgid_log,
|
||||||
|
sender.id)
|
||||||
|
return portal.handle_telegram_action(self, sender, update.action)
|
||||||
|
|
||||||
|
user = sender.tgid if sender else "admin"
|
||||||
|
if isinstance(original_update, (UpdateEditMessage, UpdateEditChannelMessage)):
|
||||||
|
self.log.debug("Handling edit %s to %s by %s", update, portal.tgid_log, user)
|
||||||
|
return portal.handle_telegram_edit(self, sender, update)
|
||||||
|
|
||||||
|
self.log.debug("Handling message %s to %s by %s", update, portal.tgid_log, user)
|
||||||
|
return portal.handle_telegram_message(self, sender, update)
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
def init(context):
|
def init(context):
|
||||||
global config
|
global config
|
||||||
|
|||||||
+13
-11
@@ -35,7 +35,7 @@ class Bot(AbstractUser):
|
|||||||
self.token = token
|
self.token = token
|
||||||
self.whitelisted = True
|
self.whitelisted = True
|
||||||
self._init_client()
|
self._init_client()
|
||||||
self.chats = {(chat.id, chat.type) for chat in BotChat.query.all()}
|
self.chats = {chat.id: chat.type for chat in BotChat.query.all()}
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
await super().start()
|
await super().start()
|
||||||
@@ -48,30 +48,32 @@ class Bot(AbstractUser):
|
|||||||
info = await self.client.get_me()
|
info = await self.client.get_me()
|
||||||
self.tgid = info.id
|
self.tgid = info.id
|
||||||
|
|
||||||
chat_ids = [id for (id, type) in self.chats if type == "chat"]
|
chat_ids = [id for id, type in self.chats.items() if type == "chat"]
|
||||||
response = await self.client(GetChatsRequest(chat_ids))
|
response = await self.client(GetChatsRequest(chat_ids))
|
||||||
for chat in response.chats:
|
for chat in response.chats:
|
||||||
if isinstance(chat, ChatForbidden) or chat.left or chat.deactivated:
|
if isinstance(chat, ChatForbidden) or chat.left or chat.deactivated:
|
||||||
self.remove_chat(chat.id, "chat")
|
self.remove_chat(chat.id)
|
||||||
|
|
||||||
channel_ids = [InputChannel(id, 0)
|
channel_ids = [InputChannel(id, 0)
|
||||||
for (id, type) in self.chats
|
for id, type in self.chats.items()
|
||||||
if type == "channel"]
|
if type == "channel"]
|
||||||
for id in channel_ids:
|
for id in channel_ids:
|
||||||
try:
|
try:
|
||||||
await self.client(GetChannelsRequest([id]))
|
await self.client(GetChannelsRequest([id]))
|
||||||
except (ChannelPrivateError, ChannelInvalidError):
|
except (ChannelPrivateError, ChannelInvalidError):
|
||||||
self.remove_chat(id.channel_id, "channel")
|
self.remove_chat(id.channel_id)
|
||||||
|
|
||||||
def add_chat(self, id, type):
|
def add_chat(self, id, type):
|
||||||
entry = (id, type)
|
if id not in self.chats:
|
||||||
if entry not in self.chats:
|
self.chats[id] = type
|
||||||
self.chats.add(entry)
|
|
||||||
self.db.add(BotChat(id=id, type=type))
|
self.db.add(BotChat(id=id, type=type))
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def remove_chat(self, id, type):
|
def remove_chat(self, id):
|
||||||
self.chats.remove((id, type))
|
try:
|
||||||
|
del self.chats[id]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
self.db.delete(BotChat.query.get(id))
|
self.db.delete(BotChat.query.get(id))
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ class Bot(AbstractUser):
|
|||||||
self.add_chat(to_id, type)
|
self.add_chat(to_id, type)
|
||||||
elif isinstance(action, MessageActionChatDeleteUser):
|
elif isinstance(action, MessageActionChatDeleteUser):
|
||||||
if action.user_id == self.tgid:
|
if action.user_id == self.tgid:
|
||||||
self.remove_chat(to_id, type)
|
self.remove_chat(to_id)
|
||||||
|
|
||||||
def is_in_chat(self, peer_id):
|
def is_in_chat(self, peer_id):
|
||||||
return peer_id in self.chats
|
return peer_id in self.chats
|
||||||
|
|||||||
@@ -169,10 +169,12 @@ class MatrixHandler:
|
|||||||
|
|
||||||
is_command, text = self.is_command(message)
|
is_command, text = self.is_command(message)
|
||||||
sender = await User.get_by_mxid(sender).ensure_started()
|
sender = await User.get_by_mxid(sender).ensure_started()
|
||||||
|
print(sender, sender.whitelisted)
|
||||||
if not sender.whitelisted:
|
if not sender.whitelisted:
|
||||||
return
|
return
|
||||||
|
|
||||||
portal = Portal.get_by_mxid(room)
|
portal = Portal.get_by_mxid(room)
|
||||||
|
print(is_command, portal, sender.logged_in, portal.has_bot)
|
||||||
if not is_command and portal and (sender.logged_in or portal.has_bot):
|
if not is_command and portal and (sender.logged_in or portal.has_bot):
|
||||||
await portal.handle_matrix_message(sender, message, event_id)
|
await portal.handle_matrix_message(sender, message, event_id)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class Portal:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_bot(self):
|
def has_bot(self):
|
||||||
|
print("BOT PRINT", self.bot)
|
||||||
return self.bot and self.bot.is_in_chat(self.tgid)
|
return self.bot and self.bot.is_in_chat(self.tgid)
|
||||||
|
|
||||||
def _hash_event(self, event):
|
def _hash_event(self, event):
|
||||||
@@ -934,7 +935,10 @@ class Portal:
|
|||||||
else:
|
else:
|
||||||
self.log.debug("Unhandled Telegram action in %s: %s", self.title, action)
|
self.log.debug("Unhandled Telegram action in %s: %s", self.title, action)
|
||||||
|
|
||||||
async def set_telegram_admin(self, puppet, user):
|
async def set_telegram_admin(self, user_id):
|
||||||
|
puppet = p.Puppet.get(user_id)
|
||||||
|
user = await u.User.get_by_tgid(user_id)
|
||||||
|
|
||||||
levels = await self.main_intent.get_power_levels(self.mxid)
|
levels = await self.main_intent.get_power_levels(self.mxid)
|
||||||
if user:
|
if user:
|
||||||
levels["users"][user.mxid] = 50
|
levels["users"][user.mxid] = 50
|
||||||
|
|||||||
+1
-124
@@ -23,7 +23,7 @@ from telethon.tl.types.contacts import ContactsNotModified
|
|||||||
from telethon.tl.functions.contacts import GetContactsRequest, SearchRequest
|
from telethon.tl.functions.contacts import GetContactsRequest, SearchRequest
|
||||||
from mautrix_appservice import MatrixRequestError
|
from mautrix_appservice import MatrixRequestError
|
||||||
|
|
||||||
from .db import User as DBUser, Message as DBMessage, Contact as DBContact
|
from .db import User as DBUser, Contact as DBContact
|
||||||
from .abstract_user import AbstractUser
|
from .abstract_user import AbstractUser
|
||||||
from . import portal as po, puppet as pu
|
from . import portal as po, puppet as pu
|
||||||
|
|
||||||
@@ -260,129 +260,6 @@ class User(AbstractUser):
|
|||||||
self.contacts.append(puppet)
|
self.contacts.append(puppet)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
# endregion
|
|
||||||
# region Telegram update handling
|
|
||||||
|
|
||||||
async def update(self, update):
|
|
||||||
if isinstance(update, (UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage,
|
|
||||||
UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage)):
|
|
||||||
await self.update_message(update)
|
|
||||||
elif isinstance(update, (UpdateChatUserTyping, UpdateUserTyping)):
|
|
||||||
await self.update_typing(update)
|
|
||||||
elif isinstance(update, UpdateUserStatus):
|
|
||||||
await self.update_status(update)
|
|
||||||
elif isinstance(update, (UpdateChatAdmins, UpdateChatParticipantAdmin)):
|
|
||||||
await self.update_admin(update)
|
|
||||||
elif isinstance(update, UpdateChatParticipants):
|
|
||||||
portal = po.Portal.get_by_tgid(update.participants.chat_id)
|
|
||||||
if portal and portal.mxid:
|
|
||||||
await portal.update_telegram_participants(update.participants.participants)
|
|
||||||
elif isinstance(update, UpdateChannelPinnedMessage):
|
|
||||||
portal = po.Portal.get_by_tgid(update.channel_id)
|
|
||||||
if portal and portal.mxid:
|
|
||||||
await portal.update_telegram_pin(self, update.id)
|
|
||||||
elif isinstance(update, (UpdateUserName, UpdateUserPhoto)):
|
|
||||||
await self.update_others_info(update)
|
|
||||||
elif isinstance(update, UpdateReadHistoryOutbox):
|
|
||||||
await self.update_read_receipt(update)
|
|
||||||
else:
|
|
||||||
self.log.debug("Unhandled update: %s", update)
|
|
||||||
|
|
||||||
async def update_read_receipt(self, update):
|
|
||||||
if not isinstance(update.peer, PeerUser):
|
|
||||||
self.log.debug("Unexpected read receipt peer: %s", update.peer)
|
|
||||||
return
|
|
||||||
|
|
||||||
portal = po.Portal.get_by_tgid(update.peer.user_id, self.tgid)
|
|
||||||
if not portal or not portal.mxid:
|
|
||||||
return
|
|
||||||
|
|
||||||
# We check that these are user read receipts, so tg_space is always the user ID.
|
|
||||||
message = DBMessage.query.get((update.max_id, self.tgid))
|
|
||||||
if not message:
|
|
||||||
return
|
|
||||||
|
|
||||||
puppet = pu.Puppet.get(update.peer.user_id)
|
|
||||||
await puppet.intent.mark_read(portal.mxid, message.mxid)
|
|
||||||
|
|
||||||
async def update_admin(self, update):
|
|
||||||
portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat")
|
|
||||||
if isinstance(update, UpdateChatAdmins):
|
|
||||||
await portal.set_telegram_admins_enabled(update.enabled)
|
|
||||||
elif isinstance(update, UpdateChatParticipantAdmin):
|
|
||||||
puppet = pu.Puppet.get(update.user_id)
|
|
||||||
user = await User.get_by_tgid(update.user_id).ensure_started()
|
|
||||||
await portal.set_telegram_admin(puppet, user)
|
|
||||||
|
|
||||||
async def update_typing(self, update):
|
|
||||||
if isinstance(update, UpdateUserTyping):
|
|
||||||
portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user")
|
|
||||||
else:
|
|
||||||
portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat")
|
|
||||||
sender = pu.Puppet.get(update.user_id)
|
|
||||||
await portal.handle_telegram_typing(sender, update)
|
|
||||||
|
|
||||||
async def update_others_info(self, update):
|
|
||||||
puppet = pu.Puppet.get(update.user_id)
|
|
||||||
if isinstance(update, UpdateUserName):
|
|
||||||
if await puppet.update_displayname(self, update):
|
|
||||||
puppet.save()
|
|
||||||
elif isinstance(update, UpdateUserPhoto):
|
|
||||||
if await puppet.update_avatar(self, update.photo.photo_big):
|
|
||||||
puppet.save()
|
|
||||||
|
|
||||||
async def update_status(self, update):
|
|
||||||
puppet = pu.Puppet.get(update.user_id)
|
|
||||||
if isinstance(update.status, UserStatusOnline):
|
|
||||||
await puppet.intent.set_presence("online")
|
|
||||||
elif isinstance(update.status, UserStatusOffline):
|
|
||||||
await puppet.intent.set_presence("offline")
|
|
||||||
else:
|
|
||||||
self.log.warning("Unexpected user status update: %s", update)
|
|
||||||
return
|
|
||||||
|
|
||||||
def get_message_details(self, update):
|
|
||||||
if isinstance(update, UpdateShortChatMessage):
|
|
||||||
portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat")
|
|
||||||
sender = pu.Puppet.get(update.from_id)
|
|
||||||
elif isinstance(update, UpdateShortMessage):
|
|
||||||
portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user")
|
|
||||||
sender = pu.Puppet.get(self.tgid if update.out else update.user_id)
|
|
||||||
elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage,
|
|
||||||
UpdateEditMessage, UpdateEditChannelMessage)):
|
|
||||||
update = update.message
|
|
||||||
if isinstance(update.to_id, PeerUser) and not update.out:
|
|
||||||
portal = po.Portal.get_by_tgid(update.from_id, peer_type="user",
|
|
||||||
tg_receiver=self.tgid)
|
|
||||||
else:
|
|
||||||
portal = po.Portal.get_by_entity(update.to_id, receiver_id=self.tgid)
|
|
||||||
sender = pu.Puppet.get(update.from_id) if update.from_id else None
|
|
||||||
else:
|
|
||||||
self.log.warning(
|
|
||||||
f"Unexpected message type in User#get_message_details: {type(update)}")
|
|
||||||
return update, None, None
|
|
||||||
return update, sender, portal
|
|
||||||
|
|
||||||
def update_message(self, original_update):
|
|
||||||
update, sender, portal = self.get_message_details(original_update)
|
|
||||||
|
|
||||||
if isinstance(update, MessageService):
|
|
||||||
if isinstance(update.action, MessageActionChannelMigrateFrom):
|
|
||||||
self.log.debug(f"Ignoring action %s to %s by %d", update.action, portal.tgid_log,
|
|
||||||
sender.id)
|
|
||||||
return
|
|
||||||
self.log.debug("Handling action %s to %s by %d", update.action, portal.tgid_log,
|
|
||||||
sender.id)
|
|
||||||
return portal.handle_telegram_action(self, sender, update.action)
|
|
||||||
|
|
||||||
user = sender.tgid if sender else "admin"
|
|
||||||
if isinstance(original_update, (UpdateEditMessage, UpdateEditChannelMessage)):
|
|
||||||
self.log.debug("Handling edit %s to %s by %s", update, portal.tgid_log, user)
|
|
||||||
return portal.handle_telegram_edit(self, sender, update)
|
|
||||||
|
|
||||||
self.log.debug("Handling message %s to %s by %s", update, portal.tgid_log, user)
|
|
||||||
return portal.handle_telegram_message(self, sender, update)
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region Class instance lookup
|
# region Class instance lookup
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user