Clean up code
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# 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/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from typing import Coroutine, List
|
from typing import Awaitable, List, Any
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging.config
|
import logging.config
|
||||||
@@ -88,7 +88,7 @@ if config["appservice.sqlalchemy_core_mode"]:
|
|||||||
|
|
||||||
loop = asyncio.get_event_loop() # type: asyncio.AbstractEventLoop
|
loop = asyncio.get_event_loop() # type: asyncio.AbstractEventLoop
|
||||||
|
|
||||||
state_store = SQLStateStore(db_session)
|
state_store = SQLStateStore()
|
||||||
mebibyte = 1024 ** 2
|
mebibyte = 1024 ** 2
|
||||||
appserv = AppService(config["homeserver.address"], config["homeserver.domain"],
|
appserv = AppService(config["homeserver.address"], config["homeserver.domain"],
|
||||||
config["appservice.as_token"], config["appservice.hs_token"],
|
config["appservice.as_token"], config["appservice.hs_token"],
|
||||||
@@ -121,8 +121,7 @@ with appserv.run(config["appservice.hostname"], config["appservice.port"]) as st
|
|||||||
init_portal(context)
|
init_portal(context)
|
||||||
startup_actions = (init_puppet(context) +
|
startup_actions = (init_puppet(context) +
|
||||||
init_user(context) +
|
init_user(context) +
|
||||||
[start,
|
[start, context.mx.init_as_bot()]) # type: List[Awaitable[Any]]
|
||||||
context.mx.init_as_bot()]) # type: List[Coroutine]
|
|
||||||
|
|
||||||
if context.bot:
|
if context.bot:
|
||||||
startup_actions.append(context.bot.start())
|
startup_actions.append(context.bot.start())
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ import logging
|
|||||||
import platform
|
import platform
|
||||||
|
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
from telethon.tl.types import Channel, ChannelForbidden, Chat, ChatForbidden, Message, \
|
from telethon.tl.patched import MessageService, Message
|
||||||
MessageActionChannelMigrateFrom, MessageService, PeerUser, TypeUpdate, \
|
from telethon.tl.types import (
|
||||||
UpdateChannelPinnedMessage, UpdateChatAdmins, UpdateChatParticipantAdmin, \
|
Channel, ChannelForbidden, Chat, ChatForbidden, MessageActionChannelMigrateFrom, PeerUser,
|
||||||
UpdateChatParticipants, UpdateChatUserTyping, UpdateDeleteChannelMessages, \
|
TypeUpdate, UpdateChannelPinnedMessage, UpdateChatAdmins, UpdateChatParticipantAdmin,
|
||||||
UpdateDeleteMessages, UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, \
|
UpdateChatParticipants, UpdateChatUserTyping, UpdateDeleteChannelMessages, UpdateDeleteMessages,
|
||||||
UpdateNewMessage, UpdateReadHistoryOutbox, UpdateShortChatMessage, UpdateShortMessage, \
|
UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, UpdateNewMessage,
|
||||||
UpdateUserName, UpdateUserPhoto, UpdateUserStatus, UpdateUserTyping, User, UserStatusOffline, \
|
UpdateReadHistoryOutbox, UpdateShortChatMessage, UpdateShortMessage, UpdateUserName,
|
||||||
UserStatusOnline
|
UpdateUserPhoto, UpdateUserStatus, UpdateUserTyping, User, UserStatusOffline, UserStatusOnline)
|
||||||
|
|
||||||
from mautrix_appservice import MatrixRequestError, AppService
|
from mautrix_appservice import MatrixRequestError, AppService
|
||||||
from alchemysession import AlchemySessionContainer
|
from alchemysession import AlchemySessionContainer
|
||||||
@@ -231,7 +231,7 @@ class AbstractUser(ABC):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# We check that these are user read receipts, so tg_space is always the user ID.
|
# We check that these are user read receipts, so tg_space is always the user ID.
|
||||||
message = DBMessage.get_by_tgid(update.max_id, self.tgid)
|
message = DBMessage.get_by_tgid(TelegramID(update.max_id), self.tgid)
|
||||||
if not message:
|
if not message:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ from typing import Awaitable, Callable, Dict, List, Optional, Pattern, TYPE_CHEC
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from telethon.tl.patched import Message, MessageService
|
||||||
from telethon.tl.types import (
|
from telethon.tl.types import (
|
||||||
ChannelParticipantAdmin, ChannelParticipantCreator, ChatForbidden, ChatParticipantAdmin,
|
ChannelParticipantAdmin, ChannelParticipantCreator, ChatForbidden, ChatParticipantAdmin,
|
||||||
ChatParticipantCreator, InputChannel, InputUser, Message, MessageActionChatAddUser,
|
ChatParticipantCreator, InputChannel, InputUser, MessageActionChatAddUser,
|
||||||
MessageActionChatDeleteUser, MessageEntityBotCommand, MessageService, PeerChannel, PeerChat,
|
MessageActionChatDeleteUser, MessageEntityBotCommand, PeerChannel, PeerChat, TypePeer,
|
||||||
TypePeer, UpdateNewChannelMessage, UpdateNewMessage)
|
UpdateNewChannelMessage, UpdateNewMessage)
|
||||||
from telethon.tl.functions.messages import GetChatsRequest, GetFullChatRequest
|
from telethon.tl.functions.messages import GetChatsRequest, GetFullChatRequest
|
||||||
from telethon.tl.functions.channels import GetChannelsRequest, GetParticipantRequest
|
from telethon.tl.functions.channels import GetChannelsRequest, GetParticipantRequest
|
||||||
from telethon.errors import ChannelInvalidError, ChannelPrivateError
|
from telethon.errors import ChannelInvalidError, ChannelPrivateError
|
||||||
@@ -30,6 +31,7 @@ from telethon.errors import ChannelInvalidError, ChannelPrivateError
|
|||||||
from .types import MatrixUserID
|
from .types import MatrixUserID
|
||||||
from .abstract_user import AbstractUser
|
from .abstract_user import AbstractUser
|
||||||
from .db import BotChat
|
from .db import BotChat
|
||||||
|
from .types import TelegramID
|
||||||
from . import puppet as pu, portal as po, user as u
|
from . import puppet as pu, portal as po, user as u
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -115,7 +117,7 @@ class Bot(AbstractUser):
|
|||||||
def add_chat(self, chat_id: int, chat_type: str) -> None:
|
def add_chat(self, chat_id: int, chat_type: str) -> None:
|
||||||
if chat_id not in self.chats:
|
if chat_id not in self.chats:
|
||||||
self.chats[chat_id] = chat_type
|
self.chats[chat_id] = chat_type
|
||||||
self.db.add(BotChat(id=chat_id, type=chat_type))
|
self.db.add(BotChat(id=TelegramID(chat_id), type=chat_type))
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
def remove_chat(self, chat_id: int) -> None:
|
def remove_chat(self, chat_id: int) -> None:
|
||||||
@@ -155,7 +157,7 @@ class Bot(AbstractUser):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def handle_command_portal(self, portal: po.Portal, reply: ReplyFunc) -> None:
|
async def handle_command_portal(self, portal: po.Portal, reply: ReplyFunc) -> Message:
|
||||||
if not config["bridge.relaybot.authless_portals"]:
|
if not config["bridge.relaybot.authless_portals"]:
|
||||||
return await reply("This bridge doesn't allow portal creation from Telegram.")
|
return await reply("This bridge doesn't allow portal creation from Telegram.")
|
||||||
|
|
||||||
@@ -221,7 +223,8 @@ class Bot(AbstractUser):
|
|||||||
text = message.message
|
text = message.message
|
||||||
|
|
||||||
if self.match_command(text, "id"):
|
if self.match_command(text, "id"):
|
||||||
return await self.handle_command_id(message, reply)
|
await self.handle_command_id(message, reply)
|
||||||
|
return
|
||||||
|
|
||||||
portal = po.Portal.get_by_entity(message.to_id)
|
portal = po.Portal.get_by_entity(message.to_id)
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from telethon.tl.functions.account import UpdateUsernameRequest
|
|||||||
|
|
||||||
from . import command_handler, CommandEvent, SECTION_AUTH
|
from . import command_handler, CommandEvent, SECTION_AUTH
|
||||||
from .. import puppet as pu, user as u
|
from .. import puppet as pu, user as u
|
||||||
from ..util import format_duration
|
from ..util import format_duration, ignore_coro
|
||||||
|
|
||||||
|
|
||||||
@command_handler(needs_auth=False,
|
@command_handler(needs_auth=False,
|
||||||
@@ -153,7 +153,7 @@ async def enter_code_register(evt: CommandEvent) -> Dict:
|
|||||||
await evt.sender.ensure_started(even_if_no_session=True)
|
await evt.sender.ensure_started(even_if_no_session=True)
|
||||||
first_name, last_name = evt.sender.command_status["full_name"]
|
first_name, last_name = evt.sender.command_status["full_name"]
|
||||||
user = await evt.sender.client.sign_up(evt.args[0], first_name, last_name)
|
user = await evt.sender.client.sign_up(evt.args[0], first_name, last_name)
|
||||||
asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop)
|
ignore_coro(asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop))
|
||||||
evt.sender.command_status = None
|
evt.sender.command_status = None
|
||||||
return await evt.reply(f"Successfully registered to Telegram.")
|
return await evt.reply(f"Successfully registered to Telegram.")
|
||||||
except PhoneNumberOccupiedError:
|
except PhoneNumberOccupiedError:
|
||||||
@@ -334,7 +334,7 @@ async def _sign_in(evt: CommandEvent, **sign_in_info) -> Dict:
|
|||||||
await evt.reply(f"[{existing_user.displayname}]"
|
await evt.reply(f"[{existing_user.displayname}]"
|
||||||
f"(https://matrix.to/#/{existing_user.mxid})"
|
f"(https://matrix.to/#/{existing_user.mxid})"
|
||||||
" was logged out from the account.")
|
" was logged out from the account.")
|
||||||
asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop)
|
ignore_coro(asyncio.ensure_future(evt.sender.post_login(user), loop=evt.loop))
|
||||||
evt.sender.command_status = None
|
evt.sender.command_status = None
|
||||||
name = f"@{user.username}" if user.username else f"+{user.phone}"
|
name = f"@{user.username}" if user.username else f"+{user.phone}"
|
||||||
return await evt.reply(f"Successfully logged in as {name}")
|
return await evt.reply(f"Successfully logged in as {name}")
|
||||||
|
|||||||
@@ -126,8 +126,7 @@ class CommandHandler:
|
|||||||
(not self.needs_admin or is_admin) and
|
(not self.needs_admin or is_admin) and
|
||||||
(not self.needs_auth or is_logged_in))
|
(not self.needs_auth or is_logged_in))
|
||||||
|
|
||||||
async def __call__(self, evt: CommandEvent
|
async def __call__(self, evt: CommandEvent) -> Dict:
|
||||||
) -> Dict:
|
|
||||||
error = await self.get_permission_error(evt)
|
error = await self.get_permission_error(evt)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
return await evt.reply(error)
|
return await evt.reply(error)
|
||||||
@@ -154,12 +153,11 @@ def command_handler(_func: Optional[Callable[[CommandEvent], Awaitable[Dict]]] =
|
|||||||
help_section: HelpSection = None
|
help_section: HelpSection = None
|
||||||
) -> Callable[[Callable[[CommandEvent], Awaitable[Optional[Dict]]]],
|
) -> Callable[[Callable[[CommandEvent], Awaitable[Optional[Dict]]]],
|
||||||
CommandHandler]:
|
CommandHandler]:
|
||||||
input_name = name
|
|
||||||
|
|
||||||
def decorator(func: Callable[[CommandEvent], Awaitable[Optional[Dict]]]) -> CommandHandler:
|
def decorator(func: Callable[[CommandEvent], Awaitable[Optional[Dict]]]) -> CommandHandler:
|
||||||
name = input_name or func.__name__.replace("_", "-")
|
actual_name = name or func.__name__.replace("_", "-")
|
||||||
handler = CommandHandler(func, needs_auth, needs_puppeting, needs_matrix_puppeting,
|
handler = CommandHandler(func, needs_auth, needs_puppeting, needs_matrix_puppeting,
|
||||||
needs_admin, management_only, name, help_text, help_args,
|
needs_admin, management_only, actual_name, help_text, help_args,
|
||||||
help_section)
|
help_section)
|
||||||
command_handlers[handler.name] = handler
|
command_handlers[handler.name] = handler
|
||||||
return handler
|
return handler
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ async def _get_help_text(evt: CommandEvent) -> str:
|
|||||||
help_sections.setdefault(handler.help_section, [])
|
help_sections.setdefault(handler.help_section, [])
|
||||||
help_sections[handler.help_section].append(handler.help + " ")
|
help_sections[handler.help_section].append(handler.help + " ")
|
||||||
help_sorted = sorted(help_sections.items(), key=lambda item: item[0].order)
|
help_sorted = sorted(help_sections.items(), key=lambda item: item[0].order)
|
||||||
help = ["#### {}\n{}\n".format(key.name, "\n".join(value)) for key, value in help_sorted]
|
helps = ["#### {}\n{}\n".format(key.name, "\n".join(value)) for key, value in help_sorted]
|
||||||
help_cache[cache_key] = "\n".join(help)
|
help_cache[cache_key] = "\n".join(helps)
|
||||||
return help_cache[cache_key]
|
return help_cache[cache_key]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from mautrix_appservice import MatrixRequestError, IntentAPI
|
|||||||
|
|
||||||
from ..types import MatrixRoomID, TelegramID
|
from ..types import MatrixRoomID, TelegramID
|
||||||
from ..config import yaml
|
from ..config import yaml
|
||||||
|
from ..util import ignore_coro
|
||||||
from .. import portal as po, user as u, util
|
from .. import portal as po, user as u, util
|
||||||
from . import (command_handler, CommandEvent,
|
from . import (command_handler, CommandEvent,
|
||||||
SECTION_ADMIN, SECTION_CREATING_PORTALS, SECTION_PORTAL_MANAGEMENT)
|
SECTION_ADMIN, SECTION_CREATING_PORTALS, SECTION_PORTAL_MANAGEMENT)
|
||||||
@@ -271,7 +272,7 @@ async def confirm_bridge(evt: CommandEvent) -> Optional[Dict]:
|
|||||||
if not ok:
|
if not ok:
|
||||||
return None
|
return None
|
||||||
elif coro:
|
elif coro:
|
||||||
asyncio.ensure_future(coro, loop=evt.loop)
|
ignore_coro(asyncio.ensure_future(coro, loop=evt.loop))
|
||||||
await evt.reply("Cleaning up previous portal room...")
|
await evt.reply("Cleaning up previous portal room...")
|
||||||
elif portal.mxid:
|
elif portal.mxid:
|
||||||
evt.sender.command_status = None
|
evt.sender.command_status = None
|
||||||
@@ -308,8 +309,9 @@ async def confirm_bridge(evt: CommandEvent) -> Optional[Dict]:
|
|||||||
portal.photo_id = ""
|
portal.photo_id = ""
|
||||||
portal.save()
|
portal.save()
|
||||||
|
|
||||||
asyncio.ensure_future(portal.update_matrix_room(user, entity, direct, levels=levels),
|
ignore_coro(asyncio.ensure_future(portal.update_matrix_room(user, entity, direct,
|
||||||
loop=evt.loop)
|
levels=levels),
|
||||||
|
loop=evt.loop))
|
||||||
|
|
||||||
return await evt.reply("Bridging complete. Portal synchronization should begin momentarily.")
|
return await evt.reply("Bridging complete. Portal synchronization should begin momentarily.")
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import re
|
|||||||
|
|
||||||
from telethon.errors import (InviteHashInvalidError, InviteHashExpiredError,
|
from telethon.errors import (InviteHashInvalidError, InviteHashExpiredError,
|
||||||
UserAlreadyParticipantError)
|
UserAlreadyParticipantError)
|
||||||
from telethon.tl.types import User as TLUser, TypeUpdates, MessageMediaGame, PeerChat
|
from telethon.tl.types import User as TLUser, TypeUpdates, MessageMediaGame
|
||||||
from telethon.tl.types.messages import BotCallbackAnswer
|
from telethon.tl.types.messages import BotCallbackAnswer
|
||||||
from telethon.tl.functions.messages import (ImportChatInviteRequest, CheckChatInviteRequest,
|
from telethon.tl.functions.messages import (ImportChatInviteRequest, CheckChatInviteRequest,
|
||||||
GetBotCallbackAnswerRequest)
|
GetBotCallbackAnswerRequest)
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class MatrixHandler:
|
|||||||
events = new_events - old_events
|
events = new_events - old_events
|
||||||
if len(events) > 0:
|
if len(events) > 0:
|
||||||
# New event pinned, set that as pinned in Telegram.
|
# New event pinned, set that as pinned in Telegram.
|
||||||
await portal.handle_matrix_pin(sender, events.pop())
|
await portal.handle_matrix_pin(sender, MatrixEventID(events.pop()))
|
||||||
elif len(new_events) == 0:
|
elif len(new_events) == 0:
|
||||||
# All pinned events removed, remove pinned event in Telegram.
|
# All pinned events removed, remove pinned event in Telegram.
|
||||||
await portal.handle_matrix_pin(sender, None)
|
await portal.handle_matrix_pin(sender, None)
|
||||||
|
|||||||
+37
-33
@@ -32,8 +32,7 @@ import re
|
|||||||
|
|
||||||
import magic
|
import magic
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
from sqlalchemy.exc import IntegrityError, InvalidRequestError
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm.exc import FlushError
|
|
||||||
|
|
||||||
from telethon.tl.functions.messages import (
|
from telethon.tl.functions.messages import (
|
||||||
AddChatUserRequest, CreateChatRequest, DeleteChatUserRequest, EditChatAdminRequest,
|
AddChatUserRequest, CreateChatRequest, DeleteChatUserRequest, EditChatAdminRequest,
|
||||||
@@ -46,28 +45,30 @@ from telethon.tl.functions.channels import (
|
|||||||
from telethon.tl.functions.messages import ReadHistoryRequest as ReadMessageHistoryRequest
|
from telethon.tl.functions.messages import ReadHistoryRequest as ReadMessageHistoryRequest
|
||||||
from telethon.tl.functions.channels import ReadHistoryRequest as ReadChannelHistoryRequest
|
from telethon.tl.functions.channels import ReadHistoryRequest as ReadChannelHistoryRequest
|
||||||
from telethon.errors import ChatAdminRequiredError, ChatNotModifiedError
|
from telethon.errors import ChatAdminRequiredError, ChatNotModifiedError
|
||||||
|
from telethon.tl.patched import Message, MessageService
|
||||||
from telethon.tl.types import (
|
from telethon.tl.types import (
|
||||||
Channel, ChannelAdminRights, ChannelBannedRights, ChannelFull, ChannelParticipantAdmin,
|
Channel, ChannelAdminRights, ChannelBannedRights, ChannelFull, ChannelParticipantAdmin,
|
||||||
ChannelParticipantCreator, ChannelParticipantsRecent, ChannelParticipantsSearch, Chat,
|
ChannelParticipantCreator, ChannelParticipantsRecent, ChannelParticipantsSearch, Chat,
|
||||||
ChatFull, ChatInviteEmpty, ChatParticipantAdmin, ChatParticipantCreator, ChatPhoto,
|
ChatFull, ChatInviteEmpty, ChatParticipantAdmin, ChatParticipantCreator, ChatPhoto,
|
||||||
DocumentAttributeFilename, DocumentAttributeImageSize, DocumentAttributeSticker,
|
DocumentAttributeFilename, DocumentAttributeImageSize, DocumentAttributeSticker,
|
||||||
DocumentAttributeVideo, FileLocation, GeoPoint, InputChannel, InputChatUploadedPhoto,
|
DocumentAttributeVideo, FileLocation, GeoPoint, InputChannel, InputChatUploadedPhoto,
|
||||||
InputPeerChannel, InputPeerChat, InputPeerUser, InputUser, InputUserSelf, Message,
|
InputPeerChannel, InputPeerChat, InputPeerUser, InputUser, InputUserSelf,
|
||||||
MessageActionChannelCreate, MessageActionChatAddUser, MessageActionChatCreate,
|
MessageActionChannelCreate, MessageActionChatAddUser, MessageActionChatCreate,
|
||||||
MessageActionChatDeletePhoto, MessageActionChatDeleteUser, MessageActionChatEditPhoto,
|
MessageActionChatDeletePhoto, MessageActionChatDeleteUser, MessageActionChatEditPhoto,
|
||||||
MessageActionChatEditTitle, MessageActionChatJoinedByLink, MessageActionChatMigrateTo,
|
MessageActionChatEditTitle, MessageActionChatJoinedByLink, MessageActionChatMigrateTo,
|
||||||
MessageActionPinMessage, MessageMediaContact, MessageMediaDocument, MessageMediaGeo,
|
MessageActionPinMessage, MessageActionGameScore, MessageMediaContact, MessageMediaDocument,
|
||||||
MessageMediaPhoto, MessageMediaUnsupported, MessageMediaGame, MessageService, PeerChannel,
|
MessageMediaGeo, MessageMediaPhoto, MessageMediaUnsupported, MessageMediaGame, PeerChannel,
|
||||||
PeerChat, PeerUser, Photo, PhotoCachedSize, SendMessageCancelAction, SendMessageTypingAction,
|
PeerChat, PeerUser, Photo, PhotoCachedSize, SendMessageCancelAction, SendMessageTypingAction,
|
||||||
TypeChannelParticipant, TypeChat, TypeChatParticipant, TypeDocumentAttribute, TypeInputPeer,
|
TypeChannelParticipant, TypeChat, TypeChatParticipant, TypeDocumentAttribute, TypeInputPeer,
|
||||||
TypeMessageAction, TypeMessageEntity, TypePeer, TypePhotoSize, TypeUpdates, TypeUser,
|
TypeMessageAction, TypeMessageEntity, TypePeer, TypePhotoSize, TypeUpdates, TypeUser,
|
||||||
TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping,
|
TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping,
|
||||||
User, UserFull, MessageEntityCode)
|
User, UserFull, MessageEntityPre)
|
||||||
from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI
|
from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI
|
||||||
|
|
||||||
from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID
|
from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from .db import Portal as DBPortal, Message as DBMessage, TelegramFile as DBTelegramFile
|
from .db import Portal as DBPortal, Message as DBMessage, TelegramFile as DBTelegramFile
|
||||||
|
from .util import ignore_coro
|
||||||
from . import puppet as p, user as u, formatter, util
|
from . import puppet as p, user as u, formatter, util
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -76,6 +77,7 @@ if TYPE_CHECKING:
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
from .tgclient import MautrixTelegramClient
|
from .tgclient import MautrixTelegramClient
|
||||||
|
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
|
||||||
config = None # type: Config
|
config = None # type: Config
|
||||||
@@ -338,7 +340,7 @@ class Portal:
|
|||||||
if synchronous:
|
if synchronous:
|
||||||
await update
|
await update
|
||||||
else:
|
else:
|
||||||
asyncio.ensure_future(update, loop=self.loop)
|
ignore_coro(asyncio.ensure_future(update, loop=self.loop))
|
||||||
await self.invite_to_matrix(invites or [])
|
await self.invite_to_matrix(invites or [])
|
||||||
return self.mxid
|
return self.mxid
|
||||||
async with self._room_create_lock:
|
async with self._room_create_lock:
|
||||||
@@ -405,10 +407,10 @@ class Portal:
|
|||||||
self.save()
|
self.save()
|
||||||
self.az.state_store.set_power_levels(self.mxid, power_levels)
|
self.az.state_store.set_power_levels(self.mxid, power_levels)
|
||||||
user.register_portal(self)
|
user.register_portal(self)
|
||||||
asyncio.ensure_future(self.update_matrix_room(user, entity, direct, puppet,
|
ignore_coro(asyncio.ensure_future(self.update_matrix_room(user, entity, direct, puppet,
|
||||||
levels=power_levels, users=users,
|
levels=power_levels, users=users,
|
||||||
participants=participants),
|
participants=participants),
|
||||||
loop=self.loop)
|
loop=self.loop))
|
||||||
|
|
||||||
return self.mxid
|
return self.mxid
|
||||||
|
|
||||||
@@ -631,7 +633,8 @@ class Portal:
|
|||||||
entity, ChannelParticipantsRecent(), offset=0, limit=limit, hash=0))
|
entity, ChannelParticipantsRecent(), offset=0, limit=limit, hash=0))
|
||||||
return response.users, response.participants
|
return response.users, response.participants
|
||||||
elif limit > 200 or limit == -1:
|
elif limit > 200 or limit == -1:
|
||||||
users, participants = [], [] # type: Tuple[List[TypeUser], List[TypeParticipant]]
|
users = [] # type: List[TypeUser]
|
||||||
|
participants = [] # type: List[TypeParticipant]
|
||||||
offset = 0
|
offset = 0
|
||||||
remaining_quota = limit if limit > 0 else 1000000
|
remaining_quota = limit if limit > 0 else 1000000
|
||||||
query = (ChannelParticipantsSearch("") if limit == -1
|
query = (ChannelParticipantsSearch("") if limit == -1
|
||||||
@@ -886,8 +889,8 @@ class Portal:
|
|||||||
await self._apply_msg_format(sender, msgtype, message)
|
await self._apply_msg_format(sender, msgtype, message)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _matrix_event_to_entities(event: Dict[str, Any]) -> Tuple[
|
def _matrix_event_to_entities(event: Dict[str, Any]
|
||||||
str, Optional[List[TypeMessageEntity]]]:
|
) -> Tuple[str, Optional[List[TypeMessageEntity]]]:
|
||||||
try:
|
try:
|
||||||
if event.get("format", None) == "org.matrix.custom.html":
|
if event.get("format", None) == "org.matrix.custom.html":
|
||||||
message, entities = formatter.matrix_to_telegram(event.get("formatted_body", ""))
|
message, entities = formatter.matrix_to_telegram(event.get("formatted_body", ""))
|
||||||
@@ -983,7 +986,7 @@ class Portal:
|
|||||||
self.log.debug("Handled Matrix message: %s", response)
|
self.log.debug("Handled Matrix message: %s", response)
|
||||||
self.is_duplicate(response, (event_id, space))
|
self.is_duplicate(response, (event_id, space))
|
||||||
DBMessage(
|
DBMessage(
|
||||||
tgid=response.id,
|
tgid=TelegramID(response.id),
|
||||||
tg_space=space,
|
tg_space=space,
|
||||||
mx_room=self.mxid,
|
mx_room=self.mxid,
|
||||||
mxid=event_id).insert()
|
mxid=event_id).insert()
|
||||||
@@ -1415,7 +1418,7 @@ class Portal:
|
|||||||
external_url=self.get_external_url(evt))
|
external_url=self.get_external_url(evt))
|
||||||
|
|
||||||
async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI,
|
async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI,
|
||||||
evt: Message, relates_to: dict = None) -> dict:
|
evt: Message, _: dict = None) -> dict:
|
||||||
override_text = ("This message is not supported on your version of Mautrix-Telegram. "
|
override_text = ("This message is not supported on your version of Mautrix-Telegram. "
|
||||||
"Please check https://github.com/tulir/mautrix-telegram or ask your "
|
"Please check https://github.com/tulir/mautrix-telegram or ask your "
|
||||||
"bridge administrator about possible updates.")
|
"bridge administrator about possible updates.")
|
||||||
@@ -1433,11 +1436,11 @@ class Portal:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _int_to_bytes(i: int) -> bytes:
|
def _int_to_bytes(i: int) -> bytes:
|
||||||
hex = "{0:010x}".format(i)
|
hex_value = "{0:010x}".format(i)
|
||||||
return codecs.decode(hex, "hex_codec")
|
return codecs.decode(hex_value, "hex_codec")
|
||||||
|
|
||||||
async def handle_telegram_game(self, source: 'AbstractUser', intent: IntentAPI,
|
async def handle_telegram_game(self, source: 'AbstractUser', intent: IntentAPI,
|
||||||
evt: Message, relates_to: dict = None):
|
evt: Message, _: dict = None):
|
||||||
game = evt.media.game
|
game = evt.media.game
|
||||||
if self.peer_type == "channel":
|
if self.peer_type == "channel":
|
||||||
play_id = base64.b64encode(b"c"
|
play_id = base64.b64encode(b"c"
|
||||||
@@ -1457,7 +1460,7 @@ class Portal:
|
|||||||
play_id = play_id.decode("utf-8").rstrip("=")
|
play_id = play_id.decode("utf-8").rstrip("=")
|
||||||
command = f"!tg play {play_id}"
|
command = f"!tg play {play_id}"
|
||||||
override_text = f"Run {command} in your bridge management room to play {game.title}"
|
override_text = f"Run {command} in your bridge management room to play {game.title}"
|
||||||
override_entities = [MessageEntityCode(offset=len("Run "), length=len(command))]
|
override_entities = [MessageEntityPre(offset=len("Run "), length=len(command), language="")]
|
||||||
text, html, relates_to = await formatter.telegram_to_matrix(
|
text, html, relates_to = await formatter.telegram_to_matrix(
|
||||||
evt, source, self.main_intent,
|
evt, source, self.main_intent,
|
||||||
override_text=override_text, override_entities=override_entities)
|
override_text=override_text, override_entities=override_entities)
|
||||||
@@ -1478,6 +1481,9 @@ class Portal:
|
|||||||
elif not self.get_config("edits_as_replies"):
|
elif not self.get_config("edits_as_replies"):
|
||||||
self.log.debug("Edits as replies disabled, ignoring edit event...")
|
self.log.debug("Edits as replies disabled, ignoring edit event...")
|
||||||
return
|
return
|
||||||
|
elif hasattr(evt, "media") and isinstance(evt.media, (MessageMediaGame,)):
|
||||||
|
self.log.debug("Ignoring game message edit event")
|
||||||
|
return
|
||||||
|
|
||||||
lock = self.optional_send_lock(sender.tgid if sender else None)
|
lock = self.optional_send_lock(sender.tgid if sender else None)
|
||||||
if lock:
|
if lock:
|
||||||
@@ -1492,7 +1498,7 @@ class Portal:
|
|||||||
if duplicate_found:
|
if duplicate_found:
|
||||||
mxid, other_tg_space = duplicate_found
|
mxid, other_tg_space = duplicate_found
|
||||||
if tg_space != other_tg_space:
|
if tg_space != other_tg_space:
|
||||||
DBMessage.update_by_tgid(evt.id, tg_space,
|
DBMessage.update_by_tgid(TelegramID(evt.id), tg_space,
|
||||||
mxid=mxid,
|
mxid=mxid,
|
||||||
mx_room=self.mxid)
|
mx_room=self.mxid)
|
||||||
return
|
return
|
||||||
@@ -1507,7 +1513,7 @@ class Portal:
|
|||||||
|
|
||||||
mxid = response["event_id"]
|
mxid = response["event_id"]
|
||||||
|
|
||||||
msg = DBMessage.get_by_tgid(evt.id, tg_space)
|
msg = DBMessage.get_by_tgid(TelegramID(evt.id), tg_space)
|
||||||
if not msg:
|
if not msg:
|
||||||
self.log.info(f"Didn't find edited message {evt.id}@{tg_space} (src {source.tgid}) "
|
self.log.info(f"Didn't find edited message {evt.id}@{tg_space} (src {source.tgid}) "
|
||||||
"in database.")
|
"in database.")
|
||||||
@@ -1536,11 +1542,12 @@ class Portal:
|
|||||||
self.log.debug(f"Ignoring message {evt.id}@{tg_space} (src {source.tgid}) "
|
self.log.debug(f"Ignoring message {evt.id}@{tg_space} (src {source.tgid}) "
|
||||||
f"as it was already handled (in space {other_tg_space})")
|
f"as it was already handled (in space {other_tg_space})")
|
||||||
if tg_space != other_tg_space:
|
if tg_space != other_tg_space:
|
||||||
DBMessage(tgid=evt.id, mx_room=self.mxid, mxid=mxid, tg_space=tg_space).insert()
|
DBMessage(tgid=TelegramID(evt.id), mx_room=self.mxid, mxid=mxid,
|
||||||
|
tg_space=tg_space).insert()
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.dedup_pre_db_check and self.peer_type == "channel":
|
if self.dedup_pre_db_check and self.peer_type == "channel":
|
||||||
msg = DBMessage.get_by_tgid(evt.id, tg_space)
|
msg = DBMessage.get_by_tgid(TelegramID(evt.id), tg_space)
|
||||||
if msg:
|
if msg:
|
||||||
self.log.debug(f"Ignoring message {evt.id} (src {source.tgid}) as it was already"
|
self.log.debug(f"Ignoring message {evt.id} (src {source.tgid}) as it was already"
|
||||||
f"handled into {msg.mxid}. This duplicate was catched in the db "
|
f"handled into {msg.mxid}. This duplicate was catched in the db "
|
||||||
@@ -1593,21 +1600,15 @@ class Portal:
|
|||||||
|
|
||||||
self.log.debug("Handled Telegram message: %s", evt)
|
self.log.debug("Handled Telegram message: %s", evt)
|
||||||
try:
|
try:
|
||||||
DBMessage(tgid=evt.id, mx_room=self.mxid, mxid=mxid, tg_space=tg_space).insert()
|
DBMessage(tgid=TelegramID(evt.id), mx_room=self.mxid, mxid=mxid,
|
||||||
|
tg_space=tg_space).insert()
|
||||||
DBMessage.update_by_mxid(temporary_identifier, self.mxid, mxid=mxid)
|
DBMessage.update_by_mxid(temporary_identifier, self.mxid, mxid=mxid)
|
||||||
except FlushError as e:
|
except IntegrityError as e:
|
||||||
self.log.exception(f"{e.__class__.__name__} while saving message mapping. "
|
self.log.exception(f"{e.__class__.__name__} while saving message mapping. "
|
||||||
"This might mean that an update was handled after it left the "
|
"This might mean that an update was handled after it left the "
|
||||||
"dedup cache queue. You can try enabling bridge.deduplication."
|
"dedup cache queue. You can try enabling bridge.deduplication."
|
||||||
"pre_db_check in the config.")
|
"pre_db_check in the config.")
|
||||||
await intent.redact(self.mxid, mxid)
|
await intent.redact(self.mxid, mxid)
|
||||||
except (IntegrityError, InvalidRequestError) as e:
|
|
||||||
self.log.exception(f"{e.__class__.__name__} while saving message mapping. "
|
|
||||||
"This might mean that an update was handled after it left the "
|
|
||||||
"dedup cache queue. You can try enabling bridge.deduplication."
|
|
||||||
"pre_db_check in the config.")
|
|
||||||
self.db.rollback()
|
|
||||||
await intent.redact(self.mxid, mxid)
|
|
||||||
|
|
||||||
async def _create_room_on_action(self, source: 'AbstractUser',
|
async def _create_room_on_action(self, source: 'AbstractUser',
|
||||||
action: TypeMessageAction) -> bool:
|
action: TypeMessageAction) -> bool:
|
||||||
@@ -1650,6 +1651,9 @@ class Portal:
|
|||||||
await sender.intent.send_emote(self.mxid, "upgraded this group to a supergroup.")
|
await sender.intent.send_emote(self.mxid, "upgraded this group to a supergroup.")
|
||||||
elif isinstance(action, MessageActionPinMessage):
|
elif isinstance(action, MessageActionPinMessage):
|
||||||
await self.receive_telegram_pin_sender(sender)
|
await self.receive_telegram_pin_sender(sender)
|
||||||
|
elif isinstance(action, MessageActionGameScore):
|
||||||
|
# TODO handle game score
|
||||||
|
pass
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ from mautrix_appservice import AppService, IntentAPI, IntentError, MatrixRequest
|
|||||||
|
|
||||||
from .types import MatrixUserID, TelegramID
|
from .types import MatrixUserID, TelegramID
|
||||||
from .db import Puppet as DBPuppet
|
from .db import Puppet as DBPuppet
|
||||||
|
from .util import ignore_coro
|
||||||
from . import util
|
from . import util
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .matrix import MatrixHandler
|
from .matrix import MatrixHandler
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .context import Context
|
from .context import Context
|
||||||
from . import user as u
|
|
||||||
from .abstract_user import AbstractUser
|
from .abstract_user import AbstractUser
|
||||||
|
|
||||||
PuppetError = Enum('PuppetError', 'Success OnlyLoginSelf InvalidAccessToken')
|
PuppetError = Enum('PuppetError', 'Success OnlyLoginSelf InvalidAccessToken')
|
||||||
@@ -153,7 +153,7 @@ class Puppet:
|
|||||||
return PuppetError.OnlyLoginSelf
|
return PuppetError.OnlyLoginSelf
|
||||||
return PuppetError.InvalidAccessToken
|
return PuppetError.InvalidAccessToken
|
||||||
if config["bridge.sync_with_custom_puppets"]:
|
if config["bridge.sync_with_custom_puppets"]:
|
||||||
asyncio.ensure_future(self.sync(), loop=self.loop)
|
ignore_coro(asyncio.ensure_future(self.sync(), loop=self.loop))
|
||||||
return PuppetError.Success
|
return PuppetError.Success
|
||||||
|
|
||||||
async def leave_rooms_with_default_user(self) -> None:
|
async def leave_rooms_with_default_user(self) -> None:
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
from sqlalchemy import orm
|
|
||||||
|
|
||||||
from mautrix_appservice import StateStore
|
from mautrix_appservice import StateStore
|
||||||
|
|
||||||
from .types import MatrixUserID, MatrixRoomID
|
from .types import MatrixUserID, MatrixRoomID
|
||||||
@@ -26,9 +24,8 @@ from .db import RoomState, UserProfile
|
|||||||
|
|
||||||
|
|
||||||
class SQLStateStore(StateStore):
|
class SQLStateStore(StateStore):
|
||||||
def __init__(self, db: orm.Session) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.db = db # type: orm.Session
|
|
||||||
self.profile_cache = {} # type: Dict[Tuple[str, str], UserProfile]
|
self.profile_cache = {} # type: Dict[Tuple[str, str], UserProfile]
|
||||||
self.room_state_cache = {} # type: Dict[str, RoomState]
|
self.room_state_cache = {} # type: Dict[str, RoomState]
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from telethon.tl.functions.messages import SendMediaRequest
|
|||||||
from telethon.tl.types import (
|
from telethon.tl.types import (
|
||||||
InputMediaUploadedDocument, InputMediaUploadedPhoto, TypeDocumentAttribute, TypeInputMedia,
|
InputMediaUploadedDocument, InputMediaUploadedPhoto, TypeDocumentAttribute, TypeInputMedia,
|
||||||
TypeInputPeer, TypeMessageEntity, TypeMessageMedia, TypePeer)
|
TypeInputPeer, TypeMessageEntity, TypeMessageMedia, TypePeer)
|
||||||
from telethon.tl import custom
|
from telethon.tl.patched import Message
|
||||||
|
|
||||||
|
|
||||||
class MautrixTelegramClient(TelegramClient):
|
class MautrixTelegramClient(TelegramClient):
|
||||||
@@ -45,7 +45,7 @@ class MautrixTelegramClient(TelegramClient):
|
|||||||
async def send_media(self, entity: Union[TypeInputPeer, TypePeer],
|
async def send_media(self, entity: Union[TypeInputPeer, TypePeer],
|
||||||
media: Union[TypeInputMedia, TypeMessageMedia],
|
media: Union[TypeInputMedia, TypeMessageMedia],
|
||||||
caption: str = None, entities: List[TypeMessageEntity] = None,
|
caption: str = None, entities: List[TypeMessageEntity] = None,
|
||||||
reply_to: int = None) -> Optional[custom.Message]:
|
reply_to: int = None) -> Optional[Message]:
|
||||||
entity = await self.get_input_entity(entity)
|
entity = await self.get_input_entity(entity)
|
||||||
reply_to = utils.get_message_id(reply_to)
|
reply_to = utils.get_message_id(reply_to)
|
||||||
request = SendMediaRequest(entity, media, message=caption or "", entities=entities or [],
|
request = SendMediaRequest(entity, media, message=caption or "", entities=entities or [],
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ import re
|
|||||||
|
|
||||||
from telethon.tl.types import (
|
from telethon.tl.types import (
|
||||||
TypeUpdate, UpdateNewMessage, UpdateNewChannelMessage, PeerUser,
|
TypeUpdate, UpdateNewMessage, UpdateNewChannelMessage, PeerUser,
|
||||||
UpdateShortChatMessage, UpdateShortMessage)
|
UpdateShortChatMessage, UpdateShortMessage, User as TLUser)
|
||||||
from telethon.tl.types import User as TLUser
|
|
||||||
from telethon.tl.types.contacts import ContactsNotModified
|
from telethon.tl.types.contacts import ContactsNotModified
|
||||||
from telethon.tl.functions.contacts import GetContactsRequest, SearchRequest
|
from telethon.tl.functions.contacts import GetContactsRequest, SearchRequest
|
||||||
from telethon.tl.functions.account import UpdateStatusRequest
|
from telethon.tl.functions.account import UpdateStatusRequest
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ from .file_transfer import transfer_file_to_matrix, convert_image
|
|||||||
from .format_duration import format_duration
|
from .format_duration import format_duration
|
||||||
from .signed_token import sign_token, verify_token
|
from .signed_token import sign_token, verify_token
|
||||||
from .recursive_dict import recursive_del, recursive_set, recursive_get
|
from .recursive_dict import recursive_del, recursive_set, recursive_get
|
||||||
|
|
||||||
|
|
||||||
|
def ignore_coro(coro):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from sqlalchemy import orm
|
|
||||||
from sqlalchemy.exc import IntegrityError, InvalidRequestError
|
from sqlalchemy.exc import IntegrityError, InvalidRequestError
|
||||||
|
|
||||||
from telethon.tl.types import (Document, FileLocation, InputFileLocation,
|
from telethon.tl.types import (Document, FileLocation, InputFileLocation,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import logging
|
|||||||
from telethon.errors import *
|
from telethon.errors import *
|
||||||
|
|
||||||
from ...commands.auth import enter_password
|
from ...commands.auth import enter_password
|
||||||
from ...util import format_duration
|
from ...util import format_duration, ignore_coro
|
||||||
from ...puppet import Puppet, PuppetError
|
from ...puppet import Puppet, PuppetError
|
||||||
from ...user import User
|
from ...user import User
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ class AuthAPI(abc.ABC):
|
|||||||
existing_user = User.get_by_tgid(user_info.id)
|
existing_user = User.get_by_tgid(user_info.id)
|
||||||
if existing_user and existing_user != user:
|
if existing_user and existing_user != user:
|
||||||
await existing_user.log_out()
|
await existing_user.log_out()
|
||||||
asyncio.ensure_future(user.post_login(user_info), loop=self.loop)
|
ignore_coro(asyncio.ensure_future(user.post_login(user_info), loop=self.loop))
|
||||||
if user.command_status and user.command_status["action"] == "Login":
|
if user.command_status and user.command_status["action"] == "Login":
|
||||||
user.command_status = None
|
user.command_status = None
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from mautrix_appservice import AppService, MatrixRequestError, IntentError
|
|||||||
from ...types import MatrixUserID, TelegramID
|
from ...types import MatrixUserID, TelegramID
|
||||||
from ...user import User
|
from ...user import User
|
||||||
from ...portal import Portal
|
from ...portal import Portal
|
||||||
|
from ...util import ignore_coro
|
||||||
from ...commands.portal import user_has_power_level, get_initial_state
|
from ...commands.portal import user_has_power_level, get_initial_state
|
||||||
from ..common import AuthAPI
|
from ..common import AuthAPI
|
||||||
|
|
||||||
@@ -190,8 +191,9 @@ class ProvisioningAPI(AuthAPI):
|
|||||||
portal.photo_id = ""
|
portal.photo_id = ""
|
||||||
portal.save()
|
portal.save()
|
||||||
|
|
||||||
asyncio.ensure_future(portal.update_matrix_room(user, entity, direct, levels=levels),
|
ignore_coro(asyncio.ensure_future(portal.update_matrix_room(user, entity, direct,
|
||||||
loop=self.loop)
|
levels=levels),
|
||||||
|
loop=self.loop))
|
||||||
|
|
||||||
return web.Response(status=202, body="{}")
|
return web.Response(status=202, body="{}")
|
||||||
|
|
||||||
@@ -285,7 +287,7 @@ class ProvisioningAPI(AuthAPI):
|
|||||||
self.log.exception("Failed to disconnect chat")
|
self.log.exception("Failed to disconnect chat")
|
||||||
return self.get_error_response(500, "exception", "Failed to disconnect chat")
|
return self.get_error_response(500, "exception", "Failed to disconnect chat")
|
||||||
else:
|
else:
|
||||||
asyncio.ensure_future(coro, loop=self.loop)
|
ignore_coro(asyncio.ensure_future(coro, loop=self.loop))
|
||||||
return web.json_response({}, status=200 if sync else 202)
|
return web.json_response({}, status=200 if sync else 202)
|
||||||
|
|
||||||
async def get_user_info(self, request: web.Request) -> web.Response:
|
async def get_user_info(self, request: web.Request) -> web.Response:
|
||||||
|
|||||||
Reference in New Issue
Block a user