aboutsummaryrefslogtreecommitdiffstats
path: root/alfred
diff options
context:
space:
mode:
authorAhmed <git@gumx.cc>2026-06-18 14:45:29 +0300
committerAhmed <git@gumx.cc>2026-06-18 14:45:29 +0300
commitde83bfadcc4807c07ba562f5d0ca599f126e2639 (patch)
treeacb15f7911542cee8e6fc947ac5e006127290fe9 /alfred
parente215418d64dac88d0998cf50438abddae5670f2b (diff)
fix: mlmmj commands
Diffstat (limited to 'alfred')
-rw-r--r--alfred/helpstrings.py4
-rw-r--r--alfred/mlmmj.py91
2 files changed, 91 insertions, 4 deletions
diff --git a/alfred/helpstrings.py b/alfred/helpstrings.py
index 2abaa5f..15362be 100644
--- a/alfred/helpstrings.py
+++ b/alfred/helpstrings.py
@@ -59,12 +59,14 @@ VPN = {
"remove": "vpn remove: remove a peer live and from wg0.conf. usage: !vpn remove name",
}
-LIST_TOPIC = "list: members subscribe unsubscribe send. type !help list <cmd> for details"
+LIST_TOPIC = "list: members subscribe unsubscribe send ls rm. type !help list <cmd> for details"
LIST = {
"members": "list members: show all subscribers. usage: !list members",
"subscribe": "list subscribe: subscribe an address. usage: !list subscribe email",
"unsubscribe": "list unsubscribe: unsubscribe an address. usage: !list unsubscribe email",
"send": "list send: send a message to the list. usage: !list send message",
+ "ls": "list ls: show latest n archive entries. usage: !list ls or !list ls n",
+ "rm": "list rm: remove a message from the archive. usage: !list rm id",
}
GIT_TOPIC = "git: list desc hook remove. type !help git <cmd> for details"
diff --git a/alfred/mlmmj.py b/alfred/mlmmj.py
index 88a6333..8c3749b 100644
--- a/alfred/mlmmj.py
+++ b/alfred/mlmmj.py
@@ -5,20 +5,58 @@ Sopel plugin: mlmmj mailing list management
!list unsubscribe <email> - unsubscribe
!list members - show all subscribers
!list send <message> - send a message to the list as owner
+ !list ls [n] - show latest n archive entries (default 5)
+ !list rm <id> - remove a message from the archive immediately
"""
-import email.utils, glob, os, subprocess
+import email.utils, glob, mailbox, os, subprocess
+from email.header import decode_header as _decode_hdr
+from email.utils import parseaddr, parsedate_to_datetime
from sopel import plugin
LIST_DIR = "/var/spool/mlmmj/list"
LIST_ADDR = "list@gumx.cc"
OWNER_ADDR = "ahmed@gumx.cc"
+ARCHIVE = os.path.join(LIST_DIR, "archive")
+ARCHIVE_GEN = "/home/ahmed/scripts/archive-gen.py"
+
+try:
+ with open(os.path.join(LIST_DIR, 'control', 'prefix')) as _f:
+ LIST_PREFIX = _f.read().strip()
+except FileNotFoundError:
+ LIST_PREFIX = ''
def _owner(bot, trigger):
return trigger.nick == bot.settings.core.owner
+def _decode(s):
+ if not s:
+ return ''
+ parts = _decode_hdr(s)
+ out = []
+ for part, charset in parts:
+ if isinstance(part, bytes):
+ out.append(part.decode(charset or 'utf-8', errors='replace'))
+ else:
+ out.append(part)
+ return ''.join(out)
+
+
+def _strip_prefix(subject):
+ if LIST_PREFIX and subject.startswith(LIST_PREFIX):
+ return subject[len(LIST_PREFIX):].lstrip()
+ return subject
+
+
+def _regen():
+ subprocess.run(
+ ["python3", ARCHIVE_GEN, LIST_DIR],
+ capture_output=True
+ )
+
+
@plugin.command("list")
def cmd_list(bot, trigger):
if not _owner(bot, trigger):
@@ -26,7 +64,7 @@ def cmd_list(bot, trigger):
args = (trigger.group(2) or "").split()
if not args:
- bot.say("usage: !list <subscribe|unsubscribe|members|send>")
+ bot.say("usage: !list <subscribe|unsubscribe|members|send|ls|rm>")
return
action = args[0]
@@ -87,5 +125,52 @@ def cmd_list(bot, trigger):
bot.say("queued" if r.returncode == 0
else f"error: {(r.stderr or r.stdout).strip()}")
+ elif action == "ls":
+ n = 5
+ if len(args) >= 2:
+ try:
+ n = int(args[1])
+ except ValueError:
+ bot.say("usage: !list ls [n]")
+ return
+
+ items = sorted(
+ f for f in os.listdir(ARCHIVE)
+ if os.path.isfile(os.path.join(ARCHIVE, f)) and not f.endswith('.html')
+ )
+ if not items:
+ bot.say("archive: empty")
+ return
+
+ for fname in reversed(items[-n:]):
+ fpath = os.path.join(ARCHIVE, fname)
+ with open(fpath, 'rb') as fh:
+ msg = mailbox.Message(fh)
+ subject = _strip_prefix(_decode(msg.get('Subject', '(no subject)')))
+ _, addr = parseaddr(_decode(msg.get('From', '')))
+ sender = addr or _decode(msg.get('From', ''))
+ date_raw = msg.get('Date', '')
+ try:
+ date_str = parsedate_to_datetime(date_raw).strftime('%Y-%m-%d %H:%M')
+ except Exception:
+ date_str = date_raw[:16] if date_raw else '?'
+ bot.say(f"{fname}: [{date_str}] {sender}: {subject}")
+
+ elif action == "rm":
+ if len(args) < 2:
+ bot.say("usage: !list rm <id>")
+ return
+ fid = args[1].strip()
+ fpath = os.path.join(ARCHIVE, fid)
+ if not os.path.isfile(fpath):
+ bot.say(f"rm: not found: {fid}")
+ return
+ os.remove(fpath)
+ html_path = fpath + '.html'
+ if os.path.isfile(html_path):
+ os.remove(html_path)
+ _regen()
+ bot.say(f"removed: {fid}")
+
else:
- bot.say("usage: !list <subscribe|unsubscribe|members|send>")
+ bot.say("usage: !list <subscribe|unsubscribe|members|send|ls|rm>")