Make help message dynamic based on permissions
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
from .handler import command_handler, CommandHandler, CommandEvent
|
||||
from .handler import (command_handler, command_handlers as _command_handlers,
|
||||
CommandHandler, CommandProcessor, CommandEvent,
|
||||
SECTION_GENERAL, SECTION_AUTH, SECTION_CREATING_PORTALS,
|
||||
SECTION_PORTAL_MANAGEMENT, SECTION_MISC, SECTION_ADMIN)
|
||||
from . import clean_rooms, auth, meta, telegram, portal
|
||||
|
||||
@@ -14,17 +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 Dict
|
||||
import asyncio
|
||||
|
||||
from telethon.errors import *
|
||||
|
||||
from . import command_handler
|
||||
from . import command_handler, CommandEvent, SECTION_AUTH
|
||||
from .. import puppet as pu
|
||||
from ..util import format_duration
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def ping(evt):
|
||||
@command_handler(needs_auth=False,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Check if you're logged into Telegram.")
|
||||
async def ping(evt: CommandEvent):
|
||||
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}")
|
||||
@@ -32,8 +35,10 @@ async def ping(evt):
|
||||
return await evt.reply("You're not logged in.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False)
|
||||
async def ping_bot(evt):
|
||||
@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):
|
||||
if not evt.tgbot:
|
||||
return await evt.reply("Telegram message relay bot not configured.")
|
||||
bot_info = await evt.tgbot.client.get_me()
|
||||
@@ -44,13 +49,11 @@ async def ping_bot(evt):
|
||||
"To use the bot, simply invite it to a portal room.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, management_only=True)
|
||||
def register(evt):
|
||||
return evt.reply("Not yet implemented.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, management_only=True)
|
||||
async def register(evt):
|
||||
@command_handler(needs_auth=False, management_only=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_args="<_phone_> <_full name_>",
|
||||
help_text="Register to Telegram")
|
||||
async def register(evt: CommandEvent):
|
||||
if await evt.sender.is_logged_in():
|
||||
return await evt.reply("You are already logged in.")
|
||||
elif len(evt.args) < 1:
|
||||
@@ -69,7 +72,7 @@ async def register(evt):
|
||||
})
|
||||
|
||||
|
||||
async def enter_code_register(evt):
|
||||
async def enter_code_register(evt: CommandEvent):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp <code>`")
|
||||
try:
|
||||
@@ -95,8 +98,10 @@ async def enter_code_register(evt):
|
||||
"Check console for more details.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, management_only=True)
|
||||
async def login(evt):
|
||||
@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):
|
||||
if await evt.sender.is_logged_in():
|
||||
return await evt.reply("You are already logged in.")
|
||||
|
||||
@@ -126,7 +131,7 @@ async def login(evt):
|
||||
return await evt.reply("This bridge instance has been configured to not allow logging in.")
|
||||
|
||||
|
||||
async def request_code(evt, phone_number, next_status):
|
||||
async def request_code(evt: CommandEvent, phone_number: str, next_status: Dict[str, str]):
|
||||
ok = False
|
||||
try:
|
||||
await evt.sender.ensure_started(even_if_no_session=True)
|
||||
@@ -158,7 +163,7 @@ async def request_code(evt, phone_number, next_status):
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def enter_phone(evt):
|
||||
async def enter_phone(evt: CommandEvent):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp enter-phone <phone>`")
|
||||
elif not evt.config.get("bridge.allow_matrix_login", True):
|
||||
@@ -173,7 +178,7 @@ async def enter_phone(evt):
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def enter_code(evt):
|
||||
async def enter_code(evt: CommandEvent):
|
||||
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):
|
||||
@@ -203,7 +208,7 @@ async def enter_code(evt):
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def enter_password(evt):
|
||||
async def enter_password(evt: CommandEvent):
|
||||
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):
|
||||
@@ -223,10 +228,10 @@ async def enter_password(evt):
|
||||
"Check console for more details.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
async def logout(evt):
|
||||
if not await evt.sender.is_logged_in():
|
||||
return await evt.reply("You're not logged in.")
|
||||
@command_handler(needs_auth=True,
|
||||
help_section=SECTION_AUTH,
|
||||
help_text="Log out from Telegram.")
|
||||
async def logout(evt: CommandEvent):
|
||||
if await evt.sender.log_out():
|
||||
return await evt.reply("Logged out successfully.")
|
||||
return await evt.reply("Failed to log out.")
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from mautrix_appservice import MatrixRequestError
|
||||
|
||||
from . import command_handler
|
||||
from . import command_handler, CommandEvent, SECTION_ADMIN
|
||||
from .. import puppet as pu, portal as po
|
||||
|
||||
|
||||
@@ -52,12 +52,10 @@ async def _find_rooms(intent):
|
||||
return management_rooms, unidentified_rooms, portals, empty_portals
|
||||
|
||||
|
||||
@command_handler(needs_admin=True, needs_auth=False, name="clean-rooms")
|
||||
async def clean_rooms(evt):
|
||||
if not evt.is_management:
|
||||
return await evt.reply("`clean-rooms` is a particularly spammy command. Please don't "
|
||||
"run it in non-management rooms.")
|
||||
|
||||
@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):
|
||||
management_rooms, unidentified_rooms, portals, empty_portals = await _find_rooms(evt.az.intent)
|
||||
|
||||
reply = ["#### Management rooms (M)"]
|
||||
|
||||
@@ -14,45 +14,38 @@
|
||||
#
|
||||
# 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 collections import namedtuple
|
||||
import markdown
|
||||
import logging
|
||||
|
||||
from telethon.errors import FloodWaitError
|
||||
|
||||
from ..util import format_duration
|
||||
from ..context import Context
|
||||
from .. import user as u
|
||||
|
||||
command_handlers = {}
|
||||
command_handlers = {} # type: Dict[str, CommandHandler]
|
||||
|
||||
HelpSection = namedtuple("HelpSection", "name order description")
|
||||
|
||||
def command_handler(needs_auth=True, management_only=False, needs_puppeting=True,
|
||||
needs_admin=False, name=None):
|
||||
def decorator(func):
|
||||
async def wrapper(evt):
|
||||
if management_only and not evt.is_management:
|
||||
return await evt.reply(f"`{evt.command}` is a restricted command:"
|
||||
"you may only run it in management rooms.")
|
||||
elif needs_auth and not await evt.sender.is_logged_in():
|
||||
return await evt.reply("This command requires you to be logged in.")
|
||||
elif needs_puppeting and not evt.sender.puppet_whitelisted:
|
||||
return await evt.reply("This command requires puppeting privileges.")
|
||||
elif needs_admin and not evt.sender.is_admin:
|
||||
return await evt.reply("This command requires administrator privileges.")
|
||||
return await func(evt)
|
||||
|
||||
command_handlers[name or func.__name__.replace("_", "-")] = wrapper
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
SECTION_GENERAL = HelpSection("General", 0, "")
|
||||
SECTION_AUTH = HelpSection("Authentication", 10, "")
|
||||
SECTION_CREATING_PORTALS = HelpSection("Creating portals", 20, "")
|
||||
SECTION_PORTAL_MANAGEMENT = HelpSection("Portal management", 30, "")
|
||||
SECTION_MISC = HelpSection("Miscellaneous", 40, "")
|
||||
SECTION_ADMIN = HelpSection("Administration", 50, "")
|
||||
|
||||
|
||||
class CommandEvent:
|
||||
def __init__(self, handler, room, sender, command, args, is_management, is_portal):
|
||||
self.az = handler.az
|
||||
self.log = handler.log
|
||||
self.loop = handler.loop
|
||||
self.tgbot = handler.tgbot
|
||||
self.config = handler.config
|
||||
self.command_prefix = handler.command_prefix
|
||||
def __init__(self, processor: "CommandProcessor", room: str, sender: u.User, command: str,
|
||||
args: List[str], is_management: bool, is_portal: bool):
|
||||
self.az = processor.az
|
||||
self.log = processor.log
|
||||
self.loop = processor.loop
|
||||
self.tgbot = processor.tgbot
|
||||
self.config = processor.config
|
||||
self.command_prefix = processor.command_prefix
|
||||
self.room_id = room
|
||||
self.sender = sender
|
||||
self.command = command
|
||||
@@ -60,7 +53,7 @@ class CommandEvent:
|
||||
self.is_management = is_management
|
||||
self.is_portal = is_portal
|
||||
|
||||
def reply(self, message, allow_html=False, render_markdown=True):
|
||||
def reply(self, message: str, allow_html: bool = False, render_markdown: bool = True):
|
||||
message = message.replace("$cmdprefix+sp ",
|
||||
"" if self.is_management else f"{self.command_prefix} ")
|
||||
message = message.replace("$cmdprefix", self.command_prefix)
|
||||
@@ -73,17 +66,78 @@ class CommandEvent:
|
||||
|
||||
|
||||
class CommandHandler:
|
||||
def __init__(self, handler: Callable[[CommandEvent], None],
|
||||
needs_auth: bool, needs_puppeting: bool, needs_admin: bool, management_only: bool,
|
||||
name: str, help_text: str, help_args: str, help_section: HelpSection):
|
||||
self._handler = handler
|
||||
self.needs_auth = needs_auth
|
||||
self.needs_puppeting = needs_puppeting
|
||||
self.needs_admin = needs_admin
|
||||
self.management_only = management_only
|
||||
self.name = name
|
||||
self._help_text = help_text
|
||||
self._help_args = help_args
|
||||
self.help_section = help_section
|
||||
|
||||
async def get_permission_error(self, evt: CommandEvent) -> Optional[str]:
|
||||
if self.management_only and not evt.is_management:
|
||||
return (f"`{evt.command}` is a restricted command: "
|
||||
"you may only run it in management rooms.")
|
||||
elif self.needs_puppeting and not evt.sender.puppet_whitelisted:
|
||||
return "This command requires puppeting privileges."
|
||||
elif self.needs_admin and not evt.sender.is_admin:
|
||||
return "This command requires administrator privileges."
|
||||
elif self.needs_auth and not await evt.sender.is_logged_in():
|
||||
return "This command requires you to be logged in."
|
||||
return None
|
||||
|
||||
def has_permission(self, is_management: bool, puppet_whitelisted: bool, is_admin: bool,
|
||||
is_logged_in: bool) -> bool:
|
||||
return ((not self.management_only or is_management) and
|
||||
(not self.needs_puppeting or puppet_whitelisted) and
|
||||
(not self.needs_admin or is_admin) and
|
||||
(not self.needs_auth or is_logged_in))
|
||||
|
||||
async def __call__(self, evt: CommandEvent):
|
||||
error = await self.get_permission_error(evt)
|
||||
if error is not None:
|
||||
return await evt.reply(error)
|
||||
return await self._handler(evt)
|
||||
|
||||
@property
|
||||
def has_help(self) -> bool:
|
||||
return bool(self.help_section) and bool(self._help_text)
|
||||
|
||||
@property
|
||||
def help(self) -> str:
|
||||
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_admin=False, management_only=False,
|
||||
name=None, help_text="", help_args="", help_section=None):
|
||||
input_name = name
|
||||
|
||||
def decorator(func: Callable[[CommandEvent], None]):
|
||||
name = input_name or func.__name__.replace("_", "-")
|
||||
handler = CommandHandler(func, needs_auth, needs_puppeting, needs_admin, management_only,
|
||||
name, help_text, help_args, help_section)
|
||||
command_handlers[handler.name] = handler
|
||||
return handler
|
||||
|
||||
return decorator if _func is None else decorator(_func)
|
||||
|
||||
|
||||
class CommandProcessor:
|
||||
log = logging.getLogger("mau.commands")
|
||||
|
||||
def __init__(self, context):
|
||||
def __init__(self, context: Context):
|
||||
self.az, self.db, self.config, self.loop, self.tgbot = context
|
||||
self.command_prefix = self.config["bridge.command_prefix"]
|
||||
|
||||
# region Utility functions for handling commands
|
||||
|
||||
async def handle(self, room, sender, command, args, is_management, is_portal):
|
||||
evt = CommandEvent(self, room, sender, command, args,
|
||||
is_management, is_portal)
|
||||
async def handle(self, room: str, sender: u.User, command: str, args: List[str],
|
||||
is_management: bool, is_portal: bool):
|
||||
evt = CommandEvent(self, room, sender, command, args, is_management, is_portal)
|
||||
orig_command = command
|
||||
command = command.lower()
|
||||
try:
|
||||
@@ -100,7 +154,7 @@ class CommandHandler:
|
||||
except FloodWaitError as e:
|
||||
return await evt.reply(f"Flood error: Please wait {format_duration(e.seconds)}")
|
||||
except Exception:
|
||||
self.log.exception("Fatal error handling command "
|
||||
self.log.exception("Unhandled error while handling command "
|
||||
f"{evt.command} {' '.join(args)} from {sender.mxid}")
|
||||
return await evt.reply("Fatal error while handling command. "
|
||||
return await evt.reply("Unhandled error while handling command. "
|
||||
"Check logs for more details.")
|
||||
|
||||
@@ -14,11 +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 . import command_handler
|
||||
from . import command_handler, CommandEvent, _command_handlers, SECTION_GENERAL
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False)
|
||||
def cancel(evt):
|
||||
@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):
|
||||
if evt.sender.command_status:
|
||||
action = evt.sender.command_status["action"]
|
||||
evt.sender.command_status = None
|
||||
@@ -28,78 +30,39 @@ def cancel(evt):
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False)
|
||||
def unknown_command(evt):
|
||||
def unknown_command(evt: CommandEvent):
|
||||
return evt.reply("Unknown command. Try `$cmdprefix+sp help` for help.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False)
|
||||
def help(evt):
|
||||
help_cache = {}
|
||||
|
||||
|
||||
async def _get_help_text(evt: CommandEvent):
|
||||
cache_key = (evt.is_management, evt.sender.puppet_whitelisted, evt.sender.is_admin,
|
||||
await evt.sender.is_logged_in())
|
||||
if cache_key not in help_cache:
|
||||
help = {}
|
||||
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_cache[cache_key] = "\n".join(help)
|
||||
return help_cache[cache_key]
|
||||
|
||||
|
||||
def _get_management_status(evt: CommandEvent):
|
||||
if evt.is_management:
|
||||
management_status = ("This is a management room: prefixing commands "
|
||||
"with `$cmdprefix` is not required.\n")
|
||||
return "This is a management room: prefixing commands with `$cmdprefix` is not required."
|
||||
elif evt.is_portal:
|
||||
management_status = ("**This is a portal room**: you must always "
|
||||
"prefix commands with `$cmdprefix`.\n"
|
||||
"Management commands will not be sent to Telegram.")
|
||||
else:
|
||||
management_status = ("**This is not a management room**: you must "
|
||||
"prefix commands with `$cmdprefix`.\n")
|
||||
help = None
|
||||
if not evt.sender.puppet_whitelisted:
|
||||
help = """\n
|
||||
#### Generic bridge commands
|
||||
**help** - Show this help message.
|
||||
**cancel** - Cancel an ongoing action (such as login).
|
||||
**ping-bot** - Get info of the message relay Telegram bot.
|
||||
**invite-link** - Get a Telegram invite link to the current chat.
|
||||
**delete-portal** - 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.
|
||||
**unbridge** - Remove puppets from the current portal room and forget the portal.
|
||||
**bridge** [_id_] - 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.
|
||||
return ("**This is a portal room**: you must always prefix commands with `$cmdprefix`.\n"
|
||||
"Management commands will not be sent to Telegram.")
|
||||
return "**This is not a management room**: you must prefix commands with `$cmdprefix`."
|
||||
|
||||
"""
|
||||
help = help or """\n
|
||||
#### Generic bridge commands
|
||||
**help** - Show this help message.
|
||||
**cancel** - Cancel an ongoing action (such as login).
|
||||
|
||||
#### Authentication
|
||||
**login** - Request an authentication code.
|
||||
**logout** - Log out from Telegram.
|
||||
**ping** - Check if you're logged into Telegram.
|
||||
|
||||
#### Miscellaneous things
|
||||
**search** [_-r|--remote_] <_query_> - Search your contacts or the Telegram servers for users.
|
||||
**sync** [`chats`|`contacts`|`me`] - Synchronize your chat portals, contacts and/or own info.
|
||||
**ping-bot** - Get info of the message relay Telegram bot.
|
||||
**set-pl** <_level_> [_mxid_] - Set a temporary power level without affecting Telegram.
|
||||
|
||||
#### Initiating chats
|
||||
**pm** <_identifier_> - Open a private chat with the given Telegram user. The identifier is either
|
||||
the internal user ID, the username or the phone number.
|
||||
**join** <_link_> - Join a chat with an invite link.
|
||||
**create** [_type_] - Create a Telegram chat of the given type for the current Matrix room. The
|
||||
type is either `group`, `supergroup` or `channel` (defaults to `group`).
|
||||
|
||||
#### Portal management
|
||||
**upgrade** - Upgrade a normal Telegram group to a supergroup.
|
||||
**invite-link** - Get a Telegram invite link to the current chat.
|
||||
**delete-portal** - 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.
|
||||
**unbridge** - Remove puppets from the current portal room and forget the portal.
|
||||
**bridge** [_id_] - 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.
|
||||
**group-name** <_name_|`-`> - Change the username of a supergroup/channel. To disable, use a dash
|
||||
(`-`) as the name.
|
||||
**clean-rooms** - Clean up unused portal/management rooms.
|
||||
|
||||
**filter** <`whitelist`|`blacklist`> <_chat ID_> - Allow or disallow bridging a specific chat.
|
||||
**filter-mode** <`whitelist`|`blacklist`> - Change whether the bridge will allow or disallow
|
||||
bridging rooms by default.
|
||||
"""
|
||||
return evt.reply(management_status + help)
|
||||
@command_handler(needs_auth=False, needs_puppeting=False,
|
||||
help_section=SECTION_GENERAL,
|
||||
help_text="Show this help message.")
|
||||
async def help(evt: CommandEvent):
|
||||
return await evt.reply(_get_management_status(evt) + "\n" + await _get_help_text(evt))
|
||||
|
||||
@@ -14,17 +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 Optional, Callable
|
||||
import asyncio
|
||||
|
||||
from telethon.errors import *
|
||||
from telethon.tl.types import ChatForbidden, ChannelForbidden
|
||||
from mautrix_appservice import MatrixRequestError
|
||||
|
||||
from .. import portal as po
|
||||
from . import command_handler, CommandEvent
|
||||
from .. import portal as po, user as u
|
||||
from . import command_handler, CommandEvent, SECTION_ADMIN, SECTION_CREATING_PORTALS, SECTION_PORTAL_MANAGEMENT
|
||||
|
||||
|
||||
@command_handler(needs_admin=True, needs_auth=False, name="set-pl")
|
||||
@command_handler(needs_admin=True, needs_auth=False, name="set-pl",
|
||||
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):
|
||||
try:
|
||||
level = int(evt.args[0])
|
||||
@@ -42,7 +46,8 @@ async def set_power_level(evt: CommandEvent):
|
||||
return await evt.reply("Failed to set power level.")
|
||||
|
||||
|
||||
@command_handler()
|
||||
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_text="Get a Telegram invite link to the current chat.")
|
||||
async def invite_link(evt: CommandEvent):
|
||||
portal = po.Portal.get_by_mxid(evt.room_id)
|
||||
if not portal:
|
||||
@@ -60,7 +65,7 @@ async def invite_link(evt: CommandEvent):
|
||||
return await evt.reply("You don't have the permission to create an invite link.")
|
||||
|
||||
|
||||
async def _has_access_to(room, intent, sender, event, default=50):
|
||||
async def _has_access_to(room: str, intent, sender: u.User, event: str, default: int = 50):
|
||||
if sender.is_admin:
|
||||
return True
|
||||
# Make sure the state store contains the power levels.
|
||||
@@ -73,7 +78,8 @@ async def _has_access_to(room, intent, sender, event, default=50):
|
||||
default=default)
|
||||
|
||||
|
||||
async def _get_portal_and_check_permission(evt, permission, action=None):
|
||||
async def _get_portal_and_check_permission(evt: CommandEvent, permission: str,
|
||||
action: Optional[str] = None):
|
||||
room_id = evt.args[0] if len(evt.args) > 0 else evt.room_id
|
||||
|
||||
portal = po.Portal.get_by_mxid(room_id)
|
||||
@@ -87,7 +93,8 @@ async def _get_portal_and_check_permission(evt, permission, action=None):
|
||||
return portal, True
|
||||
|
||||
|
||||
def _get_portal_murder_function(action, room_id, function, command, completed_message):
|
||||
def _get_portal_murder_function(action: str, room_id: str, function: Callable, command: str,
|
||||
completed_message: str):
|
||||
async def post_confirm(confirm):
|
||||
confirm.sender.command_status = None
|
||||
if len(confirm.args) > 0 and confirm.args[0] == f"confirm-{command}":
|
||||
@@ -103,7 +110,11 @@ def _get_portal_murder_function(action, room_id, function, command, completed_me
|
||||
}
|
||||
|
||||
|
||||
@command_handler(needs_auth=False, needs_puppeting=False)
|
||||
@command_handler(needs_auth=False, needs_puppeting=False,
|
||||
help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
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):
|
||||
portal, ok = await _get_portal_and_check_permission(evt, "delete_portal")
|
||||
if not ok:
|
||||
@@ -122,7 +133,9 @@ async def delete_portal(evt: CommandEvent):
|
||||
"bridge, use `$cmdprefix+sp unbridge` instead.")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
@command_handler(needs_auth=False,
|
||||
help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_text="Remove puppets from the current portal room and forget the portal.")
|
||||
async def unbridge(evt: CommandEvent):
|
||||
portal, ok = await _get_portal_and_check_permission(evt, "unbridge_room")
|
||||
if not ok:
|
||||
@@ -136,7 +149,12 @@ async def unbridge(evt: CommandEvent):
|
||||
"by typing `$cmdprefix+sp confirm-unbridge`")
|
||||
|
||||
|
||||
@command_handler(needs_auth=False)
|
||||
@command_handler(needs_auth=False,
|
||||
help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_args="[_id_]",
|
||||
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):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** "
|
||||
@@ -168,7 +186,7 @@ async def bridge(evt: CommandEvent):
|
||||
portal = po.Portal.get_by_tgid(tgid, peer_type=peer_type)
|
||||
if not portal.allow_bridging():
|
||||
return await evt.reply("This bridge doesn't allow bridging that Telegram chat.\n"
|
||||
"If you're the bridge admin, try"
|
||||
"If you're the bridge admin, try "
|
||||
"`$cmdprefix+sp whitelist <Telegram chat ID>` first.")
|
||||
if portal.mxid:
|
||||
has_portal_message = (
|
||||
@@ -203,7 +221,7 @@ async def bridge(evt: CommandEvent):
|
||||
"chat to this room, use `$cmdprefix+sp continue`")
|
||||
|
||||
|
||||
async def cleanup_old_portal_while_bridging(evt, portal):
|
||||
async def cleanup_old_portal_while_bridging(evt: CommandEvent, portal: po.Portal):
|
||||
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"
|
||||
@@ -253,7 +271,7 @@ async def confirm_bridge(evt: CommandEvent):
|
||||
"`$cmdprefix+sp cancel` to cancel.")
|
||||
|
||||
is_logged_in = await evt.sender.is_logged_in()
|
||||
user = evt.sender if is_logged_in else evt.tgbot
|
||||
user = evt.sender if is_logged_in else evt.tgbot
|
||||
try:
|
||||
entity = await user.client.get_entity(portal.peer)
|
||||
except Exception:
|
||||
@@ -300,7 +318,11 @@ async def _get_initial_state(evt: CommandEvent):
|
||||
return title, about, levels
|
||||
|
||||
|
||||
@command_handler()
|
||||
@command_handler(help_section=SECTION_CREATING_PORTALS,
|
||||
help_args="[_type_]",
|
||||
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):
|
||||
type = evt.args[0] if len(evt.args) > 0 else "group"
|
||||
if type not in {"chat", "group", "supergroup", "channel"}:
|
||||
@@ -331,7 +353,8 @@ async def create(evt: CommandEvent):
|
||||
return await evt.reply(f"Telegram chat created. ID: {portal.tgid}")
|
||||
|
||||
|
||||
@command_handler()
|
||||
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
help_text="Upgrade a normal Telegram group to a supergroup.")
|
||||
async def upgrade(evt: CommandEvent):
|
||||
portal = po.Portal.get_by_mxid(evt.room_id)
|
||||
if not portal:
|
||||
@@ -350,7 +373,10 @@ async def upgrade(evt: CommandEvent):
|
||||
return await evt.reply(e.args[0])
|
||||
|
||||
|
||||
@command_handler()
|
||||
@command_handler(help_section=SECTION_PORTAL_MANAGEMENT,
|
||||
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):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp group-name <name/->`")
|
||||
@@ -382,7 +408,11 @@ async def group_name(evt: CommandEvent):
|
||||
return await evt.reply("Invalid username")
|
||||
|
||||
|
||||
@command_handler(needs_admin=True)
|
||||
@command_handler(needs_admin=True,
|
||||
help_section=SECTION_ADMIN,
|
||||
help_args="<`whitelist`|`blacklist`>",
|
||||
help_text="Change whether the bridge will allow or disallow bridging rooms by "
|
||||
"default.")
|
||||
async def filter_mode(evt: CommandEvent):
|
||||
try:
|
||||
mode = evt.args[0]
|
||||
@@ -404,7 +434,10 @@ async def filter_mode(evt: CommandEvent):
|
||||
"`!filter blacklist <chat ID>`.")
|
||||
|
||||
|
||||
@command_handler(needs_admin=True)
|
||||
@command_handler(needs_admin=True,
|
||||
help_section=SECTION_ADMIN,
|
||||
help_args="<`whitelist`|`blacklist`> <_chat ID_>",
|
||||
help_text="Allow or disallow bridging a specific chat.")
|
||||
async def filter(evt: CommandEvent):
|
||||
try:
|
||||
action = evt.args[0]
|
||||
|
||||
@@ -20,11 +20,13 @@ from telethon.tl.functions.messages import ImportChatInviteRequest, CheckChatInv
|
||||
from telethon.tl.functions.channels import JoinChannelRequest
|
||||
|
||||
from .. import puppet as pu, portal as po
|
||||
from . import command_handler
|
||||
from . import command_handler, CommandEvent, SECTION_MISC, SECTION_CREATING_PORTALS
|
||||
|
||||
|
||||
@command_handler()
|
||||
async def search(evt):
|
||||
@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):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp search [-r|--remote] <query>`")
|
||||
|
||||
@@ -59,8 +61,14 @@ async def search(evt):
|
||||
return await evt.reply("\n".join(reply))
|
||||
|
||||
|
||||
@command_handler(name="pm")
|
||||
async def private_message(evt):
|
||||
@command_handler(name="pm",
|
||||
help_section=SECTION_CREATING_PORTALS,
|
||||
help_args="<_identifier_>",
|
||||
help_text="Open a private chat with the given Telegram user. The identifier is "
|
||||
"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):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp pm <user identifier>`")
|
||||
|
||||
@@ -79,7 +87,7 @@ async def private_message(evt):
|
||||
f"{pu.Puppet.get_displayname(user, False)}")
|
||||
|
||||
|
||||
async def _join(evt, arg):
|
||||
async def _join(evt: CommandEvent, arg: str):
|
||||
if arg.startswith("joinchat/"):
|
||||
invite_hash = arg[len("joinchat/"):]
|
||||
try:
|
||||
@@ -99,8 +107,10 @@ async def _join(evt, arg):
|
||||
return await evt.sender.client(JoinChannelRequest(channel)), None
|
||||
|
||||
|
||||
@command_handler()
|
||||
async def join(evt):
|
||||
@command_handler(help_section=SECTION_CREATING_PORTALS,
|
||||
help_args="<_link_>",
|
||||
help_text="Join a chat with an invite link.")
|
||||
async def join(evt: CommandEvent):
|
||||
if len(evt.args) == 0:
|
||||
return await evt.reply("**Usage:** `$cmdprefix+sp join <invite link>`")
|
||||
|
||||
@@ -124,8 +134,10 @@ async def join(evt):
|
||||
return await evt.reply(f"Created room for {portal.title}")
|
||||
|
||||
|
||||
@command_handler()
|
||||
async def sync(evt):
|
||||
@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):
|
||||
if len(evt.args) > 0:
|
||||
sync_only = evt.args[0]
|
||||
if sync_only not in ("chats", "contacts", "me"):
|
||||
|
||||
@@ -23,7 +23,7 @@ from mautrix_appservice import MatrixRequestError, IntentError
|
||||
from .user import User
|
||||
from .portal import Portal
|
||||
from .puppet import Puppet
|
||||
from .commands import CommandHandler
|
||||
from .commands import CommandProcessor
|
||||
|
||||
|
||||
class MatrixHandler:
|
||||
@@ -31,7 +31,7 @@ class MatrixHandler:
|
||||
|
||||
def __init__(self, context):
|
||||
self.az, self.db, self.config, _, self.tgbot = context
|
||||
self.commands = CommandHandler(context)
|
||||
self.commands = CommandProcessor(context)
|
||||
|
||||
self.az.matrix_event_handler(self.handle_event)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user