Forum teuk.org

πŸͺ„πŸ“œβ³ Mediabot v3: Accented Runes Stay Whole, Old Polls Return to the Present, and Leap-Day Birthdays Find Their Date β€” MB421 to MB435

in Mediabot Β· started by TeuK Β· 1mo ago

TeuK Β· 1mo ago

The previous chapter ended with secret-bearing API calls using verified TLS, OpenAI failures becoming actionable, and a song request losing the ability to disguise itself as a yt-dlp option.

This chapter begins immediately after MB420.

It covers fourteen Claude rounds and one final independent pre-commit audit:

MB421  readable Fortnite numbers
MB422  YouTube search durations keep their days
MB423  one shared YouTube view formatter
MB424  TMDB falls back to the original title
MB425  real IRC strikethrough for discarded dice
MB426  accented words stay whole in wordcount
MB427  compat and mood understand the same accented tokens
MB428  quote excerpts stop cutting UTF-8 glyphs
MB429  one shared UTF-8 truncation helper
MB430  achievement channel keys become canonical
MB431  extending an expired poll starts from now
MB432  Hailo ratio lookups leave the hot message path
MB433  leap-day birthdays are observed in short Februaries
MB434  birthday countdown agrees with the announcement
MB435  final audit of Unicode, cache invalidation and mixed-case aggregates

Mediabot remains on the 3.2dev line.

This is not the 3.3 release.

As always:

No database schema was changed.


🎯 MB421 β€” Fortnite numbers stop looking like serial codes

The Fortnite API may return long floating-point values for K/D and win rate.

A perfectly ordinary result could therefore arrive on IRC as:

K/D: 1.2345678901234 | Wins: 42 (8.5714285%)

MB421 changes only the presentation:

K/D      β†’ two decimal places
win rate β†’ one decimal place
trailing zeroes removed
unexpected non-numeric values preserved rather than forged into zero

The same result now reads like a score:

K/D: 1.23 | Wins: 42 (8.6%)

β³πŸ“Ί MB422–MB423 β€” YouTube receives one hourglass and one abacus

Search results no longer lose whole days

The main YouTube formatter already understood durations such as:

P1DT2H3M4S

The search command still contained an older inline parser that ignored the day component. A 26-hour archive could be displayed as only 2:03:04.

MB422 removes that duplicate interpretation and reuses the shared ISO-8601 parser. Search output remains compact by folding days into hours:

P1DT2H3M4S β†’ 26:03:04
P3D         β†’ 72:00:00

View counts stop changing dialect between commands

Two more inline formatters still disagreed on whether 45,000 views should look like:

45K
45.0k

MB423 routes every YouTube path through the existing shared formatter:

1,234     β†’ 1.2k
45,000    β†’ 45.0k
1,200,000 β†’ 1.2M

Unknown values are omitted from search lines instead of being advertised as a mysterious question mark.


🎬 MB424 β€” The original title steps in when the translation reel is blank

TMDB sometimes returns an empty localized title while still providing a valid original title.

The previous selection looked only at the localized movie or series field and could display:

Unknown title

for a result whose real name was present in original_title or original_name.

MB424 chooses the first non-empty value from:

title
name
original_title
original_name

It also derives β€œMovie” or β€œTV Series” from the already validated media_type rather than guessing from which title field happened to exist.


🎲 MB425 β€” The discarded die uses IRC ink, not Markdown tildes

Advantage and disadvantage rolls marked the discarded die with Markdown syntax:

[15, ~~8~~]

IRC clients displayed the tildes literally.

MB425 replaces them with the real IRC strikethrough control code followed by a reset. Compatible clients show the discarded result crossed out; simpler clients still show the number normally.

No Markdown costume remains in the IRC output path.


πŸ”€πŸ‡«πŸ‡· MB426–MB427 β€” Accented words remain words

CHANNEL_LOG.publictext is returned by DBI as UTF-8 bytes rather than decoded Perl characters.

Splitting those bytes with character-oriented expressions broke French words apart:

cafΓ©     β†’ caf
rΓ©ponse  β†’ r + ponse
NoΓ«l     β†’ No + l

MB426 makes wordcount use a byte-safe token boundary so UTF-8 byte sequences stay together.

MB427 applies the same rule to:

compatibility vocabulary
mood sentiment vocabulary

This matters especially for the French sentiment dictionary. Words such as ratΓ© and pitiΓ© can now reach the dictionary intact instead of becoming fragments that never match.


βœ‚οΈπŸ“œ MB428–MB429 β€” A rune is never cut in half

Quote excerpts were shortened with raw substr calls at byte limits such as 200 or 300.

If the boundary landed between the two bytes of an accented character, the output ended with invalid UTF-8 and a replacement glyph.

MB428 adds a safe quote excerpt routine and applies it to:

quote view
quote search
random quote
quotes by nick

MB429 promotes the same behaviour into one shared helper:

Mediabot::Helpers::truncate_utf8()

The helper is then reused for channel-log excerpts and Partyline displays rather than maintaining several subtly different blades.


πŸ†πŸ—οΈ MB430 β€” Every trophy is stored under one channel name

Achievement keys folded the nick to lowercase but kept the channel spelling exactly as received.

Because IRC channel names are case-insensitive, the same hall could acquire two trophy cabinets:

teuk + #Teuk
teuk + #teuk

That allowed duplicate unlock announcements and made lookups dependent on casing.

MB430 lowercases the channel component in every achievement key and migrates existing JSON data during load.

When two historical keys collapse into one, achievements are merged and the earliest unlock timestamp wins. Existing trophies are not discarded.


πŸ—³οΈβ° MB431 β€” An expired poll returns to the present

Poll expiration is lazy: a deadline may have passed while the in-memory poll still says it is active.

pollextend used to add time to the old deadline even when that deadline was already in the past:

past deadline + 60 seconds β†’ still in the past

The command could announce a negative remaining time and fail to reopen voting.

MB431 anchors the extension to:

max(current time, existing deadline)

Future polls still extend cumulatively. Lapsed polls receive a genuinely future deadline.


🧠⚑ MB432 β€” Hailo stops asking MariaDB about every line

The Hailo chatter decision runs for every public message.

Its channel ratio lookup still performed a SELECT and JOIN on every one of those messages, including channels with no configured ratio.

MB432 adds a lowercase-keyed 60-second cache and stores negative results as well:

30 messages β†’ one lookup instead of 30

An explicit ratio change invalidates the cache so the command does not have to wait for the TTL.

The final audit found one missing branch in that promise; MB435 completes it below.


πŸŽ‚πŸŒ™ MB433–MB434 β€” Leap-day birthdays find a place in short Februaries

A person born on 29 February was announced only in leap years because the daily calendar simply never reaches 02-29 in the other three years.

MB433 adopts a clear observance rule:

leap year     β†’ celebrate on 29 February
non-leap year β†’ celebrate on 28 February

The Gregorian century rules remain respected: 2000 is a leap year; 2100 is not.

MB434 then aligns birthday next with the same rule. The countdown no longer points several years ahead to the next literal 29 February while the daily announcement plans to celebrate the person on the coming 28th.

The song and the countdown now agree.


πŸ”πŸ›‘οΈ MB435 β€” The final pre-commit audit catches three loose threads

The fresh MB434 snapshot was reviewed rather than committed blindly.

Three real gaps remained.

A decoded Claude answer was sent through a byte-only truncator

truncate_utf8() correctly handled raw UTF-8 bytes from DBI, but Partyline also uses it for Claude history returned by a JSON decoder.

That content is already a Perl character string.

Trying to decode it as UTF-8 a second time can raise:

Wide character

and break .ai context precisely when the answer contains non-ASCII text.

MB435 now distinguishes the two representations:

raw byte string      β†’ trim incomplete UTF-8 tail, return bytes
decoded char string  β†’ count encoded wire bytes, cut by codepoint, return chars

No double decoding and no severed glyph.

The common Hailo UPDATE path forgot to invalidate the cache

MB432 invalidated the new ratio cache after inserting the first setting for a channel.

The much more common path β€” updating an existing row β€” returned before that invalidation.

A successful setting change could therefore keep using the previous percentage for up to 60 seconds.

MB435 invalidates after both successful database paths.

Achievement aggregates compared lowercase storage with raw IRC casing

MB430 canonicalized stored achievement keys, but the dashboard and achievement leaderboard still compared them with the unmodified live channel string.

On a mixed-case target, real trophies could disappear from those two aggregate views.

Both scans now fold the live channel before comparison.


πŸ§ͺ Validation ledger

Claude reported the following full-suite baseline before the independent audit:

full suite through MB434: 8869/8869

The independent audit added:

MB435 contract test:                 15/15
locally executable MB421–MB435 set: 148/148
fresh-copy installation:             OK
second installation:                 idempotent
forced rollback:                     OK
embedded payloads:                   SHA256 verified
source diff whitespace check:        OK

The audit container does not contain the complete Perl runtime used by teuk.org, notably JSON::MaybeXS and IO::Async. It therefore does not claim a new full-suite total.

The MB435 installer performs the real syntax checks, the complete MB421–MB435 focused selection and the affected-area regression on the server before the commit can proceed. Any failure restores the MB434 files automatically.

The focused and full-suite totals overlap and must not be added together.


🧱 Database and release impact

0 new tables
0 altered columns
0 migrations
0 schema changes
0 release to 3.3

Mediabot remains on:

3.2dev-*

✨ What this chapter leaves behind

Mediabot now:

  • presents Fortnite statistics as readable scores;
  • keeps day components in long YouTube search results;
  • formats YouTube views consistently everywhere;
  • falls back to original TMDB titles when translations are blank;
  • uses real IRC formatting for discarded dice;
  • keeps accented French words intact in wordcount, compatibility and mood;
  • truncates raw database bytes and decoded AI text without breaking UTF-8;
  • stores every channel’s achievements under one canonical key;
  • reopens a lapsed poll from the present rather than from a dead deadline;
  • caches Hailo ratios off the hot message path and invalidates both write paths;
  • observes leap-day birthdays consistently in announcements and countdowns;
  • keeps achievement dashboards accurate even when IRC casing varies.

The castle did not gain a spectacular new tower in these rounds.

Instead, its clocks stopped losing days, its scribes stopped cutting runes in half, its trophy room stopped changing names, and even the people born on a date that sometimes vanishes were finally given a reliable place in the calendar.

That is the sort of quiet magic a stable release is built from.

You must be logged in to reply.