From b774e0d04ca115ec1310965afd694eb93fa26130 Mon Sep 17 00:00:00 2001 From: groug Date: Sun, 27 Oct 2024 21:18:50 +0100 Subject: [PATCH] commit to not forget code --- glados.py | 51 ++++++++++++++++++ plugins/__init__.py | 0 plugins/admin.py | 23 ++++++++ plugins/help.py | 31 +++++++++++ plugins/plugin.py | 22 ++++++++ plugins/plugin_manager.py | 109 ++++++++++++++++++++++++++++++++++++++ plugins/say.py | 27 ++++++++++ 7 files changed, 263 insertions(+) create mode 100644 glados.py create mode 100644 plugins/__init__.py create mode 100644 plugins/admin.py create mode 100644 plugins/help.py create mode 100644 plugins/plugin.py create mode 100644 plugins/plugin_manager.py create mode 100644 plugins/say.py diff --git a/glados.py b/glados.py new file mode 100644 index 0000000..7dd2e65 --- /dev/null +++ b/glados.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -* + +from matrix_client.client import MatrixClient +from matrix_client.api import MatrixRequestError + +import os +import sys + +if len(sys.argv) > 1: + config = __import__(sys.argv[1]) +else: + import config + +class Bot: + def __init__(self): + self.client = MatrixClient(config.BOT_SERVER) + try: + self.client.login_with_password(config.BOT_USERNAME, config.BOT_PASSWORD) + except MatrixRequestError as e: + print(e) + if e.code == 403: + print("Bad username/password") + sys.exit(1) + + self.plugin_manager = PluginManager(self, config) + self.rooms = {} + for room_id, room_data in config.rooms.items(): + self.rooms[room_id] = self.client.join_room(room_id) + self.rooms[room_id].add_listener(self.callback) + + def run(self): + self.client.start_listener_thread() + #return client.sync_thread + + while True: + input() + + def callback(self, room, event): + if "@" + config.BOT_USERNAME in event['sender']: + return + + if event['type'] == "m.room.message": + self.plugin_manager.notify_message(room, event) + +def main(): + bot = Bot() + bot.run() + +if __name__ == "__main__": + main() diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/admin.py b/plugins/admin.py new file mode 100644 index 0000000..5438001 --- /dev/null +++ b/plugins/admin.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +from plugins.plugin import Plugin +import logging + + +class Admin(Plugin): + def activate(self): + super(Admin, self).activate() + + def deactivate(self): + super(Admin, self).deactivate() + + @Plugin.command(admin=True) + def admin_reload_plugins(self, msg, args): + """Reload plugins""" + self.plugin_manager.reload_plugins() + return "Plugins reloaded" + + @Plugin.command(admin=True) + def admin_test(self, msg, args): + """Simple test""" + return "You are admin" diff --git a/plugins/help.py b/plugins/help.py new file mode 100644 index 0000000..a31f218 --- /dev/null +++ b/plugins/help.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +from plugins.plugin import Plugin + + +class Help(Plugin): + def activate(self): + super(Help, self).activate() + + def deactivate(self): + super(Help, self).deactivate() + + @Plugin.command() + def help(self, room, event, args): + """Show list of commands (or only CMD) doc => !help [CMD]""" + command_names = [] + if len(args) >= 1: + help_command = ' '.join(args) + if help_command[0] != '!': + help_command = '!' + help_command + if help_command in self.plugin_manager.commands.keys(): + command_names = [help_command] + if not command_names: + command_names = self.plugin_manager.commands.keys() + reply = ['Usage:'] + for command_name in command_names: + command = self.plugin_manager.commands[command_name] + if not self.plugin_manager.is_admin_command(command_name) or self.plugin_manager.is_sender_admin(msg): + reply.append('{0}: {1}'.format(command_name, command['function'].__doc__ or 'N/A')) + room.send_text("\n".join(reply)) + return True diff --git a/plugins/plugin.py b/plugins/plugin.py new file mode 100644 index 0000000..e452455 --- /dev/null +++ b/plugins/plugin.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + + +class Plugin(object): + def __init__(self, plugin_manager): + self.plugin_manager = plugin_manager + + def activate(self): + pass + + def deactivate(self): + pass + + def notify_message(self, room, event): + pass + + @staticmethod + def command(*args, **options): + def decorator(f): + f._plugin_command = {'options': options} + return f + return decorator diff --git a/plugins/plugin_manager.py b/plugins/plugin_manager.py new file mode 100644 index 0000000..7a8d6c9 --- /dev/null +++ b/plugins/plugin_manager.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +import importlib +import inspect +from collections import OrderedDict +from plugins.plugin import Plugin + + +class PluginManager: + def __init__(self, bot, config): + self.bot = bot + self.config = config + self.plugins = OrderedDict() + self.commands = OrderedDict() + self.command_names = [] + + def load_plugin(self, name): + mod = importlib.import_module('plugins.{0}'.format(name)) + + classes = inspect.getmembers(mod, inspect.isclass) + + for class_name, c in classes: + if c != Plugin and issubclass(c, Plugin): + self.plugins[name] = c(self) + self.plugins[name]._module = mod + for method_name, method in inspect.getmembers(self.plugins[name], inspect.ismethod): + if getattr(method, '_plugin_command', False): + options = method._plugin_command['options'] + command = options.pop('command', None) + if command is None: + command = ' '.join(method.__name__.split('_')) + self.add_command(command, method, **options) + self.plugins[name].config = self.config + + # Only for debug + def reload_plugins(self): + plugin_names = self.plugins.keys() + self.deactivate_plugins() + importlib.invalidate_caches() + + # Force rebuilding modules + for plugin in self.plugins.values(): + importlib.reload(plugin._module) + + self.plugins = OrderedDict() + self.commands = OrderedDict() + self.command_names = [] + + for plugin_name in plugin_names: + self.load_plugin(plugin_name) + + self.activate_plugins() + + def activate_plugins(self): + for k, plugin in self.plugins.items(): + plugin.activate() + + def deactivate_plugins(self): + for k, plugin in self.plugins.items(): + plugin.deactivate() + + def notify_message(self, room, event, private=False): + # for k, plugin in self.plugins.items(): + # reply = plugin.notify_message(event, private) + # if reply: + # self.bot.send_message(mto=message['from'].full if private else message['from'].bare, mbody=reply, mtype='chat' if private else 'groupchat') + body = event['content']['body'] + if body.startswith('!'): + command_found = False + for command in self.command_names: + command_chain = command.split(' ') + body_chain = body.split(' ') + if len(body_chain) >= len(command_chain) and body_chain[:len(command_chain)] == command_chain: + admin_needed = self.commands[command]['options'].get('admin', False) + if admin_needed and not self.is_sender_admin(event): + reply = "Unauthorized" + else: + args = [a for a in body[len(command):].strip().split(' ') if a] + f = self.commands[command]['function'] + reply = f(room, event, args) + command_found = True + break + if not command_found: + reply = 'Unknown command' + if reply and isinstance(reply, str): + room.send_text(reply) + + def is_sender_admin(self, event): + return False + """Checks if author is admin""" + if message['type'] == 'groupchat': + # We don't know the jid. Either there's a problem or it's an anonymous room + if message['from'].full not in self.bot.presences: + return False + jid = self.bot.presences[message['from'].full] + else: + jid = message['from'].full.split('/')[0] + return jid in self.config.ADMINS + + def is_admin_command(self, command): + return command in self.commands and self.commands[command]['options'].get('admin', False) + + def add_command(self, command, function, **options): + # TODO: check the command doesn't exist. If not, try another name + command_name = '!{0}'.format(command) + class_name = inspect.getmro(function.__self__.__class__)[0].__name__ + self.commands[command_name] = {'function': function, 'class': class_name, 'options': options} + self.command_names.append(command_name) + self.command_names.sort(key=len, reverse=True) diff --git a/plugins/say.py b/plugins/say.py new file mode 100644 index 0000000..38885b9 --- /dev/null +++ b/plugins/say.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from plugins.plugin import Plugin + + +class Say(Plugin): + def activate(self): + super(Say, self).activate() + + def deactivate(self): + super(Say, self).deactivate() + + @Plugin.command() + def say(self, room, event, args): + """Make glados say something => !say SOMETHING""" + if len(args) < 1: + return Say.say.__doc__ + + room.send_text(' '.join(args[0:])) + + @Plugin.command() + def rsay(self, room, event, args): + """Make glados say something on a given room => !say ROOM_JID SOMETHING""" + if len(args) < 2: + return Say.say.__doc__ + + return {'to': args[0], 'msg': ' '.join(args[1:])}