import logging import json import os import signal import time from datetime import datetime, timezone from html.parser import HTMLParser from mastodon import Mastodon import mods.elicense class Handler: def __init__(self, app, st): self.logging = logging.getLogger("app.handler") self.app = app self.st = st self.text = sanitize_html(st["content"]) self.tokens = self.text.split()[1:] self.handle() def handle(self): if self.tokens[0] == "watch": self.watch() else: raise Exception("unknown command") def watch(self): date = self.tokens[1] slots = [int(x) for x in self.tokens[2:]] elapsed = datetime.now(timezone.utc) - self.st["created_at"] if elapsed.seconds < self.app.CONFIG["interval"]: self.app.reply(self.st, "accepted") mod = self.app.mods["elicense"] mod.watch(date, slots, lambda: self.app.reply(self.st, "reservation made")) class App: def __init__(self): logging.basicConfig() self.logging = logging.getLogger("app") with open("config.json", "r") as f: self.CONFIG = json.load(f) self.logging.setLevel(self.CONFIG["logging"]) self.logging.info("configuration loaded") self.M = self.authenticate() self.logging.info("authentication succeeded") self.mods = {} self.install("elicense", mods.elicense) self.alive = True signal.signal(signal.SIGTERM, lambda a,b: self.exit()) signal.signal(signal.SIGINT, lambda a,b: self.exit()) def authenticate(self): if os.path.exists("auth.json"): with open("auth.json", "r") as f: auth = json.load(f) M = Mastodon( api_base_url = self.CONFIG["instance"], client_id = auth["client_id"], client_secret = auth["secret"], access_token = auth["token"]) else: cid, secret = Mastodon.create_app( client_name = "maidbot", api_base_url = self.CONFIG["instance"]) M = Mastodon( client_id = cid, client_secret = secret, api_base_url = self.CONFIG["instance"]) tok = M.log_in( username = self.CONFIG["username"], password = self.CONFIG["password"]) dic = {"client_id": cid, "secret": secret, "token": tok} with open("auth.json", "w") as f: json.dump(dic, f, indent = 2) return M def install(self, name, mod): config = self.CONFIG["mods"].get(name) if config is None: return logger = logging.getLogger("mods."+name) if "logging" in config: logger.setLevel(config["logging"]) self.mods[name] = mod.init(config, logger) self.logging.info("mod installed, "+name) def run(self): while self.alive: try: self.cycle() self.logging.debug("cycle done") except Exception as e: self.logging.warning("cycle aborted:", e) for i in range(self.CONFIG["interval"]): if not self.alive: break time.sleep(1) del self.mods def cycle(self): items = self.M.notifications( account_id = self.CONFIG["master"], types = "mention") for item in items: st = item["status"] try: Handler(self, st) except Exception as e: self.reply(st, "handling aborted: {}".format(e)) self.M.notifications_dismiss(id = item["id"]) for mod in self.mods: self.mods[mod].cycle() def exit(self): self.alive = False self.logging.info("exit requested") def reply(self, st, msg): try: self.M.status_post( status = "@{} {}".format(st["account"]["acct"], msg), in_reply_to_id = st["id"], visibility = "direct") self.logging.debug("reply:", msg) except Exception as e: self.logging.error("reply failure:", msg, "(", e, ")") def sanitize_html(html): class F(HTMLParser): text = "" def handle_starttag(self, tag, attrs): if tag == "br": self.text += " " def handle_data(self, data): self.text += data f = F() f.feed(html) return f.text App().run()