aboutsummaryrefslogtreecommitdiffstats
path: root/alfred
diff options
context:
space:
mode:
Diffstat (limited to 'alfred')
-rw-r--r--alfred/helpstrings.py14
-rw-r--r--alfred/twt.py173
2 files changed, 185 insertions, 2 deletions
diff --git a/alfred/helpstrings.py b/alfred/helpstrings.py
index d944799..2abaa5f 100644
--- a/alfred/helpstrings.py
+++ b/alfred/helpstrings.py
@@ -1,4 +1,4 @@
-TOPICS = "topics: srv, bot, files, irc, coffee, vpn, list, git. type !help <topic> for details"
+TOPICS = "topics: srv, bot, files, irc, coffee, vpn, list, git, twt. type !help <topic> for details"
SRV_TOPIC = "srv: uptime disk mem load status net top topmem io conns who logs. type !help srv <cmd> for details"
SRV = {
@@ -75,6 +75,15 @@ GIT = {
"remove": "git remove: remove a repo, prompts for confirmation. usage: !git remove repo",
}
+TWF_TOPIC = "twt: last [n] count delete yes no <message>. type !help twt <cmd> for details"
+TWF = {
+ "last": "twt last: show last n twts, most recent first (default 5). usage: !twt last or !twt last n",
+ "count": "twt count: total number of twts. usage: !twt count",
+ "delete": "twt delete: delete the most recent twt, asks for confirmation. usage: !twt delete",
+ "yes": "twt yes or no: confirm or cancel a pending delete. usage: !twt yes or !twt no",
+ "no": "twt yes or no: confirm or cancel a pending delete. usage: !twt yes or !twt no",
+}
+
TOPIC_MAP = {
"srv": (SRV_TOPIC, SRV),
"bot": (BOT_TOPIC, BOT),
@@ -84,9 +93,10 @@ TOPIC_MAP = {
"vpn": (VPN_TOPIC, VPN),
"list": (LIST_TOPIC, LIST),
"git": (GIT_TOPIC, GIT),
+ "twt": (TWF_TOPIC, TWF),
}
-_ALL = {**SRV, **BOT, **FILES, **IRC, **COFFEE, **VPN, **LIST, **GIT}
+_ALL = {**SRV, **BOT, **FILES, **IRC, **COFFEE, **VPN, **LIST, **GIT, **TWF}
def lookup(args):
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 <message> - 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}")