""" Sopel plugin: mlmmj mailing list management !list subscribe - subscribe (forced, no confirmation email) !list unsubscribe - unsubscribe !list members - show all subscribers !list send - send a message to the list as owner (bypasses moderation) !list mod - show pending moderation queue !list approve - approve a held post !list discard - discard a held post """ import email.utils, glob, mailbox, os, subprocess, tempfile from sopel import plugin LIST_DIR = "/var/spool/mlmmj/list" LIST_ADDR = "list@gumx.cc" OWNER_ADDR = "ahmed@gumx.cc" MOD_DIR = os.path.join(LIST_DIR, "moderation") def _owner(bot, trigger): return trigger.nick == bot.settings.core.owner @plugin.command("list") def cmd_list(bot, trigger): if not _owner(bot, trigger): return args = (trigger.group(2) or "").split(None, 1) if not args: bot.say("usage: !list ") return action = args[0] if action == "subscribe": if len(args) < 2: bot.say("usage: !list subscribe ") return email_addr = args[1].strip() r = subprocess.run( ["/usr/bin/mlmmj-sub", "-L", LIST_DIR, "-a", email_addr, "-s", "-f"], capture_output=True, text=True ) bot.say(f"subscribed: {email_addr}" if r.returncode == 0 else f"error: {(r.stderr or r.stdout).strip()}") elif action == "unsubscribe": if len(args) < 2: bot.say("usage: !list unsubscribe ") return email_addr = args[1].strip() r = subprocess.run( ["/usr/bin/mlmmj-unsub", "-L", LIST_DIR, "-a", email_addr, "-s", "-f"], capture_output=True, text=True ) bot.say(f"unsubscribed: {email_addr}" if r.returncode == 0 else f"error: {(r.stderr or r.stdout).strip()}") elif action == "members": subs_dir = os.path.join(LIST_DIR, "subscribers.d") subs = [] if os.path.isdir(subs_dir): for fname in sorted(glob.glob(os.path.join(subs_dir, "*"))): with open(fname) as f: subs.extend(line.strip() for line in f if line.strip()) if subs: bot.say(f"{len(subs)} subscriber(s): " + ", ".join(subs)) else: bot.say("no subscribers") elif action == "send": if len(args) < 2: bot.say("usage: !list send ") return message = args[1].strip() msg = ( f"From: {OWNER_ADDR}\n" f"To: {LIST_ADDR}\n" f"Subject: {message}\n" f"Date: {email.utils.formatdate(localtime=True)}\n" f"\n" f"{message}\n" ) with tempfile.NamedTemporaryFile(mode="w", suffix=".eml", delete=False) as f: f.write(msg) tmpname = f.name try: r = subprocess.run( ["/usr/bin/mlmmj-send", "-L", LIST_DIR, "-l", "1", "-m", tmpname], capture_output=True, text=True ) bot.say("sent" if r.returncode == 0 else f"error: {(r.stderr or r.stdout).strip()}") finally: os.unlink(tmpname) elif action == "mod": files = sorted(os.listdir(MOD_DIR)) if os.path.isdir(MOD_DIR) else [] if not files: bot.say("moderation queue empty") return for fname in files[:5]: fpath = os.path.join(MOD_DIR, fname) try: with open(fpath, "rb") as f: msg = mailbox.Message(f) subj = (msg.get("Subject", "(no subject)") or "")[:50] frm = (msg.get("From", "") or "")[:30] except Exception: subj, frm = "(unreadable)", "" bot.say(f"{fname} {subj} — {frm}") if len(files) > 5: bot.say(f"... and {len(files) - 5} more") elif action == "approve": args2 = (trigger.group(2) or "").split(None, 2) if len(args2) < 2: bot.say("usage: !list approve ") return fid = args2[1].strip() path = os.path.join(MOD_DIR, fid) if not os.path.isfile(path): bot.say(f"not found: {fid}") return r = subprocess.run( ["/usr/bin/mlmmj-send", "-L", LIST_DIR, "-l", "1", "-m", path], capture_output=True, text=True ) if r.returncode == 0: os.unlink(path) bot.say(f"approved: {fid}") else: bot.say(f"error: {(r.stderr or r.stdout).strip()}") elif action == "discard": args2 = (trigger.group(2) or "").split(None, 2) if len(args2) < 2: bot.say("usage: !list discard ") return fid = args2[1].strip() path = os.path.join(MOD_DIR, fid) if not os.path.isfile(path): bot.say(f"not found: {fid}") return os.unlink(path) bot.say(f"discarded: {fid}") else: bot.say("usage: !list ")