From 4f53833cc21c2e37fadb2eb23eb234afcd81b59b Mon Sep 17 00:00:00 2001 From: Siina Mashek Date: Wed, 27 Apr 2022 11:24:45 +0300 Subject: [PATCH] Restructured to use primitive plugin system. --- ATTRIBUTION.adoc | 8 ++-- ameliabot/quote.py | 68 ------------------------------ bot.py | 4 +- commands.ini | 9 ++-- {ameliabot => core}/__init__.py | 8 +--- {ameliabot => core}/logger.py | 0 {ameliabot => core}/owncast.py | 29 +++++++++---- plugins/dadjoke.py | 27 ++++++++++++ plugins/quote.py | 73 +++++++++++++++++++++++++++++++++ 9 files changed, 135 insertions(+), 91 deletions(-) delete mode 100644 ameliabot/quote.py rename {ameliabot => core}/__init__.py (85%) rename {ameliabot => core}/logger.py (100%) rename {ameliabot => core}/owncast.py (76%) create mode 100644 plugins/dadjoke.py create mode 100644 plugins/quote.py diff --git a/ATTRIBUTION.adoc b/ATTRIBUTION.adoc index 8f481e5..7709711 100644 --- a/ATTRIBUTION.adoc +++ b/ATTRIBUTION.adoc @@ -6,9 +6,9 @@ = Code Attribution -== ameliabot.logger -`ameliabot.logger` is based on {url-colargulog}[colargulog] by David Ohana under the {url-apache}[Apache-2.0 License]. Changed for `flake8` compliance. +== core.logger +`core.logger` is based on {url-colargulog}[colargulog] by David Ohana under the {url-apache}[Apache-2.0 License]. Changed for `flake8` compliance. -== ameliabot.owncast -`ameliabot.owncast` originally started as {url-hatbot}[hatbot] by {url-hatnix}[hatniX], licensed under the {url-unlicense}[Unlicense], which this project also uses. +== core.owncast +`core.owncast` originally started as {url-hatbot}[hatbot] by {url-hatnix}[hatniX], licensed under the {url-unlicense}[Unlicense], which this project also uses. diff --git a/ameliabot/quote.py b/ameliabot/quote.py deleted file mode 100644 index 22b4de9..0000000 --- a/ameliabot/quote.py +++ /dev/null @@ -1,68 +0,0 @@ -from datetime import datetime, timezone -import random -import sqlite3 -from ameliabot.logger import logging - - -class Quote: - def __init__(self): - self.__init_table() - self.num_quotes = self._get_num_quotes() - logging.info("Quote subsystem online") - - def insert(self, owner, submitter, text): - conn = self.__connect() - text = text.replace("'", "''") - conn.execute(''' - INSERT INTO quotes (submitter, text, timestamp) - VALUES ('{}', '{}', {})'''.format( - submitter, text, datetime.now().replace(tzinfo=timezone.utc))) - self.num_quotes += 1 - logging.debug("Quote number %s inserted" % self.num_quotes) - - def get(self, arg=None): - conn = self.__connect() - if arg: - ret = "SELECT id, text, timestamp FROM quotes WHERE " - try: - if int(arg) > self.num_quotes: - return "No quote matching that number." - ret += "id = %s" % arg - except ValueError: - ret += "text like '%{}%'".format(arg.lower()) - - quote = conn.execute(ret) - return self._format(list(quote.fetchone())) - else: - return self.get(random.randint(0, self.num_quotes)) - - def _get_num_quotes(self): - conn = self.__connect() - try: - num = conn.execute("SELECT COUNT(id) FROM quotes") - except sqlite3.OperationalError: - return 0 - return int(num.fetchone()[0]) - - def _format(self, quote): - num, text, timestamp = quote - timestamp = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S.%f%z') - return "{}. {}, {}".format( - num, text, datetime.strftime(timestamp, '%Y')) - - def __connect(self): - return sqlite3.connect("data/quote.db") - - def __init_table(self): - try: - conn = self.__connect() - except sqlite3.OperationalError: - import os - os.makedirs("data") - self.__init_table() - - conn.execute(''' - CREATE TABLE IF NOT EXISTS quotes ( - id INTEGER PRIMARY KEY, submitter TEXT, - text TEXT, timestamp TEXT - )''') diff --git a/bot.py b/bot.py index 8ed1f2e..ae45776 100644 --- a/bot.py +++ b/bot.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from flask import Flask, Response -from ameliabot import owncast, __version__ -from ameliabot.logger import logging +from core import owncast, __version__ +from core.logger import logging logging.info("Loaded %s, running ameliabot v%s)" % ( diff --git a/commands.ini b/commands.ini index e1eec47..c07fbb7 100644 --- a/commands.ini +++ b/commands.ini @@ -6,11 +6,14 @@ !save = {sender} is reminding {botowner} to _**SAVE NOW**_! !slap = *slaps {target} with a large trout!* !version = I am running ameliabot {bot_version} - -[commands.quote] -!quote = {quote} +!dadjoke = {plugins.dadjoke} +!quote = {plugins.quote} !addquote = {quote_parameters} +[plugins] +dadjoke = Enabled +quote = Enabled + [aliases] help = commands bot = version diff --git a/ameliabot/__init__.py b/core/__init__.py similarity index 85% rename from ameliabot/__init__.py rename to core/__init__.py index 5b2764e..9e3a1b4 100644 --- a/ameliabot/__init__.py +++ b/core/__init__.py @@ -1,7 +1,7 @@ from configparser import ConfigParser import sys -from ameliabot.logger import ColorizedArgsFormatter, BraceFormatStyleFormatter -from ameliabot.logger import logging +from core.logger import ColorizedArgsFormatter, BraceFormatStyleFormatter +from core.logger import logging __version__ = "0.1.0" @@ -46,10 +46,6 @@ cmd_file = ConfigParser() try: cmd_file.read("commands.ini") commands = cmd_file["commands"] - if config["quotes"]["Enabled"]: - # Perhaps configparser handles this and I haven't figured it out - for cmd in cmd_file["commands.quote"]: - commands[cmd] = cmd_file["commands.quote"][cmd] logging.info("Commands loaded") aliases = cmd_file["aliases"] diff --git a/ameliabot/logger.py b/core/logger.py similarity index 100% rename from ameliabot/logger.py rename to core/logger.py diff --git a/ameliabot/owncast.py b/core/owncast.py similarity index 76% rename from ameliabot/owncast.py rename to core/owncast.py index 7c98dc9..8f9b746 100644 --- a/ameliabot/owncast.py +++ b/core/owncast.py @@ -1,14 +1,22 @@ +import importlib +import os import random import requests from requests.structures import CaseInsensitiveDict -from ameliabot import __version__, config, aliases, commands -from ameliabot.logger import logging +import sys +from core import __version__, config, aliases, commands +from core.logger import logging from flask import request -# Make quote system and dependencies optional -if config["quotes"]["Enabled"]: - from ameliabot.quote import Quote - quote = Quote() + +# This is gross and needs to be replaced +plugin_path = "%s/../plugins" % os.path.dirname(os.path.realpath(__file__)) +sys.path.append(plugin_path) +modules = os.listdir(plugin_path) + +for module in modules: + if module.endswith(".py") and not module.startswith("__"): + globals()[module[:-3]] = importlib.import_module(module[:-3]) # prepare the header for the bot posts headers = CaseInsensitiveDict() @@ -59,17 +67,22 @@ def get_quote(num): pass except ValueError: pass - return quote.get(num) + return quote.get(num) # NOQA should work even if linter complains def process_chat(data): sender = data["user"]["displayName"] text = data["body"] command_reply = get_command(text) - logging.info("<{}> {}".format(sender, text)) if command_reply: + logging.debug("Command found") + if command_reply.startswith("{plugins."): + plugin = command_reply[1:-1].split(".") + logging.debug("Attempting to run plugin: %s" % plugin[1]) + reply = globals()[plugin[1]].run() + return reply try: first_parameter = text.split(" ")[1] except IndexError: diff --git a/plugins/dadjoke.py b/plugins/dadjoke.py new file mode 100644 index 0000000..aa17132 --- /dev/null +++ b/plugins/dadjoke.py @@ -0,0 +1,27 @@ +from urllib import request +import json + + +def get_joke(): + url = "https://icanhazdadjoke.com" + try: + with request.urlopen(create_request(url)) as response: + joke = json.loads(response.read()) + if joke["status"] == 200: + return joke["joke"] + except (request.URLError, request.HTTPError): + return "No dad joke right now." + + +def create_request(url): + headers = { + "Accept": "application/json", + "User-Agent": "ameliabot (https://criminallycute.fi/ameliabot)" + } + return request.Request( + url, data=None, headers=headers + ) + + +def run(): + return get_joke() diff --git a/plugins/quote.py b/plugins/quote.py new file mode 100644 index 0000000..41068c8 --- /dev/null +++ b/plugins/quote.py @@ -0,0 +1,73 @@ +from datetime import datetime, timezone +import random +import sqlite3 +""" +This is a really, really rough plugin and needs to be redone. +""" + + +def insert(owner, submitter, text): + conn = __connect() + text = text.replace("'", "''") + conn.execute(''' + INSERT INTO quotes (submitter, text, timestamp) + VALUES ('{}', '{}', {})'''.format( + submitter, text, datetime.now().replace(tzinfo=timezone.utc))) + + +def get(arg=None): + conn = __connect() + num_quotes = __get_num_quotes() + if arg: + ret = "SELECT id, text, timestamp FROM quotes WHERE " + try: + if int(arg) > num_quotes: + return "No quote matching that number." + ret += "id = %s" % arg + except ValueError: + ret += "text like '%{}%'".format(arg.lower()) + + quote = conn.execute(ret) + return __format(list(quote.fetchone())) + else: + return get(random.randint(0, num_quotes)) + + +def __get_num_quotes(): + conn = __connect() + try: + num = conn.execute("SELECT COUNT(id) FROM quotes") + except sqlite3.OperationalError: + return 0 + return int(num.fetchone()[0]) + + +def __format(quote): + num, text, timestamp = quote + timestamp = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S.%f%z') + return "{}. {}, {}".format( + num, text, datetime.strftime(timestamp, '%Y')) + + +def __connect(): + return sqlite3.connect("data/quote.db") + + +def __init_table(): + try: + conn = __connect() + except sqlite3.OperationalError: + import os + os.makedirs("data") + __init_table() + + conn.execute(''' + CREATE TABLE IF NOT EXISTS quotes ( + id INTEGER PRIMARY KEY, submitter TEXT, + text TEXT, timestamp TEXT + )''') + + +def run(arg=None): + __init_table() + return get(arg)