Tracks when users were last seen in chat channels.
The seen module is a chat activity tracker for the eevee ecosystem. It passively observes all messages across connected channels and records each user's last activity — when they spoke and what they said. Users can then query this history to find out when someone was last active, who's been around recently, or who's been lurking.
Seen uses a SQLite database keyed by (nick, platform, network, instance, channel), making it fully multi-platform aware. It works across IRC, Discord, and any connector the eevee router supports. On IRC, responses are colorized using semantic colors (cyan for nicks, green for timestamps, yellow for actions, blue for info).
The module registers a broadcast with the router to receive all messages, then upserts each event into the database. Commands are registered via NATS and routed through the standard eevee command pipeline with built-in rate limiting.
- Passive tracking — Records every user message across all platforms, networks, and channels automatically
- Multi-platform — Stores activity keyed by platform, network, instance, and channel
- IRC colorization — Semantic color coding for nicknames, timestamps, and actions on IRC platforms
- Departure tracking — Records when users leave channels (part/quit/kick) with their last message snapshot
- Five query commands —
seen,since,lurkers,lurkers-report, andlastwordsfor different views of activity data - Rate limited — All commands respect configurable rate limits via libeevee defaults
- Graceful shutdown — Closes the SQLite database cleanly on termination
- Migration support — Includes a script to migrate from the old single-platform schema
This module is part of the eevee ecosystem and is not published independently.
# From the eevee project root
npm installTo build the seen module specifically:
cd seen
npm run buildSeen reads its configuration from the standard eevee module config via loadModuleConfig. The following options are available:
| Key | Type | Default | Description |
|---|---|---|---|
ratelimit |
RateLimitConfig |
defaultRateLimit |
Rate limit settings for all seen commands |
dbPath |
string |
(auto) | Override the database file path (default: $MODULE_DATA/seen.db) |
| Variable | Required | Description |
|---|---|---|
MODULE_DATA |
Yes | Directory where seen.db is stored. Created automatically if it doesn't exist. |
Other standard eevee environment variables (NATS_URL, etc.) are also required — see the main eevee documentation.
Show when a user was last seen and what they last said.
Example:
> seen alice
alice: [alice] [2026-05-07 14:32] [haha nice]
If the user has never been seen:
> seen bob
bob: I haven't seen bob yet
Show all users seen in the last X minutes. Maximum lookback is 1440 minutes (24 hours).
Example:
> since 60
alice: In the last 60 minutes, I've seen: alice, charlie, dave
If no one has been seen:
> since 5
alice: I haven't seen anyone yet
Show users currently in the channel who haven't been seen in the specified number of days. This cross-references the seen database with the live channel user list (queried from the IRC connector) to find lurkers who are present but inactive.
| Parameter | Default | Max | Description |
|---|---|---|---|
days |
30 | 365 | How far back to look for last activity |
--limit N / -l N |
10 | 50 | Maximum number of results to return |
Examples:
> lurkers # Top 10 lurkers from the last 30 days
> lurkers 60 # Top 10 lurkers from the last 60 days
> lurkers 30 --limit 5 # Top 5 lurkers from the last 30 days
> lurkers --limit 20 # Top 20 lurkers from the last 30 days
Example output:
alice: Top 10 lurkers not seen in the last 30 days (currently in channel): bob (45d), eve (never), frank (67d)
Users who are in the channel but have no record at all are shown as (never).
Comprehensive lurkers report — admin only. Unlike lurkers, this command shows all channel users (active, inactive, and never seen) and delivers the full report via private message, keeping the channel clean.
| Parameter | Default | Max | Description |
|---|---|---|---|
days |
30 | 365 | How far back to look for last activity |
Requirements:
- You must be a channel admin (have
+omode) to run this command. - The report is sent via private message — only you see it.
Examples:
> lurkers-report # Full report with 30-day window
> lurkers-report 7 # Full report with 7-day window
Example output (via private message):
=== Lurkers Report for #channel (30 day window) ===
Active users (seen within 30 days): alice (today), bob (5d ago), carol (today)
Inactive users (not seen in 30 days): dave (45d ago), eve (62d ago)
Never seen: frank, greg
Total: 7 users (3 active, 2 inactive, 2 never seen)
The channel receives a brief confirmation: alice: Lurkers report sent via private message.
Show what a user last said before leaving a channel (part, quit, or kick). Looks up the most recent departure across all channels — not limited to the channel where the command is issued.
Example (part):
> lastwords alice
alice: [alice] Last said "brb getting oil" at 2026-08-15 22:14, parted from #eevee at 2026-08-15 22:17 (Ping timeout)
Example (kick):
> lastwords bender
alice: [bender] Last said "bite my shiny metal ass" at 2026-08-15 20:03, kicked from #eevee at 2026-08-15 20:05 by coilette (too rude)
Example (quit):
> lastwords fry
alice: [fry] Last said "i'm confused" at 2026-08-15 18:30, quit from #eevee at 2026-08-15 18:45 (Connection reset)
If no departure record exists:
> lastwords zoidberg
alice: I don't have a departure record for zoidberg
Only users who have spoken at least once (have a seen_users record) get a departure entry. Lurkers who never spoke are not tracked.
┌─────────────┐ broadcast.register ┌──────────┐
│ seen module ├───────────────────────►│ router │
│ (startup) │ │ │
└──────┬───────┘ └────┬─────┘
│ │
│ broadcast.message.<uuid> │ command.execute.<uuid>
│ (all chat messages) │ (command invocations)
◄─────────────────────────────────────┤
│ │
▼ │
┌──────────────┐ │
│ SQLite DB │ │
│ (seen_users)│ │
└──────────────┘ │
│
◄─────────────────────────────────────┘
│
│ queryChannelUsers() (lurkers only)
├──────────────────────────────────────► IRC connector
◄──────────────────────────────────────┘
- Startup — The module creates a NATS connection, initializes the SQLite database, registers a broadcast with the router to receive all chat messages, and registers an event subscription to receive departure events (part/quit/kick).
- Message tracking — Every broadcast message triggers an upsert into the
seen_userstable, recording the nick, timestamp, message text, platform, network, instance, and channel. - Departure tracking — When a user parts, quits, or is kicked, the connector publishes an event to NATS. The router forwards it to the seen module, which looks up the user's
seen_usersrecord and snapshots their last message intouser_departures. Lurkers who never spoke are skipped. - Command handling — Commands (
seen,since,lurkers,lurkers-report,lastwords) are registered with the router. When invoked, they query the database and send a formatted response back to the channel. - Lurkers — The
lurkerscommand additionally queries the IRC connector viaqueryChannelUsers()to get the current channel membership, then cross-references with the seen database.
CREATE TABLE seen_users (
nick TEXT,
date TEXT, -- ISO 8601 timestamp
text TEXT, -- last message content
platform TEXT, -- e.g. "irc", "discord"
network TEXT, -- e.g. "libera"
instance TEXT, -- e.g. "default"
channel TEXT, -- e.g. "#general"
PRIMARY KEY (nick, platform, network, instance, channel)
);
CREATE TABLE user_departures (
nick TEXT,
platform TEXT,
network TEXT,
instance TEXT,
channel TEXT,
last_message TEXT, -- snapshot of user's last message
last_message_date TEXT, -- when the last message was sent
departure_date TEXT, -- when the departure occurred
departure_type TEXT, -- 'part', 'quit', or 'kick'
departure_reason TEXT, -- reason message (if any)
kicked_by TEXT, -- kicker's nick (kick only, NULL otherwise)
PRIMARY KEY (nick, platform, network, instance, channel)
);On IRC platforms, responses are colorized using a semantic color map defined in src/utils/colorize.mts:
| Semantic type | IRC color | Used for |
|---|---|---|
user |
cyan | Nicknames |
date |
green | Timestamps, numeric values |
action |
yellow | Last message content |
info |
blue | Informational text |
warning |
olive | Error/usage messages |
Non-IRC platforms receive plain-text responses without color codes.
# Clone the repo and install
git clone <repo-url>
cd eevee
npm install
# Build the seen module
cd seen
npm run build
# Watch mode (build + run with hot reload)
npm run dev
# Lint
npm testIf upgrading from a previous version of eeveebot that used the single-platform seen table, use the migration script:
node --loader ts-node/esm src/migrations/migrate-from-old-eeveebot.mts \
./old-seen.db ./seen.db irc libera default "#general"Parameters:
| Position | Default | Description |
|---|---|---|
old-db-path |
(required) | Path to the old database |
new-db-path |
(required) | Path for the new database |
platform |
irc |
Platform identifier |
network |
unknown |
Network identifier |
instance |
unknown |
Instance identifier |
channel |
unknown |
Channel identifier |
Contributions are welcome! Bug reports and pull requests can be filed at the module's repository.
CC BY-NC-SA 4.0 — see LICENSE for the full text.