From 0aefb97844c495c0235b8e2c3c88eda9609fc8ee Mon Sep 17 00:00:00 2001 From: Ahmed Date: Sat, 20 Jun 2026 16:08:04 +0300 Subject: fix: bit of vibed update --- alfred/twt.py | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 253 insertions(+), 2 deletions(-) (limited to 'alfred/twt.py') diff --git a/alfred/twt.py b/alfred/twt.py index 0953d3c..97bf5df 100644 --- a/alfred/twt.py +++ b/alfred/twt.py @@ -16,8 +16,10 @@ Config ([twt] section in sopel config): url - url value written in file header """ +import glob import os -from datetime import datetime +from datetime import datetime, timezone +from pathlib import Path from zoneinfo import ZoneInfo from sopel import plugin @@ -31,7 +33,209 @@ import helpstrings as h _pending_delete = {} -_SUBCOMMANDS = {"last", "count", "delete", "yes", "no"} +_SUBCOMMANDS = {"last", "count", "delete", "yes", "no", "add", "users", "rm"} + +WKFO_TOKENS = '/etc/nginx/twt-tokens.map' +WKFO_TWR = '/var/www/wk.fo/twt' + + +def _load_twt_tokens(): + tokens = {} + try: + with open(WKFO_TOKENS) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#') or not line.startswith('"'): + continue + end = line.index('"', 1) + tok = line[1:end] + rest = line[end+1:].strip().rstrip(';').split('#')[0].strip() + if rest: + tokens[tok] = rest + except FileNotFoundError: + pass + return tokens + + +def _save_twt_tokens(tokens): + with open(WKFO_TOKENS, 'w') as f: + for tok, user in tokens.items(): + f.write(f'"{tok}" {user};\n') + + +def _esc(s): + return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') + + +def _load_twtxt(path): + twts = [] + try: + with open(path) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + parts = line.split('\t', 1) + if len(parts) == 2: + try: + dt = datetime.fromisoformat(parts[0].replace('Z', '+00:00')) + except ValueError: + dt = datetime.min.replace(tzinfo=timezone.utc) + twts.append((dt, parts[1])) + except FileNotFoundError: + pass + return twts + + +_WKFO_HEAD = """\ + + + + + +{title} + + + +""" + +_WKFO_FOOTER = """\ + + +""" + + +def _build_user_page(username): + twts = _load_twtxt(f'{WKFO_TWR}/~{username}/twtxt.txt') + items = '\n'.join( + f'
  • [{dt.strftime("%Y-%m-%d")}] {_esc(text)}
  • ' + for dt, text in reversed(sorted(twts)) + ) or '
  • (no twts yet)
  • ' + + return ( + _WKFO_HEAD.format(title=f'wk.fo / twt / ~{_esc(username)}') + '\n' + '
    \n' + f'\n' + '
    \n' + '
    \n' + f'

    ~{_esc(username)}

    \n' + f'

    twtxt feed

    \n' + '
    \n' + '

    \n' + '

    \n' + '

    \n' + '

    \n' + '
    \n' + '\n' + '
    \n' + '\n' + '
    \n' + + _WKFO_FOOTER + ) + + +def _build_index_page(): + users = sorted( + os.path.basename(d)[1:] + for d in glob.glob(f'{WKFO_TWR}/~*') + if os.path.isdir(d) + ) + + all_twts = [] + for u in users: + for dt, text in _load_twtxt(f'{WKFO_TWR}/~{u}/twtxt.txt'): + all_twts.append((dt, u, text)) + all_twts.sort(reverse=True) + + feed_items = '\n'.join( + f'
  • [{dt.strftime("%Y-%m-%d")}] ' + f'~{_esc(u)}: {_esc(text)}
  • ' + for dt, u, text in all_twts[:50] + ) or '
  • (nothing yet)
  • ' + + user_items = '\n'.join( + f'
  • ~{_esc(u)} ' + f'(twtxt)
  • ' + for u in users + ) or '
  • (no users yet)
  • ' + + return ( + _WKFO_HEAD.format(title='wk.fo / twt') + '\n' + '
    \n' + '\n' + '
    \n' + '
    \n' + '

    twt

    \n' + '

    Hosted twtxt. Short messages from friends.

    \n' + '

    feed

    \n' + '\n' + '

    users

    \n' + '\n' + '
    \n' + + _WKFO_FOOTER + ) + + +def _wkfo_regen(bot, username=None): + try: + root = Path(WKFO_TWR) + root.mkdir(parents=True, exist_ok=True) + if username: + Path(f'{WKFO_TWR}/~{username}/index.html').write_text( + _build_user_page(username), encoding='utf-8') + Path(f'{WKFO_TWR}/index.html').write_text( + _build_index_page(), encoding='utf-8') + except Exception as e: + bot.say(f'regen error: {e}') class TwtSection(StaticSection): @@ -168,6 +372,53 @@ def cmd_twt(bot, trigger): bot.say("cancelled.") return + # --- wk.fo/twt user management --- + if sub == "users": + tokens = _load_twt_tokens() + if not tokens: + bot.say("no wk.fo/twt users") + return + users = sorted(set(tokens.values())) + bot.say(", ".join(users)) + return + + if sub == "add": + if len(args) < 3: + bot.say("usage: !twt add ") + return + username = args[1] + token = args[2] + tokens = _load_twt_tokens() + if token in tokens: + bot.say(f"token already exists for {tokens[token]}") + return + user_dir = Path(f'{WKFO_TWR}/~{username}') + user_dir.mkdir(parents=True, exist_ok=True) + twtxt = user_dir / 'twtxt.txt' + if not twtxt.exists(): + twtxt.write_text(f'# nick = {username}\n# url = https://wk.fo/twt/~{username}/twtxt.txt\n') + tokens[token] = username + _save_twt_tokens(tokens) + _wkfo_regen(bot, username) + bot.say(f"added {username}") + return + + if sub == "rm": + if len(args) < 2: + bot.say("usage: !twt rm ") + return + target = args[1] + tokens = _load_twt_tokens() + before = len(tokens) + tokens = {t: u for t, u in tokens.items() if u != target} + if len(tokens) == before: + bot.say(f"not found: {target}") + return + _save_twt_tokens(tokens) + _wkfo_regen(bot) + bot.say(f"removed {target} (feed files kept)") + return + # --- post (anything that's not a subcommand) --- ts = _append(bot, raw) bot.say(f"twted [{ts[:10]}]: {raw}") -- cgit v1.2.3