From 58f62605992da8fe6f107fc850aa4f18ba4ec274 Mon Sep 17 00:00:00 2001 From: romangraef Date: Sun, 11 Mar 2018 18:22:02 +0100 Subject: Initial commit --- .gitignore | 2 ++ commands/__init__.py | 2 ++ commands/handling.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ launch.sh | 5 ++++ lib/__init__.py | 58 ++++++++++++++++++++++++++++++++++++++++++++ main.py | 19 +++++++++++++++ modules/builtins/eval.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++ modules/builtins/help.py | 10 ++++++++ requirements.txt | 1 + 9 files changed, 219 insertions(+) create mode 100644 .gitignore create mode 100644 commands/__init__.py create mode 100644 commands/handling.py create mode 100755 launch.sh create mode 100644 lib/__init__.py create mode 100644 main.py create mode 100644 modules/builtins/eval.py create mode 100644 modules/builtins/help.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce2e17d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.ini +*.session \ No newline at end of file diff --git a/commands/__init__.py b/commands/__init__.py new file mode 100644 index 0000000..dc26d49 --- /dev/null +++ b/commands/__init__.py @@ -0,0 +1,2 @@ +from commands.handling import handle_commands, load_commands + diff --git a/commands/handling.py b/commands/handling.py new file mode 100644 index 0000000..46cdb66 --- /dev/null +++ b/commands/handling.py @@ -0,0 +1,62 @@ +import sys +import traceback + +import importlib +import os +import pyrogram +import re +import types +from pyrogram.api import types as tgtypes + +import lib + +PREFIX = "!" + + +def load_module(module): + functions = [module.__dict__.get(a) for a in dir(module) + if isinstance(module.__dict__.get(a), types.FunctionType)] + for func in functions: + if lib.is_command(func): + lib.register_command(func) + + +def load_commands(folder='modules'): + for dirname, dirnames, filenames in os.walk(folder): + for filename in filenames: + filename: str + if filename.endswith('.py'): + filename = filename[:-3] + pos = os.path.join(dirname, filename) + module = importlib.import_module(pos.replace('/', '.')) + load_module(module) + + +def handle_commands(client: pyrogram.Client, update, users, chats): + if not (isinstance(update, tgtypes.UpdateNewMessage) + or isinstance(update, tgtypes.UpdateNewChannelMessage) + or isinstance(update, tgtypes.UpdateNewEncryptedMessage)): + return + update: tgtypes.UpdateNewMessage + message: tgtypes.Message = update.message + author_id = message.from_id + if author_id != client.user_id: + # do not react to other people + return + text: str = message.message + if text[:len(PREFIX)] != PREFIX: + return + parts = re.split(r'\s+', text) + if len(parts) < 1: + return + command = parts[0][1:] + args = parts[1:] + cmd_func = lib.commands[command.lower()] + ctx = lib.CommandContext(client=client, channel=message.to_id, args=args, message=message) + try: + cmd_func(ctx) + except KeyError: + ctx.respond('unknown command') + except Exception as e: + ctx.respond("unknown exception during execution. Error will be DM'd" + str(e)) + print(traceback.format_exc(), file=sys.stderr) diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..ab81ae7 --- /dev/null +++ b/launch.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +DIRNAME=$(dirname $(readlink -f $0)) +cd ${DIRNAME} +${DIRNAME}/venv/bin/python ${DIRNAME}/main.py +cd / diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..2c67b83 --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,58 @@ +from typing import List + +import pyrogram +from pyrogram.api import types as tgtypes + + +def property_decorator(key): + def decorator(value): + def wrapper(func): + setattr(func, key, value) + return func + + return wrapper + + return decorator + + +name = property_decorator('name') +description = property_decorator('description') + + +def is_command(func): + return hasattr(func, 'name') and func.name is not None + + +def get_command_name(func): + return func.name + + +def get_command_description(func): + return func.description + + +commands = {} + + +def get_all_commands(): + return commands.values() + + +class CommandContext(object): + def __init__(self, client: pyrogram.Client, channel, args: List[str], message: tgtypes.Message): + import re + self.args = args + self.client = client + self.channel = channel + self.message = message + self.rest_content = re.sub('.*? ', '', message.message) + self.author = message.from_id + + def respond(self, text): + self.client.send_message(self.channel, text=text) + + +def register_command(func): + if not is_command(func): + return + commands[get_command_name(func)] = func diff --git a/main.py b/main.py new file mode 100644 index 0000000..693f9d4 --- /dev/null +++ b/main.py @@ -0,0 +1,19 @@ +from pyrogram import Client + +from commands import handle_commands, load_commands + + +def update_handler(client, update, users, chats): + handle_commands(client, update, users, chats) + + +def main(): + load_commands() + client = Client(session_name="userbot") + client.set_update_handler(update_handler) + client.start() + client.idle() + + +if __name__ == '__main__': + main() diff --git a/modules/builtins/eval.py b/modules/builtins/eval.py new file mode 100644 index 0000000..dfbdd57 --- /dev/null +++ b/modules/builtins/eval.py @@ -0,0 +1,60 @@ +import ast + +from lib import * + + +@name('eval') +@description('evals a given piece of python code') +def eval_command(ctx: CommandContext): + try: + block = ast.parse(ctx.rest_content, mode='exec') + last = ast.Expression(block.body.pop().value) + except KeyboardInterrupt: + raise + except SystemExit: + raise + except BaseException as e: + ctx.respond("Compilation failed: %r" % e) + return + + _globals, _locals = {}, { + 'ctx': ctx, + 'message': ctx.message, + 'client': ctx.client, + 'print': + lambda *content, stdout=False: + print(*content) + if stdout + else ctx.respond('\t'.join(map(str, content))) + } + try: + exec(compile(block, '', mode='exec'), _globals, _locals) + except KeyboardInterrupt: + raise + except SystemExit: + raise + except BaseException as e: + ctx.respond("Evaluation failed: %r" % str(e)) + return + + try: + compiled = compile(last, '', mode='eval') + except KeyboardInterrupt: + raise + except SystemExit: + raise + except BaseException as e: + ctx.respond("Last statement has to be an expression: %r" % str(e)) + return + + try: + result = eval(compiled, _globals, _locals) + except KeyboardInterrupt: + raise + except SystemExit: + raise + except BaseException as e: + ctx.respond("Evaluation failed: %r" % str(e)) + return + + ctx.respond("Evaluation succes: \n```\n%s\n```" % str(result)) diff --git a/modules/builtins/help.py b/modules/builtins/help.py new file mode 100644 index 0000000..523d86d --- /dev/null +++ b/modules/builtins/help.py @@ -0,0 +1,10 @@ +from lib import CommandContext, get_all_commands, name, description, get_command_name, get_command_description + + +@name('help') +@description('lists all commands with their descriptions') +def help_command(ctx: CommandContext): + resp = "" + for command in get_all_commands(): + resp += '%s - %s\n' % (get_command_name(command), get_command_description(command)) + ctx.respond(resp) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..032294a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pyrogram[tgcrypto] -- cgit