Forum teuk.org

⏳🧠 Mediabot v3: The Time‑Turner & Pensieve Pass — Reminders, Summaries, and Safer Stats

in Mediabot · started by TeuK · yesterday

TeuK · yesterday

After the Fawkes bugfix pass, Mediabot v3 did not need another dramatic phoenix flight.

This time, the work was quieter, more precise, and a little more temporal.

A Time‑Turner was taken from the drawer.
A Pensieve was placed on the desk.
And Mediabot went through a pass focused on time-aware reminders, smarter summaries, safer statistics, and heavier commands that now behave responsibly.

No database migration.
No schema change.
No giant rewrite.

Just careful magic in the places where time, memory, and IRC history meet.

Recurring reminders should respect time.
Summaries should remember what was already summarized.
Statistics should not freeze the bot.
Commands should fail cleanly when used in the wrong place.

That was the spirit of this pass.


⏳ Why this pass mattered

Mediabot has grown a lot recently.

It now has richer radio tools, AI summaries, Partyline diagnostics, karma history, reminders, activity stats, YouTube helpers, URL handling, and many small IRC commands that people actually use.

That is good.

But once a bot starts dealing with time — daily reminders, weekly reminders, summaries since last call, “today”, “week”, “yesterday”, streaks, word counts — the risks change.

A small mistake can mean:

  • a reminder fires immediately instead of later;
  • a daily reminder comes back after being cancelled;
  • a summary is sent to the wrong target;
  • a statistics command scans far too much history;
  • a typo creates a reminder for a nick that will never appear.

So this pass focused on making Mediabot’s sense of time more reliable.


🕰 Daily reminders: no more cursed resurrection

A previous improvement introduced daily reminders.

That was useful, but a defensive edge case needed attention: a cancelled daily reminder must never be able to resurrect itself.

The reinsert logic now checks the reminder state carefully before scheduling the next occurrence.

In other words:

Cancelled means cancelled.

No Inferi reminders crawling out of the database the next morning.


🪄 Snoozing recurring reminders properly

Recurring reminders store metadata in the message field, for example:

[daily:09:00] [at:TS] drink coffee

A snooze operation used to remove only an [at:TS] tag at the very beginning of the message.

That was not enough.

For recurring reminders, the [at:TS] tag may appear after [daily:...] or [weekly:...].

The snooze logic now strips existing [at:TS] tags wherever they are before adding the new one.

Result:

[at:NEW_TS] [daily:09:00] drink coffee

instead of:

[at:NEW_TS] [daily:09:00] [at:OLD_TS] drink coffee

No more raw epoch timestamps sneaking into IRC like cursed runes.


📅 Weekly reminders arrive

The Time‑Turner work also added weekly reminders:

!remind weekly mon 09:00 gwen weekly meeting

Supported day names include English and French-style shortcuts such as:

mon/lun
tue/mar
wed/mer
thu/jeu
fri/ven
sat/sam
sun/dim

The design stays intentionally simple: no schema migration, no extra table.

The recurrence metadata lives inside the existing message field using a tag like:

[weekly:MON:09:00]

When delivered, the reminder is recreated for the following week.

This keeps the feature useful without turning the database into a Ministry paperwork dungeon.


🧾 Reminder lists are cleaner

Recurring reminder tags are useful internally, but they should not leak into user-facing output.

The list/show output now reformats reminder metadata into something readable:

#12 -> gwen [in 2h30m] [daily 09:00]: "bonjour !"
#15 -> teuk [in 3d] [weekly Mon 09:00]: "meeting"

That is much better than exposing raw tags like:

[daily:09:00] [at:TS]

A good spellbook should hide the plumbing.


🧭 Reminders work better in private messages

Some reminder queries filtered by channel name even when the command was used in private message.

That meant private remind list / remind show could fail or behave inconsistently because there was no current channel.

Now the private-message path searches reminders across all relevant channels instead of applying a broken channel filter.

So this works properly:

/msg mediabot remind list

The bot should not forget how to read its own scrolls just because you asked quietly.


🧑‍🚫 Unknown reminder targets are refused

Before, a typo could create a reminder for a nick that did not exist.

That reminder could then sit in the database forever, waiting for a ghost.

Now !remind validates the target nick before accepting the reminder.

It checks:

  1. the current channel nicklist;
  2. USER_SEEN;
  3. the registered USER table.

If the nick is unknown, the reminder is refused:

Unknown nick 'maauvaissnick'. The remind was not created.

This is one of those small changes that prevents long-term mess.

A reminder should be a message to a person, not a letter to Azkaban.


🧠 Pensieve summaries: !ai summary last

The AI summary feature gained a very useful memory trick:

!ai summary last

Instead of summarizing the whole day again, Mediabot can summarize only the messages since the previous successful !ai summary on that channel.

Example flow:

!ai summary today

Two hours later:

!ai summary last

Mediabot summarizes only the recent discussion since the last summary.

The timestamp is stored in memory, not persisted across restarts. That is deliberate and lightweight.

It feels like a Pensieve: useful while the memory is fresh, not a permanent archive charm.


🧪 Summary cache-hit respects the output target

There was a subtle callback bug in the Claude summary path.

When a summary response was served from cache, it could ignore the caller’s output callback and send the response in the wrong place.

For example, a summary intended as a NOTICE could come back as a channel PRIVMSG on cache hit.

That is now corrected: cached responses use the same output path as fresh responses.

Same spell, same destination.


📊 !top grows smarter

!top can now focus on better time windows and bot filtering:

!top today
!top yesterday
!top week
!top nobots
!top bots
!top 10 week

The bot exclusion list can be configured through:

main.BOT_NICKS = mediabot,rssBotto,...

The bot’s own nick is always considered a bot.

This makes activity stats much more useful on channels where bots talk a lot.

Sometimes you want the living humans.
Sometimes you want to know how much the machines are shouting.

Now Mediabot can tell the difference.


📈 !active understands real calendar periods

!active also gained calendar-friendly periods:

!active today
!active yesterday
!active week

That is different from sliding windows like:

!active 3d
!active 12h

Both styles are useful.

Calendar periods answer human questions:

Who talked today?
Who was active yesterday?
Who has been around this week?

The castle clock matters.


🗓 !monthstats can compare two nicks

Monthly stats gained a comparison mode:

!monthstats teuk vs menz

The output shows two normalized mini-bars per month, making it easy to compare activity over the last 12 months.

This is the kind of feature that looks small but is very handy for long-running IRC communities.

Mediabot has history.
Now it can compare some of it more clearly.


🪙 !karmadiff gets time windows and top givers

!karmadiff became more useful too:

!karmadiff teuk 6h
!karmadiff teuk 12h
!karmadiff teuk 24h
!karmadiff teuk 7d

It now shows:

  • karma movement;
  • chosen time window;
  • vote count;
  • current score;
  • top givers.

Example:

teuk: karma +3 in last 6h (5 vote(s), score: +42) | by: menz(3), gwen(1), poyan(1)

That turns karma from a raw number into a small story.


🧮 !calclast respects public/private context

!calclast used to always answer by NOTICE, even when called publicly from a channel.

Now it behaves like the other history commands:

  • public call → public channel reply;
  • private call → private NOTICE.

This keeps command behavior consistent.

No need for a private owl when the question was asked in the Great Hall.


🧯 !wordcount now has a safety limit

This was an important safety fix.

!wordcount could previously read every matching line from CHANNEL_LOG for a nick.

On old active channels, that can mean a very large query and a lot of memory.

Now the command limits itself to the most recent 50,000 messages:

ORDER BY cl.id DESC
LIMIT 50000

If the limit is reached, the output mentions it:

[last 50k msgs]

That is honest, fast, and much safer.

A statistics command should not be able to stun the bot like a badly aimed Stupefy.


🛡 !wordcount now refuses private context

Since wordcount is channel-scoped, it should not run from a private query where there is no channel.

The command now fails cleanly:

wordcount must be used from a channel.

That avoids undefined channel behavior and keeps the command semantics clear.


🔥 !streak rank query gets a TTL cache

The rank calculation for !streak can be expensive because it has to group activity over a long period.

Now the rank result is cached for 5 minutes per:

channel + target + current streak

That means repeated calls do not hammer the database unnecessarily.

If the streak changes, the cache key changes naturally.

A neat little charm: simple, effective, and not over-engineered.


🎧 Spotify refactoring: fewer tangled vines

External.pm is still huge, but one knot was untangled.

The Spotify handler had several closures inside _handle_spotify, making the function hard to read and hard to test.

Those helpers are now private module functions:

_spotify_is_bad
_spotify_clean
_spotify_duration_from_ms
_spotify_duration_from_iso
_spotify_extract_meta
_spotify_extract_jsonish

The external behavior stays the same, but the code is less tangled.

A small step toward the larger future refactor of External.pm.


🏗 About the future External.pm refactor

The plan is clear but intentionally not executed in this pass.

External.pm is still large and wants to become a façade over smaller modules:

Mediabot::External::Claude
Mediabot::External::Spotify
Mediabot::External::URL
Mediabot::External::YouTube

That is the right direction.

But it should be done later, on a dedicated snap, with focused tests.

This pass was about reminders, summaries, activity stats, and safety.

One chamber at a time.


🛡 No database schema change

This point matters:

No SQL migration required.
No new table.
No new column.

Weekly reminders, daily reminders, snooze timestamps, and recurrence metadata all continue to use the existing reminder structure.

The improvements are application-level.

That makes the deployment much safer.


🧪 Suggested regression tests

Reminders

m remind mauvaisnick bonjour
m remind weekly mon 09:00 teuk test weekly
m remind daily 09:00 teuk test daily
m remind list
m remindsnooze <id> 30m
m remind cancel <id>

AI summary

m ai summary today
m ai summary today
m ai summary last

The second summary call should respect the same output target as the first one, even if it hits the cache.

Activity and stats

m top today
m top yesterday
m top week
m top nobots
m top bots
m active today
m active yesterday
m active week
m monthstats teuk vs menz
m karmadiff teuk 6h
m wordcount teuk
m streak teuk
m streak teuk

Private command guard

In private message:

wordcount teuk

Expected:

wordcount must be used from a channel.

Shell checks

perl -I. -c Mediabot/UserCommands.pm
perl -I. -c Mediabot/External.pm
perl -I. -c Mediabot/Partyline.pm
perl -I. -c Mediabot/DBCommands.pm
perl -I. -c Mediabot/AdminCommands.pm
perl -I. -c Mediabot/Helpers.pm
perl -I. -c Mediabot/ChannelCommands.pm
perl -I. -c Mediabot/Hailo.pm
perl -I. -c Mediabot/LoginCommands.pm
perl -I. -c mediabot.pl

LC_ALL=C grep -P '\x00' -n Mediabot/*.pm Mediabot/*/*.pm || true
git diff --check

🏁 Final status

This was a Time‑Turner and Pensieve pass.

Mediabot now handles time and memory more carefully:

  • daily reminders are safer;
  • weekly reminders exist;
  • snooze cleans old timestamps properly;
  • private reminder listing works better;
  • unknown reminder targets are refused;
  • !ai summary last can summarize only recent discussion;
  • summary cache-hit output goes to the right target;
  • !top and !active understand better time periods;
  • !wordcount is bounded and channel-only;
  • !streak rank has a TTL cache;
  • Spotify parsing is less tangled internally;
  • no database schema change.

You must be logged in to reply.