class Pladdble: ''' Pladdble is class that helps pladder to interface with a mumble server. ''' def __init__(self, bot, network): self.connector = RetryProxy(bot.bus, f'se.raek.PladderConnector.{network}') def connected_users(self) -> str: users = self.connector.GetChannelUsers('Root', on_error=lambda e: e) if users is None: return 'Icke ansluten till servern' return f'Antalet anslutna nötter är: {len(users) - 1}' # Exclude the bot itself def list_users(self) -> str: config = self.connector.GetConfig(on_error=lambda e: None) if self is None: return 'Icke ansluten till servern' self_nick = config['user'] users = self.connector.GetChannelUsers('Root', on_error=lambda e: None) if users is None: return 'Icke ansluten till servern' users.remove(self_nick) # Remove the bot itself from the list return ", ".join(users) def get_info(self) -> str: config = self.connector.GetConfig(on_error=lambda e: None) if self is None: return 'Icke ansluten till servern' info_string = [ f'Bot name: {config["user"]}', f'Server address: {config["host"]}', f'Port: {config["port"]}', f'Network: {config["network"]}', ] return ' '.join(info_string)
def __init__(self, config, client): super().__init__() self.config = config bus = SessionBus() self.bot = RetryProxy(bus, "se.raek.PladderBot") self.connector = PladderConnector(bus, config, client) self.enter_context(dbus_loop())
def __init__(self, config, client): super().__init__() self.config = config bus = SessionBus() self.bot = RetryProxy(bus, "se.raek.PladderBot") self.connector = PladderConnector(bus, config, client) self.running = False self.exe = None self.loop = None self.loop_future = None
def channels(self, context, network=None): if network is None: network = context.metadata["network"] connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") channels = connector.GetChannels(on_error=lambda e: None) if channels is None: return f"Not connected to network {network}." else: if any(map(lambda c: " " in c, channels)): channels = ["{" + c + "}" for c in channels] return f"{network}: {', '.join(channels)}"
def connector_config(self, context, network=None): if network is None: network = context.metadata["network"] connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") config = connector.GetConfig(on_error=lambda e: None) if config is None: return f"Not connected to network {network}." else: parts = [] for key, value in config.items(): parts.append(f"{key}={repr(value)}") return f"{network}: {', '.join(parts)}"
def users(self, context, network_and_channel=""): parts = network_and_channel.split("/") if len(parts) != 2: return "Invalid argument. Syntax: NetworkName/#channel" network, channel = parts if not network: network = context.metadata["network"] connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") users = connector.GetChannelUsers(channel, on_error=lambda e: None) if users is None: return f"Not connected to network {network}." else: return f"{network}/{channel}: {', '.join(sorted(users))}"
class DbusHook(Hook, ExitStack): def __init__(self, config, client): super().__init__() self.config = config bus = SessionBus() self.bot = RetryProxy(bus, "se.raek.PladderBot") self.connector = PladderConnector(bus, config, client) self.enter_context(dbus_loop()) def on_trigger(self, timestamp, channel, sender, text): return self.bot.RunCommand(timestamp, self.config.network, channel, sender, text, on_error=self._handle_bot_error) def _handle_bot_error(self, e): if "org.freedesktop.DBus.Error.ServiceUnknown" in str(e): return { "text": "Internal error: could not reach pladder-bot. " + "Please check the log: \"journalctl --user-unit pladder-bot.service -e\"", "command": "error", } else: logger.error(str(e)) return { "text": "Internal error: " + str(e), "command": "error", }
def pladder_plugin(bot): connector = RetryProxy(bot.bus, f'se.raek.PladderConnector.{NETWORK}') pladdble = Pladdble(connector) cmds = bot.new_command_group("pladdble") cmds.register_command('mömb', pladdble.connected_users) cmds.register_command('mömb-users', pladdble.list_users) cmds.register_command('mömb-info', pladdble.get_info) yield
class DbusHook(Hook): def __init__(self, config, client): super().__init__() self.config = config bus = SessionBus() self.bot = RetryProxy(bus, "se.raek.PladderBot") self.connector = PladderConnector(bus, config, client) self.running = False self.exe = None self.loop = None self.loop_future = None def __enter__(self): assert not self.running self.exe = ThreadPoolExecutor(max_workers=1).__enter__() self.loop = GLib.MainLoop() self.loop_future = self.exe.submit(self.loop.run) self.running = True logger.info("Dbus thread started") return self def __exit__(self, exc_type, exc_value, traceback): assert self.running # Signal loop to stop self.loop.quit() self.loop = None # Wait for loop task to finish self.loop_future.result() self.loop_future = None # Wait for executor to shut down self.exe.__exit__(None, None, None) self.exe = None # Everything is torn down self.running = False logger.info("Dbus thread stopped") return None def on_trigger(self, timestamp, channel, sender, text): return self.bot.RunCommand(timestamp, self.config.network, channel, sender, text, on_error=self._handle_bot_error) def _handle_bot_error(self, e): if "org.freedesktop.DBus.Error.ServiceUnknown" in str(e): return { "text": "Internal error: could not reach pladder-bot. " + "Please check the log: \"journalctl --user-unit pladder-bot.service -e\"", "command": "error", } else: logger.error(str(e)) return { "text": "Internal error: " + str(e), "command": "error", }
def pladder_plugin(bot): web_api = RetryProxy(bot.bus, "se.raek.PladderWebApi") web_commands = WebCommands(web_api) cmds = bot.new_command_group("web") cmds.register_command("create-token", web_commands.create_token, contextual=True) cmds.register_command("show-token", web_commands.show_token) cmds.register_command("list-tokens", web_commands.list_tokens) cmds.register_command("delete-token", web_commands.delete_token) yield
def send(self, context, target, user_text): if "send_called" in context.metadata: return "Only one send per script is allowed." context.metadata["send_called"] = True target_parts = target.split("/") if len(target_parts) != 2: return "Invalid target. Syntax: NetworkName/#channel" network, channel = target_parts if not network: network = context.metadata["network"] if context.metadata["channel"] == context.metadata["nick"]: text = "({network}/{nick}) ".format(**context.metadata) else: text = "({network}/{channel}/{nick}) ".format(**context.metadata) text += user_text connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") result = connector.SendMessage(channel, text, on_error=lambda e: e) if isinstance(result, Exception): return str(result) else: return f"{result} {user_text}"
def __init__(self): super().__init__() state_home = os.environ.get( "XDG_CONFIG_HOME", os.path.join(os.environ["HOME"], ".config")) state_dir = os.path.join(state_home, "pladder-web") os.makedirs(state_dir, exist_ok=True) db_file_path = os.path.join(state_dir, "tokens.db") self.db = self.enter_context(TokenDb(db_file_path)) bus = SessionBus() self.dbus_connector = PladderConnector(self.db, bus) self.dbus_web_api = PladderWebApi(self.db, bus) self.bot = RetryProxy(bus, "se.raek.PladderBot") self.enter_context(dbus_loop())
def __init__(self, state_dir, bus): super().__init__() os.makedirs(state_dir, exist_ok=True) self.state_dir = state_dir self.bus = bus self.log = RetryProxy(bus, "se.raek.PladderLog") self.fuse = Fuse(state_dir) self.bindings = [] self.register_command("help", self.help) self.register_command("version", self.version) self.register_command("searchlog", self.searchlog, contextual=True) self.register_command("send", self.send, contextual=True, varargs=True) self.register_command("channels", self.channels, contextual=True) self.register_command("users", self.users, contextual=True) self.register_command("connector-config", self.connector_config, contextual=True) self.register_command("comp", self.comp, contextual=True) self.register_command("give", self.give, varargs=True) self.register_command("echo", lambda text="": text, varargs=True) self.register_command("show-args", lambda *args: repr(args)) self.register_command("show-context", self.show_context, contextual=True) self.register_command( "pick", lambda *args: random.choice(args) if args else "") self.register_command("wpick", wpick) self.register_command( "concat", lambda *args: " ".join(arg.strip() for arg in args)) self.register_command("eval", self.eval_command, contextual=True) self.register_command("eval-pick", self.eval_pick, contextual=True) self.register_command("=", self.eq) self.register_command("/=", self.ne) self.register_command("bool", self.bool_command) self.register_command("if", self.if_command) self.register_command("trace", self.trace, contextual=True) self.register_command("source", self.source)
def __init__(self, bot, network): self.connector = RetryProxy(bot.bus, f'se.raek.PladderConnector.{network}')
class PladderBot(ExitStack): dbus = PLADDER_BOT_XML def __init__(self, state_dir, bus): super().__init__() os.makedirs(state_dir, exist_ok=True) self.state_dir = state_dir self.bus = bus self.log = RetryProxy(bus, "se.raek.PladderLog") self.fuse = Fuse(state_dir) self.bindings = [] self.register_command("help", self.help) self.register_command("version", self.version) self.register_command("searchlog", self.searchlog, contextual=True) self.register_command("send", self.send, contextual=True, varargs=True) self.register_command("channels", self.channels, contextual=True) self.register_command("users", self.users, contextual=True) self.register_command("connector-config", self.connector_config, contextual=True) self.register_command("comp", self.comp, contextual=True) self.register_command("give", self.give, varargs=True) self.register_command("echo", lambda text="": text, varargs=True) self.register_command("show-args", lambda *args: repr(args)) self.register_command("show-context", self.show_context, contextual=True) self.register_command( "pick", lambda *args: random.choice(args) if args else "") self.register_command("wpick", wpick) self.register_command( "concat", lambda *args: " ".join(arg.strip() for arg in args)) self.register_command("eval", self.eval_command, contextual=True) self.register_command("eval-pick", self.eval_pick, contextual=True) self.register_command("=", self.eq) self.register_command("/=", self.ne) self.register_command("bool", self.bool_command) self.register_command("if", self.if_command) self.register_command("trace", self.trace, contextual=True) self.register_command("source", self.source) def comp(self, context, command1, *command2_words): command2_result = self.apply(context, list(command2_words)) return self.apply(context, [command1, command2_result]) def give(self, target, text): return f"{target}: {text}" def RunCommand(self, timestamp, network, channel, nick, text): metadata = { 'datetime': datetime.fromtimestamp(timestamp, tz=timezone.utc), 'network': network, 'channel': channel, 'nick': nick, 'text': text } try: fuse_result = self.fuse.run(metadata['datetime'], network, channel) if fuse_result == FuseResult.JUST_BLOWN: return { 'text': f'{color.LIGHT_YELLOW}*daily fuse blown*{color.LIGHT_YELLOW}', 'command': 'error' } elif fuse_result == FuseResult.BLOWN: return {'text': '', 'command': 'error'} context = new_context(self.bindings, metadata) result, display_name = interpret(context, text) result = result[:10000] return {'text': result, 'command': display_name} except ApplyError as e: return { 'text': "Usage: {}".format(self.command_usage(e.command)), 'command': e.command.display_name } except ScriptError as e: return {'text': str(e), 'command': 'error'} except RecursionError: return { 'text': "RecursionError: Maximum recursion depth exceeded", 'command': 'error' } except Exception as e: print(str(e)) return {'text': "Internal error: " + str(e), 'command': 'error'} def apply(self, context, words): if not words: return "" command_name, arguments = words[0], words[1:] command = lookup_command(context.bindings, command_name) command_context = context._replace(command_name=command_name) return apply_call(command_context, command, command_name, arguments) def register_command(self, name, fn, varargs=False, contextual=False, source=None): self.bindings.append( command_binding(name, fn, varargs, contextual, source)) def command_usage(self, command): result = command.display_name parameters = list(signature(command.fn).parameters.values()) if command.contextual: # pop context argument parameters.pop(0) for i, parameter in enumerate(parameters): if i == len(parameters) - 1 and command.varargs: result += f" {{{parameter.name}...}}" elif parameter.default != Parameter.empty: result += f" [{parameter.name}]" else: result += f" <{parameter.name}>" return result def help(self, command_name=None): if not command_name: result = "Available commands: " result += ", ".join(command.display_name for command in self.bindings) return result else: command = lookup_command(self.bindings, command_name) if command: return "Usage: {}".format(self.command_usage(command)) else: return f"Unknown command: {command_name}" def version(self): return LAST_COMMIT def searchlog(self, context, needle, index=0): metadata = context.metadata try: index = int(index) except ValueError: return "'index' needs to be a number!" def format_log_line(index, date, nick, text): return '[{}: {} {}: {}]'.format(index, date.strftime('%H:%M'), nick, text) lines = self.log.SearchLines(metadata['network'], metadata['channel'], needle, 3, index, on_error=lambda e: None) if lines is None: return "Error: Could not reach pladder-log service!" lines_by_day = defaultdict(list) for index, timestamp, nick, text in lines: date = datetime.fromtimestamp(timestamp, tz=timezone.utc).astimezone(tz=None) line = format_log_line(index, date, nick, text) lines_by_day[(date.year, date.month, date.day)].append(line) formatted = [ '{}-{:02}-{:02}: {}'.format(*day, ', '.join(lines)) for (day, lines) in lines_by_day.items() ] result = '; '.join(formatted) if result: return result else: return "Found no matches for '{}'".format(needle) def send(self, context, target, user_text): if "send_called" in context.metadata: return "Only one send per script is allowed." context.metadata["send_called"] = True target_parts = target.split("/") if len(target_parts) != 2: return "Invalid target. Syntax: NetworkName/#channel" network, channel = target_parts if not network: network = context.metadata["network"] if context.metadata["channel"] == context.metadata["nick"]: text = "({network}/{nick}) ".format(**context.metadata) else: text = "({network}/{channel}/{nick}) ".format(**context.metadata) text += user_text connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") result = connector.SendMessage(channel, text, on_error=lambda e: e) print(repr(result)) if isinstance(result, Exception): return str(result) else: return f"{result} {user_text}" def channels(self, context, network=None): if network is None: network = context.metadata["network"] connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") channels = connector.GetChannels(on_error=lambda e: None) if channels is None: return f"Not connected to network {network}." else: if any(map(lambda c: " " in c, channels)): channels = ["{" + c + "}" for c in channels] return f"{network}: {', '.join(channels)}" def users(self, context, network_and_channel=""): parts = network_and_channel.split("/") if len(parts) != 2: return "Invalid argument. Syntax: NetworkName/#channel" network, channel = parts if not network: network = context.metadata["network"] connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") users = connector.GetChannelUsers(channel, on_error=lambda e: None) if users is None: return f"Not connected to network {network}." else: return f"{network}/{channel}: {', '.join(sorted(users))}" def connector_config(self, context, network=None): if network is None: network = context.metadata["network"] connector = RetryProxy(self.bus, f"se.raek.PladderConnector.{network}") config = connector.GetConfig(on_error=lambda e: None) if config is None: return f"Not connected to network {network}." else: parts = [] for key, value in config.items(): parts.append(f"{key}={repr(value)}") return f"{network}: {', '.join(parts)}" def show_context(self, context): return repr({ "bindings": "...", "metadata": repr(context.metadata), "command_name": context.command_name, }) def eval_command(self, context, script): text, _display_name = interpret(context, script) return text def eval_pick(self, context, *args): script = random.choice(args) if args else "" text, _display_name = interpret(context, script) return text def eq(self, value1, value2): return self.bool_py_to_pladder(value1 == value2) def ne(self, value1, value2): return self.bool_py_to_pladder(value2 == value1) def bool_command(self, value): return self.bool_py_to_pladder(self.bool_pladder_to_py(value)) def if_command(self, condition, then_value, else_value): if self.bool_pladder_to_py(condition): return then_value else: return else_value def bool_py_to_pladder(self, b): return "true" if b else "false" def bool_pladder_to_py(self, string): if string == "true": return True elif string == "false": return False else: raise ScriptError(f'Expected "true" or "false", got "{string}"') def trace(self, context, mode, script): if mode not in ["-brief", "-full"]: return "Mode must be one of: -brief, -full" subcontext = new_context(context.bindings, context.metadata) try: interpret(subcontext, script) except Exception: pass color_pairs = [ (color.LIGHT_RED, color.DARK_RED), (color.LIGHT_GREEN, color.DARK_GREEN), (color.LIGHT_BLUE, color.DARK_BLUE), (color.LIGHT_YELLOW, color.DARK_YELLOW), (color.LIGHT_MAGENTA, color.DARK_MAGENTA), (color.LIGHT_CYAN, color.DARK_CYAN), ] if mode == "-brief": return brief_trace(subcontext.trace, color_pairs) elif mode == "-full": return full_trace(subcontext.trace, color_pairs) def source(self, command_name): command = lookup_command(self.bindings, command_name) return command.source