teamtalkbot/bot/commands/__init__.py
Umiko 411f359921
Some checks are pending
Build-nightly / docker (push) Waiting to run
add new [e] command.
2025-05-05 04:25:04 +07:00

269 lines
11 KiB
Python

from __future__ import annotations
import logging
import re
from threading import Thread
from typing import Any, List, TYPE_CHECKING, Tuple
from bot import app_vars, errors
from bot.TeamTalk.structs import Message, User, UserType
from bot.commands import admin_commands, user_commands
from bot.commands.task_processor import TaskProcessor
re_command = re.compile("[a-z]+")
re_arg_split = re.compile(r"(?<!\\)\|")
if TYPE_CHECKING:
from bot import Bot
from bot.config.models import CronEntryModel
class CommandProcessor:
def __init__(self, bot: Bot):
self.task_processor = TaskProcessor(self)
self.bot = bot
self.config = bot.config
self.config_manager = bot.config_manager
self.cache = bot.cache
self.cache_manager = bot.cache_manager
self.module_manager = bot.module_manager
self.player = bot.player
self.periodic_player = bot.periodic_player
self.service_manager = bot.service_manager
self.ttclient = bot.ttclient
self.translator = bot.translator
self.locked = False
self.current_command_id = 0
self.commands_dict = {
"start": user_commands.StartBottt,
"about": user_commands.AboutCommand,
"contacts": user_commands.ContactsBot,
"help": user_commands.HelpCommand,
"p": user_commands.PlayPauseCommand,
"e": user_commands.QueueCommand,
"u": user_commands.PlayUrlCommand,
"sv": user_commands.ServiceCommand,
"s": user_commands.StopCommand,
"b": user_commands.PreviousTrackCommand,
"n": user_commands.NextTrackCommand,
"c": user_commands.SelectTrackCommand,
"sb": user_commands.SeekBackCommand,
"sf": user_commands.SeekForwardCommand,
"sbf": user_commands.DefaultSeekStepCommand,
"v": user_commands.VolumeCommand,
"sp": user_commands.SpeedCommand,
"f": user_commands.FavoritesCommand,
"m": user_commands.ModeCommand,
"gl": user_commands.GetLinkCommand,
"dl": user_commands.DownloadCommand,
"r": user_commands.RecentsCommand,
"log":user_commands.ChangeLogCommand,
}
self.admin_commands_dict = {
"bc": admin_commands.BlockCommandCommand,
"bmsg": admin_commands.SendBroadcastMessages,
"cc": admin_commands.ClearCacheCommand,
"sds": admin_commands.ShowDefaultServerCommand,
"cds": admin_commands.DefaultServerCommand,
"cdt": admin_commands.DefaultTCPPortCommand,
"cdd": admin_commands.DefaultUDPPortCommand,
"cdu": admin_commands.DefaultUsernameCommand,
"cdup": admin_commands.DefaultPasswordCommand,
"cdc": admin_commands.DefaultChannelCommand,
"cdcp": admin_commands.DefaultChannelPasswordCommand,
"cdid": admin_commands.GetChannelIDCommand,
"cg": admin_commands.ChangeGenderCommand,
"cl": admin_commands.ChangeLanguageCommand,
"cmsg": admin_commands.SendChannelMessages,
"cn": admin_commands.ChangeNicknameCommand,
"cs": admin_commands.ChangeStatusCommand,
"cr": admin_commands.SchedulerCommand,
"jc": admin_commands.JoinChannelCommand,
# "jcj": admin_commands.CreateChannelCommand,
# "ts": TaskSchedulerCommand,
"lll": admin_commands.LockCommand,
"ua": admin_commands.AdminUsersCommand,
"ub": admin_commands.BannedUsersCommand,
"eh": admin_commands.EventHandlingCommand,
"sc": admin_commands.SaveConfigCommand,
#"va": admin_commands.VoiceTransmissionCommand,
"rsrs": admin_commands.RestartCommand,
"qqq": admin_commands.QuitCommand,
}
def run(self):
self.task_processor.start()
def __call__(self, message: Message) -> None:
command_thread = Thread(target=self._run, args=(message,))
command_thread.start()
def _run(self, message: Message) -> None:
try:
command_name, arg = self.parse_command(message.text)
if self.check_access(message.user, command_name):
command_class = self.get_command(command_name, message.user)
command = command_class(self)
self.current_command_id = id(command)
result = command(arg, message.user)
if result:
self.ttclient.send_message(
result,
message.user,
) # here was command.ttclient later
except errors.InvalidArgumentError:
self.ttclient.send_message(
self.help(command_name, message.user),
message.user,
)
except errors.AccessDeniedError as e:
self.ttclient.send_message(str(e), message.user)
except (errors.ParseCommandError, errors.UnknownCommandError):
self.ttclient.send_message(
self.translator.translate('Unknown command. Send "start" for help.'),
message.user,
)
except Exception as e:
logging.error("", exc_info=True)
self.ttclient.send_message(
self.translator.translate("Error: {}").format(str(e)),
message.user,
)
def check_access(self, user: User, command: str) -> bool:
if (
not user.is_admin and user.type != UserType.Admin
) or app_vars.app_name in user.client_name:
if app_vars.app_name in user.client_name:
raise errors.AccessDeniedError("")
elif user.is_banned:
raise errors.AccessDeniedError(
self.translator.translate("You are banned"),
)
elif user.channel.id != self.ttclient.channel.id:
raise errors.AccessDeniedError(
self.translator.translate("You are not in bot's channel"),
)
elif self.locked:
raise errors.AccessDeniedError(
self.translator.translate("Bot is locked"),
)
elif command in self.config.general.blocked_commands:
raise errors.AccessDeniedError(
self.translator.translate("This command is blocked"),
)
else:
return True
else:
return True
def get_command(self, command: str, user: Optional[User]) -> Any:
if command in self.commands_dict:
return self.commands_dict[command]
elif (
(user is not None)
and (user.is_admin or user.type == UserType.Admin)
and command in self.admin_commands_dict
):
return self.admin_commands_dict[command]
else:
raise errors.UnknownCommandError()
def help(self, arg: str, user: User) -> str:
if arg:
if arg in self.commands_dict:
return "{} {}".format(arg, self.commands_dict[arg](self).help)
elif user.is_admin and arg in self.admin_commands_dict:
return "{} {}".format(arg, self.admin_commands_dict[arg](self).help)
else:
return self.translator.translate("Unknown command")
else:
help_strings: List[str] = []
for i in list(self.commands_dict):
help_strings.append(self.help(i, user))
if user.is_admin:
for i in list(self.admin_commands_dict):
help_strings.append(self.help(i, user))
return "\n".join(help_strings)
def parse_command(self, text: str) -> Tuple[str, str]:
text = text.strip()
try:
command = re.findall(re_command, text.split(" ")[0].lower())[0]
except IndexError:
raise errors.ParseCommandError()
arg = " ".join(text.split(" ")[1::])
return command, arg
def split_arg(self, arg: str) -> List[str]:
args = re.split(re_arg_split, arg)
for i, arg in enumerate(args):
args[i] = args[i].strip().replace("\\|", "|")
return args
class ScheduledCommandProcessor(CommandProcessor):
"""Command processor, specifically tailored for scheduled tasks. Takes a CronEntry instead of a message."""
def __init__(self, bot: Bot):
super().__init__(bot)
def __call__(self, task: CronEntryModel) -> None:
command_thread = Thread(target=self._run, args=(task,))
command_thread.start()
def _run(self, task: CronEntryModel) -> None:
"""Takes a cron task and runs it. Doesn't provide user to the command, doesn't check access (as there is no user to check). Logs errors and reports them to the channel also if that is enabled. Cron tasks can run admin commands."""
try:
command_name, arg = self.parse_command(task.command)
if (
command_name in self.commands_dict
or command_name in self.admin_commands_dict
):
command_class = self.get_command(command_name, None)
command = command_class(self)
self.current_command_id = id(command)
result = command(arg, None)
if result:
logger.info(
f"Successfully ran cron command '{task.command}; result: {result}"
)
self.ttclient.send_message(
result,
message.user,
)
except errors.InvalidArgumentError:
log.error(
f"Invalid argument for scheduled command '{task.command}'; cron pattern: {task.pattern}"
)
if self.config.general.send_channel_messages:
self.ttclient.send_message(
self.translator.translate(
f"Scheduled command '{task.command}' failed: invalid argument"
),
type=2,
)
except errors.AccessDeniedError as e:
log.error(
f"Got access denied while running scheduled task '{task.command}'; cron pattern: {task.pattern}"
)
except (errors.ParseCommandError, errors.UnknownCommandError):
if self.config.general.send_channel_messages:
self.ttclient.send_message(
self.translator.translate(
f"Unknown scheduled command: '{task.command}'."
),
type=2,
)
except Exception as e:
logging.error(
f"Error running scheduled command '{task.command}", exc_info=True
)
if self.config.general.send_channel_messages:
self.ttclient.send_message(
self.translator.translate(
f"Error running scheduled command '{task.command}: {e}"
),
type=2,
)