Add missing type hints and fix most type errors except for Optionals.
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
#
|
||||
# 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/>.
|
||||
from typing import Dict
|
||||
from typing import Any, Awaitable, Dict, Optional
|
||||
import asyncio
|
||||
|
||||
from telethon.errors import (
|
||||
@@ -31,7 +31,7 @@ from ..util import format_duration
|
||||
@command_handler(needs_auth=False,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Check if you're logged into Telegram.")
|
||||
async def ping(evt: CommandEvent) -> None:
|
||||
async def ping(evt: CommandEvent) -> Optional[Dict]:
|
||||
me = await evt.sender.client.get_me() if await evt.sender.is_logged_in() else None
|
||||
if me:
|
||||
return await evt.reply(f"You're logged in as @{me.username}")
|
||||
@@ -42,7 +42,7 @@ async def ping(evt: CommandEvent) -> None:
|
||||
@command_handler(needs_auth=False, needs_puppeting=False,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Get the info of the message relay Telegram bot.")
|
||||
async def ping_bot(evt: CommandEvent) -> None:
|
||||
async def ping_bot(evt: CommandEvent) -> Optional[Dict]:
|
||||
if not evt.tgbot:
|
||||
return await evt.reply("Telegram message relay bot not configured.")
|
||||
bot_info = await evt.tgbot.client.get_me()
|
||||
@@ -57,19 +57,19 @@ async def ping_bot(evt: CommandEvent) -> None:
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Revert your Telegram account's Matrix puppet to use the default Matrix "
|
||||
"account.")
|
||||
async def logout_matrix(evt: CommandEvent) -> None:
|
||||
async def logout_matrix(evt: CommandEvent) -> Optional[Dict]:
|
||||
puppet = pu.Puppet.get(evt.sender.tgid)
|
||||
if not puppet.is_real_user:
|
||||
return await evt.reply("You are not logged in with your Matrix account.")
|
||||
await puppet.switch_mxid(None, None)
|
||||
await evt.reply("Reverted your Telegram account's Matrix puppet back to the default.")
|
||||
return await evt.reply("Reverted your Telegram account's Matrix puppet back to the default.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=True, management_only=True, needs_matrix_puppeting=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Replace your Telegram account's Matrix puppet with your own Matrix "
|
||||
"account")
|
||||
async def login_matrix(evt: CommandEvent) -> None:
|
||||
async def login_matrix(evt: CommandEvent) -> Optional[Dict]:
|
||||
puppet = pu.Puppet.get(evt.sender.tgid)
|
||||
if puppet.is_real_user:
|
||||
return await evt.reply("You have already logged in with your Matrix account. "
|
||||
@@ -100,7 +100,7 @@ async def login_matrix(evt: CommandEvent) -> None:
|
||||
return await evt.reply("This bridge instance has been configured to not allow logging in.")
|
||||
|
||||
|
||||
async def enter_matrix_token(evt: CommandEvent) -> None:
|
||||
async def enter_matrix_token(evt: CommandEvent) -> Dict:
|
||||
evt.sender.command_status = None
|
||||
|
||||
puppet = pu.Puppet.get(evt.sender.tgid)
|
||||
@@ -109,10 +109,11 @@ async def enter_matrix_token(evt: CommandEvent) -> None:
|
||||
"Log out with `$cmdprefix+sp logout-matrix` first.")
|
||||
|
||||
resp = await puppet.switch_mxid(" ".join(evt.args), evt.sender.mxid)
|
||||
if resp == 2:
|
||||
if resp == pu.PuppetError.OnlyLoginSelf:
|
||||
return await evt.reply("You can only log in as your own Matrix user.")
|
||||
elif resp == 1:
|
||||
elif resp == pu.PuppetError.InvalidAccessToken:
|
||||
return await evt.reply("Failed to verify access token.")
|
||||
assert resp == pu.PuppetError.Success, "Encountered an unhandled PuppetError."
|
||||
return await evt.reply(
|
||||
f"Replaced your Telegram account's Matrix puppet with {puppet.custom_mxid}.")
|
||||
|
||||
@@ -121,7 +122,7 @@ async def enter_matrix_token(evt: CommandEvent) -> None:
|
||||
help_section=SECTION_AUTH,
|
||||
help_args="<_phone_> <_full name_>",
|
||||
help_text="Register to Telegram")
|
||||
async def register(evt: CommandEvent) -> None:
|
||||
async def register(evt: CommandEvent) -> Optional[Dict]:
|
||||
if await evt.sender.is_logged_in():
|
||||
return await evt.reply("You are already logged in.")
|
||||
elif len(evt.args) < 1:
|
||||
@@ -138,9 +139,10 @@ async def register(evt: CommandEvent) -> None:
|
||||
"action": "Register",
|
||||
"full_name": full_name,
|
||||
})
|
||||
return None
|
||||
|
||||
|
||||
async def enter_code_register(evt: CommandEvent) -> None:
|
||||
async def enter_code_register(evt: CommandEvent) -> Dict:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp <code>`")
|
||||
try:
|
||||
@@ -169,7 +171,7 @@ async def enter_code_register(evt: CommandEvent) -> None:
|
||||
@command_handler(needs_auth=False, management_only=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Get instructions on how to log in.")
|
||||
async def login(evt: CommandEvent) -> None:
|
||||
async def login(evt: CommandEvent) -> Optional[Dict]:
|
||||
if await evt.sender.is_logged_in():
|
||||
return await evt.reply("You are already logged in.")
|
||||
|
||||
@@ -200,7 +202,8 @@ async def login(evt: CommandEvent) -> None:
|
||||
return await evt.reply("This bridge instance has been configured to not allow logging in.")
|
||||
|
||||
|
||||
async def request_code(evt: CommandEvent, phone_number: str, next_status: Dict[str, str]) -> None:
|
||||
async def request_code(evt: CommandEvent, phone_number: str, next_status: Dict[str, Any]
|
||||
) -> Dict:
|
||||
ok = False
|
||||
try:
|
||||
await evt.sender.ensure_started(even_if_no_session=True)
|
||||
@@ -232,7 +235,7 @@ async def request_code(evt: CommandEvent, phone_number: str, next_status: Dict[s
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def enter_phone_or_token(evt: CommandEvent) -> None:
|
||||
async def enter_phone_or_token(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp enter-phone-or-token <phone-or-token>`")
|
||||
elif not evt.config.get("bridge.allow_matrix_login", True):
|
||||
@@ -252,10 +255,11 @@ async def enter_phone_or_token(evt: CommandEvent) -> None:
|
||||
"next": enter_code,
|
||||
"action": "Login",
|
||||
})
|
||||
return None
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def enter_code(evt: CommandEvent) -> None:
|
||||
async def enter_code(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp enter-code <code>`")
|
||||
elif not evt.config.get("bridge.allow_matrix_login", True):
|
||||
@@ -267,10 +271,11 @@ async def enter_code(evt: CommandEvent) -> None:
|
||||
evt.log.exception("Error sending phone code")
|
||||
return await evt.reply("Unhandled exception while sending code. "
|
||||
"Check console for more details.")
|
||||
return None
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def enter_password(evt: CommandEvent) -> None:
|
||||
async def enter_password(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp enter-password <password>`")
|
||||
elif not evt.config.get("bridge.allow_matrix_login", True):
|
||||
@@ -286,9 +291,10 @@ async def enter_password(evt: CommandEvent) -> None:
|
||||
evt.log.exception("Error sending password")
|
||||
return await evt.reply("Unhandled exception while sending password. "
|
||||
"Check console for more details.")
|
||||
return None
|
||||
|
||||
|
||||
async def sign_in(evt: CommandEvent, **sign_in_info) -> None:
|
||||
async def sign_in(evt: CommandEvent, **sign_in_info) -> Dict:
|
||||
try:
|
||||
await evt.sender.ensure_started(even_if_no_session=True)
|
||||
user = await evt.sender.client.sign_in(**sign_in_info)
|
||||
@@ -313,7 +319,7 @@ async def sign_in(evt: CommandEvent, **sign_in_info) -> None:
|
||||
@command_handler(needs_auth=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Log out from Telegram.")
|
||||
async def logout(evt: CommandEvent) -> None:
|
||||
async def logout(evt: CommandEvent) -> Optional[Dict]:
|
||||
if await evt.sender.log_out():
|
||||
return await evt.reply("Logged out successfully.")
|
||||
return await evt.reply("Failed to log out.")
|
||||
|
||||
@@ -14,21 +14,21 @@
|
||||
#
|
||||
# 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/>.
|
||||
from typing import Tuple, List
|
||||
from typing import Dict, List, NewType, Optional, Tuple, Union
|
||||
|
||||
from mautrix_appservice import MatrixRequestError, IntentAPI
|
||||
|
||||
from ..types import MatrixRoomId, MatrixUserId
|
||||
from . import command_handler, CommandEvent, SECTION_ADMIN
|
||||
from .. import puppet as pu, portal as po
|
||||
|
||||
ManagementRoomList = List[Tuple[str, str]]
|
||||
RoomIDList = List[str]
|
||||
ManagementRoom = NewType('ManagementRoom', Tuple[MatrixRoomId, MatrixUserId])
|
||||
|
||||
|
||||
async def _find_rooms(intent: IntentAPI) -> Tuple[ManagementRoomList, RoomIDList,
|
||||
async def _find_rooms(intent: IntentAPI) -> Tuple[List[ManagementRoom], List[MatrixRoomId],
|
||||
List["po.Portal"], List["po.Portal"]]:
|
||||
management_rooms = [] # type: ManagementRoomList
|
||||
unidentified_rooms = [] # type: RoomIDList
|
||||
management_rooms = [] # type: List[ManagementRoom]
|
||||
unidentified_rooms = [] # type: List[MatrixRoomId]
|
||||
portals = [] # type: List[po.Portal]
|
||||
empty_portals = [] # type: List[po.Portal]
|
||||
|
||||
@@ -45,7 +45,7 @@ async def _find_rooms(intent: IntentAPI) -> Tuple[ManagementRoomList, RoomIDList
|
||||
if pu.Puppet.get_id_from_mxid(other_member):
|
||||
unidentified_rooms.append(room)
|
||||
else:
|
||||
management_rooms.append((room, other_member))
|
||||
management_rooms.append(ManagementRoom((room, other_member)))
|
||||
else:
|
||||
unidentified_rooms.append(room)
|
||||
else:
|
||||
@@ -61,7 +61,7 @@ async def _find_rooms(intent: IntentAPI) -> Tuple[ManagementRoomList, RoomIDList
|
||||
@command_handler(needs_admin=True, needs_auth=False, management_only=True, name="clean-rooms",
|
||||
help_section=SECTION_ADMIN,
|
||||
help_text="Clean up unused portal/management rooms.")
|
||||
async def clean_rooms(evt: CommandEvent) -> None:
|
||||
async def clean_rooms(evt: CommandEvent) -> Optional[Dict]:
|
||||
management_rooms, unidentified_rooms, portals, empty_portals = await _find_rooms(evt.az.intent)
|
||||
|
||||
reply = ["#### Management rooms (M)"]
|
||||
@@ -106,13 +106,14 @@ async def clean_rooms(evt: CommandEvent) -> None:
|
||||
return await evt.reply("\n".join(reply))
|
||||
|
||||
|
||||
async def set_rooms_to_clean(evt, management_rooms: ManagementRoomList,
|
||||
unidentified_rooms: RoomIDList, portals: List["po.Portal"],
|
||||
async def set_rooms_to_clean(evt, management_rooms: List[ManagementRoom],
|
||||
unidentified_rooms: List[MatrixRoomId], portals: List["po.Portal"],
|
||||
empty_portals: List["po.Portal"]) -> None:
|
||||
command = evt.args[0]
|
||||
rooms_to_clean = []
|
||||
rooms_to_clean = [] # type: List[Union[po.Portal, MatrixRoomId]]
|
||||
if command == "clean-recommended":
|
||||
rooms_to_clean = empty_portals + unidentified_rooms
|
||||
rooms_to_clean += empty_portals
|
||||
rooms_to_clean += unidentified_rooms
|
||||
elif command == "clean-groups":
|
||||
if len(evt.args) < 2:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp clean-groups [M][A][U][I]")
|
||||
@@ -158,7 +159,7 @@ async def set_rooms_to_clean(evt, management_rooms: ManagementRoomList,
|
||||
"`$cmdprefix+sp confirm-clean`.")
|
||||
|
||||
|
||||
async def execute_room_cleanup(evt, rooms_to_clean) -> None:
|
||||
async def execute_room_cleanup(evt, rooms_to_clean: List[Union[po.Portal, MatrixRoomId]]) -> None:
|
||||
if len(evt.args) > 0 and evt.args[0] == "confirm-clean":
|
||||
await evt.reply(f"Cleaning {len(rooms_to_clean)} rooms. "
|
||||
"This might take a while.")
|
||||
@@ -167,7 +168,7 @@ async def execute_room_cleanup(evt, rooms_to_clean) -> None:
|
||||
if isinstance(room, po.Portal):
|
||||
await room.cleanup_and_delete()
|
||||
cleaned += 1
|
||||
elif isinstance(room, str):
|
||||
elif isinstance(room, str): # str is aliased by MatrixRoomId
|
||||
await po.Portal.cleanup_room(evt.az.intent, room, message="Room deleted")
|
||||
cleaned += 1
|
||||
evt.sender.command_status = None
|
||||
|
||||
@@ -14,19 +14,20 @@
|
||||
#
|
||||
# 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/>.
|
||||
from typing import List, Dict, Callable, Optional
|
||||
from typing import Any, Awaitable, Callable, Coroutine, Dict, List, NamedTuple, Optional, Union
|
||||
from collections import namedtuple
|
||||
import markdown
|
||||
import logging
|
||||
|
||||
from telethon.errors import FloodWaitError
|
||||
|
||||
from ..types import MatrixRoomId
|
||||
from ..util import format_duration
|
||||
from .. import user as u, context as c
|
||||
|
||||
command_handlers = {} # type: Dict[str, CommandHandler]
|
||||
|
||||
HelpSection = namedtuple("HelpSection", "name order description")
|
||||
HelpSection = NamedTuple('HelpSection', [('name', str), ('order', int), ('description', str)])
|
||||
|
||||
SECTION_GENERAL = HelpSection("General", 0, "")
|
||||
SECTION_AUTH = HelpSection("Authentication", 10, "")
|
||||
@@ -37,8 +38,8 @@ SECTION_ADMIN = HelpSection("Administration", 50, "")
|
||||
|
||||
|
||||
class CommandEvent:
|
||||
def __init__(self, processor: "CommandProcessor", room: str, sender: u.User, command: str,
|
||||
args: List[str], is_management: bool, is_portal: bool) -> None:
|
||||
def __init__(self, processor: 'CommandProcessor', room: MatrixRoomId, sender: u.User,
|
||||
command: str, args: List[str], is_management: bool, is_portal: bool) -> None:
|
||||
self.az = processor.az
|
||||
self.log = processor.log
|
||||
self.loop = processor.loop
|
||||
@@ -53,7 +54,8 @@ class CommandEvent:
|
||||
self.is_management = is_management
|
||||
self.is_portal = is_portal
|
||||
|
||||
def reply(self, message: str, allow_html: bool = False, render_markdown: bool = True) -> None:
|
||||
def reply(self, message: str, allow_html: bool = False, render_markdown: bool = True
|
||||
) -> Awaitable[Dict]:
|
||||
message = message.replace("$cmdprefix+sp ",
|
||||
"" if self.is_management else f"{self.command_prefix} ")
|
||||
message = message.replace("$cmdprefix", self.command_prefix)
|
||||
@@ -66,7 +68,7 @@ class CommandEvent:
|
||||
|
||||
|
||||
class CommandHandler:
|
||||
def __init__(self, handler: Callable[[CommandEvent], None], needs_auth: bool,
|
||||
def __init__(self, handler: Callable[[CommandEvent], Awaitable[Dict]], needs_auth: bool,
|
||||
needs_puppeting: bool, needs_matrix_puppeting: bool, needs_admin: bool,
|
||||
management_only: bool, name: str, help_text: str, help_args: str,
|
||||
help_section: HelpSection) -> None:
|
||||
@@ -103,7 +105,8 @@ class CommandHandler:
|
||||
(not self.needs_admin or is_admin) and
|
||||
(not self.needs_auth or is_logged_in))
|
||||
|
||||
async def __call__(self, evt: CommandEvent) -> None:
|
||||
async def __call__(self, evt: CommandEvent
|
||||
) -> Dict:
|
||||
error = await self.get_permission_error(evt)
|
||||
if error is not None:
|
||||
return await evt.reply(error)
|
||||
@@ -118,13 +121,21 @@ class CommandHandler:
|
||||
return f"**{self.name}** {self._help_args} - {self._help_text}"
|
||||
|
||||
|
||||
def command_handler(_func: Optional[Callable[[CommandEvent], None]] = None, *, needs_auth=True,
|
||||
needs_puppeting=True, needs_matrix_puppeting=False, needs_admin=False,
|
||||
management_only=False, name=None, help_text="", help_args="",
|
||||
help_section=None) -> None:
|
||||
def command_handler(_func: Optional[Callable[[CommandEvent], Awaitable[Dict]]] = None, *,
|
||||
needs_auth: bool = True,
|
||||
needs_puppeting: bool = True,
|
||||
needs_matrix_puppeting: bool = False,
|
||||
needs_admin: bool = False,
|
||||
management_only: bool = False,
|
||||
name: Optional[str] = None,
|
||||
help_text: str = "",
|
||||
help_args: str = "",
|
||||
help_section: HelpSection = None
|
||||
) -> Callable[[Callable[[CommandEvent], Awaitable[Optional[Dict]]]],
|
||||
CommandHandler]:
|
||||
input_name = name
|
||||
|
||||
def decorator(func: Callable[[CommandEvent], None]) -> None:
|
||||
def decorator(func: Callable[[CommandEvent], Awaitable[Optional[Dict]]]) -> CommandHandler:
|
||||
name = input_name or func.__name__.replace("_", "-")
|
||||
handler = CommandHandler(func, needs_auth, needs_puppeting, needs_matrix_puppeting,
|
||||
needs_admin, management_only, name, help_text, help_args,
|
||||
@@ -139,26 +150,26 @@ class CommandProcessor:
|
||||
log = logging.getLogger("mau.commands")
|
||||
|
||||
def __init__(self, context: c.Context) -> None:
|
||||
self.az, self.db, self.config, self.loop, self.tgbot = context
|
||||
self.az, self.db, self.config, self.loop, self.tgbot = context.core
|
||||
self.public_website = context.public_website
|
||||
self.command_prefix = self.config["bridge.command_prefix"]
|
||||
|
||||
async def handle(self, room: str, sender: u.User, command: str, args: List[str],
|
||||
is_management: bool, is_portal: bool) -> None:
|
||||
async def handle(self, room: MatrixRoomId, sender: u.User, command: str, args: List[str],
|
||||
is_management: bool, is_portal: bool) -> Optional[Dict]:
|
||||
evt = CommandEvent(self, room, sender, command, args, is_management, is_portal)
|
||||
orig_command = command
|
||||
command = command.lower()
|
||||
try:
|
||||
command = command_handlers[command]
|
||||
command_handler = command_handlers[command]
|
||||
except KeyError:
|
||||
if sender.command_status and "next" in sender.command_status:
|
||||
args.insert(0, orig_command)
|
||||
evt.command = ""
|
||||
command = sender.command_status["next"]
|
||||
else:
|
||||
command = command_handlers["unknown-command"]
|
||||
command_handler = command_handlers["unknown-command"]
|
||||
try:
|
||||
await command(evt)
|
||||
await command_handler(evt)
|
||||
except FloodWaitError as e:
|
||||
return await evt.reply(f"Flood error: Please wait {format_duration(e.seconds)}")
|
||||
except Exception:
|
||||
@@ -166,3 +177,4 @@ class CommandProcessor:
|
||||
f"{evt.command} {' '.join(args)} from {sender.mxid}")
|
||||
return await evt.reply("Unhandled error while handling command. "
|
||||
"Check logs for more details.")
|
||||
return None
|
||||
|
||||
@@ -14,46 +14,49 @@
|
||||
#
|
||||
# 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/>.
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from . import command_handler, CommandEvent, _command_handlers, SECTION_GENERAL
|
||||
from .handler import HelpSection
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False,
|
||||
help_section=SECTION_GENERAL,
|
||||
help_text="Cancel an ongoing action (such as login)")
|
||||
def cancel(evt: CommandEvent) -> None:
|
||||
async def cancel(evt: CommandEvent) -> Optional[Dict]:
|
||||
if evt.sender.command_status:
|
||||
action = evt.sender.command_status["action"]
|
||||
evt.sender.command_status = None
|
||||
return evt.reply(f"{action} cancelled.")
|
||||
return await evt.reply(f"{action} cancelled.")
|
||||
else:
|
||||
return evt.reply("No ongoing command.")
|
||||
return await evt.reply("No ongoing command.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False)
|
||||
def unknown_command(evt: CommandEvent) -> None:
|
||||
return evt.reply("Unknown command. Try `$cmdprefix+sp help` for help.")
|
||||
async def unknown_command(evt: CommandEvent) -> Optional[Dict]:
|
||||
return await evt.reply("Unknown command. Try `$cmdprefix+sp help` for help.")
|
||||
|
||||
|
||||
help_cache = {}
|
||||
help_cache = {} # type: Dict[Tuple[bool, bool, bool, bool, bool], str]
|
||||
|
||||
|
||||
async def _get_help_text(evt: CommandEvent) -> None:
|
||||
async def _get_help_text(evt: CommandEvent) -> str:
|
||||
cache_key = (evt.is_management, evt.sender.puppet_whitelisted,
|
||||
evt.sender.matrix_puppet_whitelisted, evt.sender.is_admin,
|
||||
await evt.sender.is_logged_in())
|
||||
if cache_key not in help_cache:
|
||||
help = {}
|
||||
help_sections = {} # type: Dict[HelpSection, List[str]]
|
||||
for handler in _command_handlers.values():
|
||||
if handler.has_help and handler.has_permission(*cache_key):
|
||||
help.setdefault(handler.help_section, [])
|
||||
help[handler.help_section].append(handler.help + " ")
|
||||
help = sorted(help.items(), key=lambda item: item[0].order)
|
||||
help = ["#### {}\n{}\n".format(key.name, "\n".join(value)) for key, value in help]
|
||||
help_sections.setdefault(handler.help_section, [])
|
||||
help_sections[handler.help_section].append(handler.help + " ")
|
||||
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]
|
||||
help_cache[cache_key] = "\n".join(help)
|
||||
return help_cache[cache_key]
|
||||
|
||||
|
||||
def _get_management_status(evt: CommandEvent) -> None:
|
||||
def _get_management_status(evt: CommandEvent) -> str:
|
||||
if evt.is_management:
|
||||
return "This is a management room: prefixing commands with `$cmdprefix` is not required."
|
||||
elif evt.is_portal:
|
||||
@@ -65,5 +68,5 @@ def _get_management_status(evt: CommandEvent) -> None:
|
||||
@command_handler(needs_auth=False, needs_puppeting=False,
|
||||
help_section=SECTION_GENERAL,
|
||||
help_text="Show this help message.")
|
||||
async def help(evt: CommandEvent) -> None:
|
||||
async def help(evt: CommandEvent) -> Optional[Dict]:
|
||||
return await evt.reply(_get_management_status(evt) + "\n" + await _get_help_text(evt))
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#
|
||||
# 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/>.
|
||||
from typing import Optional, Callable
|
||||
from typing import Awaitable, Dict, Callable, Coroutine, Optional, Tuple, Union, cast
|
||||
import asyncio
|
||||
|
||||
from telethon.errors import (ChatAdminRequiredError, UsernameInvalidError,
|
||||
@@ -22,6 +22,7 @@ from telethon.errors import (ChatAdminRequiredError, UsernameInvalidError,
|
||||
from telethon.tl.types import ChatForbidden, ChannelForbidden
|
||||
from mautrix_appservice import MatrixRequestError, IntentAPI
|
||||
|
||||
from ..types import MatrixRoomId, TelegramId
|
||||
from .. import portal as po, user as u
|
||||
from . import (command_handler, CommandEvent,
|
||||
SECTION_ADMIN, SECTION_CREATING_PORTALS, SECTION_PORTAL_MANAGEMENT)
|
||||
@@ -31,7 +32,7 @@ from . import (command_handler, CommandEvent,
|
||||
help_section=SECTION_ADMIN,
|
||||
help_args="<_level_> [_mxid_]",
|
||||
help_text="Set a temporary power level without affecting Telegram.")
|
||||
async def set_power_level(evt: CommandEvent) -> None:
|
||||
async def set_power_level(evt: CommandEvent) -> Dict:
|
||||
try:
|
||||
level = int(evt.args[0])
|
||||
except KeyError:
|
||||
@@ -46,11 +47,12 @@ async def set_power_level(evt: CommandEvent) -> None:
|
||||
except MatrixRequestError:
|
||||
evt.log.exception("Failed to set power level.")
|
||||
return await evt.reply("Failed to set power level.")
|
||||
return {}
|
||||
|
||||
|
||||
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_text="Get a Telegram invite link to the current chat.")
|
||||
async def invite_link(evt: CommandEvent) -> None:
|
||||
async def invite_link(evt: CommandEvent) -> Dict:
|
||||
portal = po.Portal.get_by_mxid(evt.room_id)
|
||||
if not portal:
|
||||
return await evt.reply("This is not a portal room.")
|
||||
@@ -68,7 +70,7 @@ async def invite_link(evt: CommandEvent) -> None:
|
||||
|
||||
|
||||
async def user_has_power_level(room: str, intent, sender: u.User, event: str, default: int = 50
|
||||
) -> None:
|
||||
) -> bool:
|
||||
if sender.is_admin:
|
||||
return True
|
||||
# Make sure the state store contains the power levels.
|
||||
@@ -82,8 +84,9 @@ async def user_has_power_level(room: str, intent, sender: u.User, event: str, de
|
||||
|
||||
|
||||
async def _get_portal_and_check_permission(evt: CommandEvent, permission: str,
|
||||
action: Optional[str] = None) -> None:
|
||||
room_id = evt.args[0] if len(evt.args) > 0 else evt.room_id
|
||||
action: Optional[str] = None
|
||||
) -> Tuple[Union[Dict, po.Portal], bool]:
|
||||
room_id = MatrixRoomId(evt.args[0]) if len(evt.args) > 0 else evt.room_id
|
||||
|
||||
portal = po.Portal.get_by_mxid(room_id)
|
||||
if not portal:
|
||||
@@ -97,8 +100,8 @@ async def _get_portal_and_check_permission(evt: CommandEvent, permission: str,
|
||||
|
||||
|
||||
def _get_portal_murder_function(action: str, room_id: str, function: Callable, command: str,
|
||||
completed_message: str) -> None:
|
||||
async def post_confirm(confirm) -> None:
|
||||
completed_message: str) -> Dict:
|
||||
async def post_confirm(confirm) -> Optional[Dict]:
|
||||
confirm.sender.command_status = None
|
||||
if len(confirm.args) > 0 and confirm.args[0] == f"confirm-{command}":
|
||||
await function()
|
||||
@@ -106,6 +109,7 @@ def _get_portal_murder_function(action: str, room_id: str, function: Callable, c
|
||||
return await confirm.reply(completed_message)
|
||||
else:
|
||||
return await confirm.reply(f"{action} cancelled.")
|
||||
return None
|
||||
|
||||
return {
|
||||
"next": post_confirm,
|
||||
@@ -118,10 +122,11 @@ def _get_portal_murder_function(action: str, room_id: str, function: Callable, c
|
||||
help_text="Remove all users from the current portal room and forget the portal. "
|
||||
"Only works for group chats; to delete a private chat portal, simply "
|
||||
"leave the room.")
|
||||
async def delete_portal(evt: CommandEvent) -> None:
|
||||
portal, ok = await _get_portal_and_check_permission(evt, "unbridge")
|
||||
async def delete_portal(evt: CommandEvent) -> Optional[Dict]:
|
||||
result, ok = await _get_portal_and_check_permission(evt, "unbridge")
|
||||
if not ok:
|
||||
return
|
||||
return None
|
||||
portal = cast('po.Portal', result)
|
||||
|
||||
evt.sender.command_status = _get_portal_murder_function("Portal deletion", portal.mxid,
|
||||
portal.cleanup_and_delete, "delete",
|
||||
@@ -139,10 +144,11 @@ async def delete_portal(evt: CommandEvent) -> None:
|
||||
@command_handler(needs_auth=False, needs_puppeting=False,
|
||||
help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_text="Remove puppets from the current portal room and forget the portal.")
|
||||
async def unbridge(evt: CommandEvent) -> None:
|
||||
portal, ok = await _get_portal_and_check_permission(evt, "unbridge")
|
||||
async def unbridge(evt: CommandEvent) -> Optional[Dict]:
|
||||
result, ok = await _get_portal_and_check_permission(evt, "unbridge")
|
||||
if not ok:
|
||||
return
|
||||
return None
|
||||
portal = cast('po.Portal', result)
|
||||
|
||||
evt.sender.command_status = _get_portal_murder_function("Room unbridging", portal.mxid,
|
||||
portal.unbridge, "unbridge",
|
||||
@@ -158,11 +164,11 @@ async def unbridge(evt: CommandEvent) -> None:
|
||||
help_text="Bridge the current Matrix room to the Telegram chat with the given "
|
||||
"ID. The ID must be the prefixed version that you get with the `/id` "
|
||||
"command of the Telegram-side bot.")
|
||||
async def bridge(evt: CommandEvent) -> None:
|
||||
async def bridge(evt: CommandEvent) -> Dict:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** "
|
||||
"`$cmdprefix+sp bridge <Telegram chat ID> [Matrix room ID]`")
|
||||
room_id = evt.args[1] if len(evt.args) > 1 else evt.room_id
|
||||
room_id = MatrixRoomId(evt.args[1]) if len(evt.args) > 1 else evt.room_id
|
||||
that_this = "This" if room_id == evt.room_id else "That"
|
||||
|
||||
portal = po.Portal.get_by_mxid(room_id)
|
||||
@@ -173,12 +179,12 @@ async def bridge(evt: CommandEvent) -> None:
|
||||
return await evt.reply(f"You do not have the permissions to bridge {that_this} room.")
|
||||
|
||||
# The /id bot command provides the prefixed ID, so we assume
|
||||
tgid = evt.args[0]
|
||||
if tgid.startswith("-100"):
|
||||
tgid = int(tgid[4:])
|
||||
tgid_str = evt.args[0]
|
||||
if tgid_str.startswith("-100"):
|
||||
tgid = TelegramId(int(tgid_str[4:]))
|
||||
peer_type = "channel"
|
||||
elif tgid.startswith("-"):
|
||||
tgid = -int(tgid)
|
||||
elif tgid_str.startswith("-"):
|
||||
tgid = TelegramId(-int(tgid_str))
|
||||
peer_type = "chat"
|
||||
else:
|
||||
return await evt.reply("That doesn't seem like a prefixed Telegram chat ID.\n\n"
|
||||
@@ -224,7 +230,8 @@ async def bridge(evt: CommandEvent) -> None:
|
||||
"chat to this room, use `$cmdprefix+sp continue`")
|
||||
|
||||
|
||||
async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: "po.Portal") -> None:
|
||||
async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: "po.Portal"
|
||||
) -> Tuple[bool, Coroutine[None, None, None]]:
|
||||
if not portal.mxid:
|
||||
await evt.reply("The portal seems to have lost its Matrix room between you"
|
||||
"calling `$cmdprefix+sp bridge` and this command.\n\n"
|
||||
@@ -247,7 +254,7 @@ async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: "po.Porta
|
||||
return False, None
|
||||
|
||||
|
||||
async def confirm_bridge(evt: CommandEvent) -> None:
|
||||
async def confirm_bridge(evt: CommandEvent) -> Optional[Dict]:
|
||||
status = evt.sender.command_status
|
||||
try:
|
||||
portal = po.Portal.get_by_tgid(status["tgid"], peer_type=status["peer_type"])
|
||||
@@ -260,7 +267,7 @@ async def confirm_bridge(evt: CommandEvent) -> None:
|
||||
if "mxid" in status:
|
||||
ok, coro = await cleanup_old_portal_while_bridging(evt, portal)
|
||||
if not ok:
|
||||
return
|
||||
return None
|
||||
elif coro:
|
||||
asyncio.ensure_future(coro, loop=evt.loop)
|
||||
await evt.reply("Cleaning up previous portal room...")
|
||||
@@ -304,7 +311,7 @@ async def confirm_bridge(evt: CommandEvent) -> None:
|
||||
return await evt.reply("Bridging complete. Portal synchronization should begin momentarily.")
|
||||
|
||||
|
||||
async def get_initial_state(intent: IntentAPI, room_id: str) -> None:
|
||||
async def get_initial_state(intent: IntentAPI, room_id: str) -> Tuple[str, str, Dict]:
|
||||
state = await intent.get_room_state(room_id)
|
||||
title = None
|
||||
about = None
|
||||
@@ -330,7 +337,7 @@ async def get_initial_state(intent: IntentAPI, room_id: str) -> None:
|
||||
help_text="Create a Telegram chat of the given type for the current Matrix room. "
|
||||
"The type is either `group`, `supergroup` or `channel` (defaults to "
|
||||
"`group`).")
|
||||
async def create(evt: CommandEvent) -> None:
|
||||
async def create(evt: CommandEvent) -> Dict:
|
||||
type = evt.args[0] if len(evt.args) > 0 else "group"
|
||||
if type not in {"chat", "group", "supergroup", "channel"}:
|
||||
return await evt.reply(
|
||||
@@ -365,7 +372,7 @@ async def create(evt: CommandEvent) -> None:
|
||||
|
||||
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_text="Upgrade a normal Telegram group to a supergroup.")
|
||||
async def upgrade(evt: CommandEvent) -> None:
|
||||
async def upgrade(evt: CommandEvent) -> Dict:
|
||||
portal = po.Portal.get_by_mxid(evt.room_id)
|
||||
if not portal:
|
||||
return await evt.reply("This is not a portal room.")
|
||||
@@ -387,7 +394,7 @@ async def upgrade(evt: CommandEvent) -> None:
|
||||
help_args="<_name_|`-`>",
|
||||
help_text="Change the username of a supergroup/channel. "
|
||||
"To disable, use a dash (`-`) as the name.")
|
||||
async def group_name(evt: CommandEvent) -> None:
|
||||
async def group_name(evt: CommandEvent) -> Dict:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp group-name <name/->`")
|
||||
|
||||
@@ -423,7 +430,7 @@ async def group_name(evt: CommandEvent) -> None:
|
||||
help_args="<`whitelist`|`blacklist`>",
|
||||
help_text="Change whether the bridge will allow or disallow bridging rooms by "
|
||||
"default.")
|
||||
async def filter_mode(evt: CommandEvent) -> None:
|
||||
async def filter_mode(evt: CommandEvent) -> Dict:
|
||||
try:
|
||||
mode = evt.args[0]
|
||||
if mode not in ("whitelist", "blacklist"):
|
||||
@@ -448,19 +455,19 @@ async def filter_mode(evt: CommandEvent) -> None:
|
||||
help_section=SECTION_ADMIN,
|
||||
help_args="<`whitelist`|`blacklist`> <_chat ID_>",
|
||||
help_text="Allow or disallow bridging a specific chat.")
|
||||
async def filter(evt: CommandEvent) -> None:
|
||||
async def filter(evt: CommandEvent) -> Optional[Dict]:
|
||||
try:
|
||||
action = evt.args[0]
|
||||
if action not in ("whitelist", "blacklist", "add", "remove"):
|
||||
raise ValueError()
|
||||
|
||||
id = evt.args[1]
|
||||
if id.startswith("-100"):
|
||||
id = int(id[4:])
|
||||
elif id.startswith("-"):
|
||||
id = int(id[1:])
|
||||
id_str = evt.args[1]
|
||||
if id_str.startswith("-100"):
|
||||
id = int(id_str[4:])
|
||||
elif id_str.startswith("-"):
|
||||
id = int(id_str[1:])
|
||||
else:
|
||||
id = int(id)
|
||||
id = int(id_str)
|
||||
except (IndexError, ValueError):
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp filter <whitelist/blacklist> <chat ID>`")
|
||||
|
||||
@@ -490,3 +497,4 @@ async def filter(evt: CommandEvent) -> None:
|
||||
list.remove(id)
|
||||
save()
|
||||
return await evt.reply(f"Chat ID removed from {mode}.")
|
||||
return None
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
#
|
||||
# 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/>.
|
||||
from typing import Awaitable, Dict, List, Optional, Tuple
|
||||
import re
|
||||
|
||||
from telethon.errors import (
|
||||
InviteHashInvalidError, InviteHashExpiredError, UserAlreadyParticipantError)
|
||||
from telethon.tl.types import User as TLUser
|
||||
from telethon.tl.types import TypeUpdates
|
||||
from telethon.tl.functions.messages import ImportChatInviteRequest, CheckChatInviteRequest
|
||||
from telethon.tl.functions.channels import JoinChannelRequest
|
||||
|
||||
@@ -28,7 +31,7 @@ from . import command_handler, CommandEvent, SECTION_MISC, SECTION_CREATING_PORT
|
||||
@command_handler(help_section=SECTION_MISC,
|
||||
help_args="[_-r|--remote_] <_query_>",
|
||||
help_text="Search your contacts or the Telegram servers for users.")
|
||||
async def search(evt: CommandEvent) -> None:
|
||||
async def search(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp search [-r|--remote] <query>`")
|
||||
|
||||
@@ -49,7 +52,7 @@ async def search(evt: CommandEvent) -> None:
|
||||
"Minimum length of remote query is 5 characters.")
|
||||
return await evt.reply("No results 3:")
|
||||
|
||||
reply = []
|
||||
reply = [] # type: List[str]
|
||||
if remote:
|
||||
reply += ["**Results from Telegram server:**", ""]
|
||||
else:
|
||||
@@ -70,7 +73,7 @@ async def search(evt: CommandEvent) -> None:
|
||||
"either the internal user ID, the username or the phone number. "
|
||||
"**N.B.** The phone numbers you start chats with must already be in "
|
||||
"your contacts.")
|
||||
async def private_message(evt: CommandEvent) -> None:
|
||||
async def private_message(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp pm <user identifier>`")
|
||||
|
||||
@@ -89,7 +92,7 @@ async def private_message(evt: CommandEvent) -> None:
|
||||
f"{pu.Puppet.get_displayname(user, False)}")
|
||||
|
||||
|
||||
async def _join(evt: CommandEvent, arg: str) -> None:
|
||||
async def _join(evt: CommandEvent, arg: str) -> Tuple[TypeUpdates, Dict]:
|
||||
if arg.startswith("joinchat/"):
|
||||
invite_hash = arg[len("joinchat/"):]
|
||||
try:
|
||||
@@ -112,7 +115,7 @@ async def _join(evt: CommandEvent, arg: str) -> None:
|
||||
@command_handler(help_section=SECTION_CREATING_PORTALS,
|
||||
help_args="<_link_>",
|
||||
help_text="Join a chat with an invite link.")
|
||||
async def join(evt: CommandEvent) -> None:
|
||||
async def join(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp join <invite link>`")
|
||||
|
||||
@@ -123,7 +126,7 @@ async def join(evt: CommandEvent) -> None:
|
||||
|
||||
updates, _ = await _join(evt, arg.group(1))
|
||||
if not updates:
|
||||
return
|
||||
return None
|
||||
|
||||
for chat in updates.chats:
|
||||
portal = po.Portal.get_by_entity(chat)
|
||||
@@ -134,12 +137,13 @@ async def join(evt: CommandEvent) -> None:
|
||||
await evt.reply(f"Creating room for {chat.title}... This might take a while.")
|
||||
await portal.create_matrix_room(evt.sender, chat, [evt.sender.mxid])
|
||||
return await evt.reply(f"Created room for {portal.title}")
|
||||
return None
|
||||
|
||||
|
||||
@command_handler(help_section=SECTION_MISC,
|
||||
help_args="[`chats`|`contacts`|`me`]",
|
||||
help_text="Synchronize your chat portals, contacts and/or own info.")
|
||||
async def sync(evt: CommandEvent) -> None:
|
||||
async def sync(evt: CommandEvent) -> Optional[Dict]:
|
||||
if len(evt.args) > 0:
|
||||
sync_only = evt.args[0]
|
||||
if sync_only not in ("chats", "contacts", "me"):
|
||||
|
||||
Reference in New Issue
Block a user