diff --git a/README.md b/README.md index 117cbdb..0441f8a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ python3 bot.py | `!quote` | Random quote | | `!quote ` | Quote by number | | `!addquote ` | Store a new quote, returns its id | +| `!grabquote [user]`| Grab the last line a user said (`!grab` alias); no user = last speaker | | `!delquote ` | Delete a quote | | `!search ` | First quote containing a substring | | `!quotecount` | How many quotes are stored | @@ -36,6 +37,9 @@ python3 bot.py ## Notes - Quotes live in `quotes.db` (SQLite, created on first run, gitignored). +- `!grabquote` reads an in-memory buffer of each user's last channel line; + that buffer is per-session and resets on reconnect. Grabbed lines are stored + as ` message`. Bot commands are never recorded, so you can't grab one. - The bot auto-reconnects with exponential backoff and answers `PING`. - If the nick is taken it appends `_` and retries. - A NickServ password, if set via `IRC_NICKSERV_PASS`, is sent on connect — diff --git a/bot.py b/bot.py index c760ef4..d3aa837 100644 --- a/bot.py +++ b/bot.py @@ -113,6 +113,10 @@ class IRCBot: self.store = store self.sock: socket.socket | None = None self._recv_buf = b"" + # Last non-command channel line per user, for !grabquote. + # Keyed by lowercased nick -> (original_nick, text). + self.last_lines: dict[str, tuple[str, str]] = {} + self.last_speaker: str | None = None # --- low-level I/O ---------------------------------------------------- @@ -233,11 +237,17 @@ class IRCBot: return target, message = params[0], params[1] sender = prefix.split("!", 1)[0] if prefix else "" + is_channel = target.startswith(("#", "&")) # Reply to the channel for channel messages, or to the user for PMs. - reply_to = target if target.startswith(("#", "&")) else sender + reply_to = target if is_channel else sender if not message.startswith(CMD_PREFIX): + # Remember the last real line each user said, so !grabquote can + # recall it. Bot commands are deliberately not recorded. + if is_channel and sender: + self.last_lines[sender.lower()] = (sender, message) + self.last_speaker = sender return parts = message[len(CMD_PREFIX):].strip().split(" ", 1) cmd = parts[0].lower() @@ -293,6 +303,21 @@ def cmd_delquote(bot: IRCBot, reply_to: str, sender: str, arg: str): bot.send_privmsg(reply_to, f"Deleted quote #{arg}." if ok else f"No quote #{arg}.") +def cmd_grabquote(bot: IRCBot, reply_to: str, sender: str, arg: str): + """!grabquote [user] — store the last line a user said (default: last speaker).""" + nick = arg.split()[0] if arg.split() else bot.last_speaker + if not nick: + bot.send_privmsg(reply_to, "Nothing to grab yet.") + return + rec = bot.last_lines.get(nick.lower()) + if not rec: + bot.send_privmsg(reply_to, f"I haven't seen {nick} say anything.") + return + orig_nick, text = rec + qid = bot.store.add(f"<{orig_nick}> {text}", sender) + bot.send_privmsg(reply_to, f"Grabbed quote #{qid}: <{orig_nick}> {text}") + + def cmd_search(bot: IRCBot, reply_to: str, sender: str, arg: str): """!search — first quote matching a substring.""" if not arg: @@ -310,14 +335,16 @@ def cmd_count(bot: IRCBot, reply_to: str, sender: str, arg: str): def cmd_help(bot: IRCBot, reply_to: str, sender: str, arg: str): bot.send_privmsg( reply_to, - "Commands: !quote [id], !addquote , !delquote , " - "!search , !quotecount, !help", + "Commands: !quote [id], !addquote , !grabquote [user], " + "!delquote , !search , !quotecount, !help", ) COMMANDS = { "quote": cmd_quote, "addquote": cmd_addquote, + "grabquote": cmd_grabquote, + "grab": cmd_grabquote, "delquote": cmd_delquote, "search": cmd_search, "quotecount": cmd_count, diff --git a/memory.md b/memory.md index 89827f8..ee835d8 100644 --- a/memory.md +++ b/memory.md @@ -41,6 +41,12 @@ Durable memory for this project. Read at session start, update before session en NickServ login landed and getting bounced (477); now defers JOIN until the `900`/"now identified" confirmation. Verified join succeeds (got 353/366). +### 2026-06-04 (cont.) +- Added `!grabquote [user]` (alias `!grab`): stores the last line a user said, + formatted ` message`. Backed by an in-memory `last_lines` buffer + (per-session, resets on reconnect); bot commands are excluded from it. No-arg + form grabs the last real speaker. Offline-tested. + ## External references - Libera.Chat: — server `irc.libera.chat:6697` (TLS).