From 4a482539d09b4d3dd3fb5875f2987fc427fe4ba8 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Wed, 17 Jun 2026 18:24:00 +0300 Subject: fix: alfred help and twt commands --- alfred/twt.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 alfred/twt.py (limited to 'alfred/twt.py') diff --git a/alfred/twt.py b/alfred/twt.py new file mode 100644 index 0000000..0953d3c --- /dev/null +++ b/alfred/twt.py @@ -0,0 +1,173 @@ +""" +Sopel plugin: twtxt feed + +Commands (owner only): + !twt - show usage + !twt last [n] - show last n twts, most recent first (default 5) + !twt count - total number of twts + !twt delete - delete the most recent twt (asks yes/no) + !twt yes / !twt no - confirm or cancel a pending delete + !twt - post a twt + +Config ([twt] section in sopel config): + twtxt_path - path to twtxt.txt (default /var/www/twt.gumx.cc/twtxt.txt) + timezone - timezone name for timestamps (default UTC) + nick - nick value written in file header (default gumx) + url - url value written in file header +""" + +import os +from datetime import datetime +from zoneinfo import ZoneInfo + +from sopel import plugin +from sopel.config.types import FilenameAttribute, StaticSection, ValidatedAttribute + +import os as _os, sys as _sys +_alfred_dir = _os.path.dirname(_os.path.abspath(__file__)) +if _alfred_dir not in _sys.path: + _sys.path.insert(0, _alfred_dir) +import helpstrings as h + +_pending_delete = {} + +_SUBCOMMANDS = {"last", "count", "delete", "yes", "no"} + + +class TwtSection(StaticSection): + twtxt_path = FilenameAttribute("twtxt_path", relative=False, + default="/var/www/twt.gumx.cc/twtxt.txt") + timezone = ValidatedAttribute("timezone", default="UTC") + nick = ValidatedAttribute("nick", default="gumx") + url = ValidatedAttribute("url", default="https://twt.gumx.cc/twtxt.txt") + + +def setup(bot): + bot.settings.define_section("twt", TwtSection) + + +def _is_owner(bot, trigger): + return trigger.nick == bot.settings.core.owner + + +def _path(bot): + return bot.settings.twt.twtxt_path + + +def _read(bot): + p = _path(bot) + if not os.path.exists(p): + return [] + with open(p) as f: + lines = f.readlines() + twts = [] + for line in lines: + line = line.rstrip("\n") + if not line or line.startswith("#"): + continue + tab = line.find("\t") + if tab == -1: + continue + twts.append((line[:tab], line[tab + 1:])) + return twts + + +def _write(bot, twts): + p = _path(bot) + nick = bot.settings.twt.nick + url = bot.settings.twt.url + with open(p, "w") as f: + f.write(f"# nick = {nick}\n") + f.write(f"# url = {url}\n") + for ts, text in twts: + f.write(f"{ts}\t{text}\n") + + +def _append(bot, text): + tz = ZoneInfo(bot.settings.twt.timezone) + now = datetime.now(tz) + offset = now.strftime("%z") + offset = offset[:3] + ":" + offset[3:] + ts = now.strftime("%Y-%m-%dT%H:%M:%S") + offset + + p = _path(bot) + nick = bot.settings.twt.nick + url = bot.settings.twt.url + if not os.path.exists(p): + os.makedirs(os.path.dirname(p), exist_ok=True) + with open(p, "w") as f: + f.write(f"# nick = {nick}\n") + f.write(f"# url = {url}\n") + with open(p, "a") as f: + f.write(f"{ts}\t{text}\n") + return ts + + +@plugin.commands("twt") +def cmd_twt(bot, trigger): + if not _is_owner(bot, trigger): + return + + raw = (trigger.group(2) or "").strip() + args = raw.split() + sub = args[0].lower() if args else "" + + if not sub: + bot.say(h.TWF_TOPIC) + return + + # --- last --- + if sub == "last": + try: + n = int(args[1]) if len(args) > 1 else 5 + n = max(1, min(n, 10)) + except ValueError: + bot.say("usage: !twt last [n]") + return + twts = _read(bot) + if not twts: + bot.say("no twts yet.") + return + for ts, text in reversed(twts[-n:]): + bot.say(f"[{ts[:10]}] {text}") + return + + # --- count --- + if sub == "count": + n = len(_read(bot)) + bot.say(f"{n} twt{'s' if n != 1 else ''}") + return + + # --- delete --- + if sub == "delete": + twts = _read(bot) + if not twts: + bot.say("nothing to delete.") + return + ts, text = twts[-1] + _pending_delete[trigger.nick] = True + bot.say(f"delete [{ts[:10]}] {text!r}? !twt yes / !twt no") + return + + # --- yes/no (confirm delete) --- + if sub == "yes": + if not _pending_delete.pop(trigger.nick, False): + bot.say("nothing pending.") + return + twts = _read(bot) + if not twts: + bot.say("nothing to delete.") + return + removed = twts.pop() + _write(bot, twts) + bot.say(f"deleted [{removed[0][:10]}] {removed[1]!r}") + return + + if sub == "no": + if _pending_delete.pop(trigger.nick, False): + bot.say("cancelled.") + return + + # --- post (anything that's not a subcommand) --- + ts = _append(bot, raw) + bot.say(f"twted [{ts[:10]}]: {raw}") -- cgit v1.2.3