Development chronicle — mb620 to mb622 · Real daily signs, UTF-8 wire safety and async parity
Mediabot’s horoscope command has moved beyond a purely local deterministic flavour text.
This round adds a real daily horoscope provider by zodiac sign, keeps the existing local horoscope as a safe fallback, fixes the UTF-8 problem that could turn perfectly good French into mojibake on IRC, and makes sure both horoscope and its short alias horo use the same asynchronous execution path.
No database schema change was required.
The historical Mediabot horoscope remains in place:
The new layer enriches it with a real daily horoscope when Mediabot knows the zodiac sign.
Examples:
m horoscope lion
m horoscope bélier
m horoscope SaYa
m horoscope
A sign supplied explicitly wins over the birthday stored for the target user.
If no sign is supplied, Mediabot can still derive it from the existing USER.birthday value.
The horoscope module understands zodiac signs in several useful forms:
Lion
Leo
Bélier
Belier
Gémeaux
Gemini
Cáncer
Scorpio
♌
Internally everything is normalized to the canonical English slug expected by the provider:
Bélier ─► aries
Gémeaux ─► gemini
Lion ─► leo
Poissons ─► pisces
The normalization accepts both already-decoded IRC strings and raw UTF-8 byte input.
External data never replaces Mediabot’s local horoscope.
The flow is:
local horoscope
│
├── always available
│
└── known zodiac sign
│
▼
remote daily horoscope
│
├── valid response ─► extra line
│
└── failure ───────► local result only
Network failure, bad JSON, missing text or an unusable provider response does not produce an ugly error in the channel.
The user simply receives the local horoscope.
The remote provider speaks English.
On an English channel, its response can be used directly.
On French or Spanish channels, Mediabot asks the existing Claude layer for a short translation into the channel language.
If translation is unavailable, Mediabot prefers silence for that optional line rather than mixing an English sentence into an otherwise French or Spanish horoscope.
The local horoscope already uses a private deterministic PRNG rather than modifying Perl’s global random generator.
With explicit signs, the sign now also participates in the local seed.
That means:
m horoscope lion
m horoscope vierge
do not receive the exact same local flavour merely because they were requested by the same user on the same date.
The historical no-sign behaviour remains stable.
The visible bug was classic mojibake:
humeur électrique
The deeper cause was not the horoscope API.
Incoming IRC text is decoded into Perl character strings. Some modules, however, still contained non-ASCII source literals without:
use utf8;
Interpolating decoded IRC text into those byte literals could turn the whole outgoing line into mixed character/byte data and cause a second UTF-8 encoding step.
The repair was applied to the class of affected modules, not only to one horoscope string.
Modules that can emit non-ASCII literals now explicitly treat their source as UTF-8 characters.
The UTF-8 regression test does more than search source code.
It recreates Mediabot’s real outbound rule:
Perl character string
│
▼
UTF-8 encode once
│
▼
IRC bytes
│
▼
strict UTF-8 decode in the test
The guard checks that:
é do not reappear.The horoscope round is now numbered mb620, with test 803.
The wire-encoding round is mb621, with test 804.
This avoids colliding with the already published news rounds mb616 and mb617.
horo cannot block the IRC loopThe final review found one runtime asymmetry.
The long command had correctly moved to the async worker:
m horoscope lion
│
▼
CommandAsync
│
▼
HTTP / optional translation
But the historical short alias still called the implementation directly:
m horo lion
│
▼
synchronous call
Since the command can now perform network requests, the alias could block the IRC event loop while the long form did not.
That path is now closed.
Both forms use the same canonical worker label:
horoscope ─┐
├──► CommandAsync("horoscope") ─► mbHoroscope_ctx
horo ──────┘
They therefore share the same worker behaviour, channel lock, timeout handling and output facade.
The help text also now documents the same contract for both commands:
horoscope [nick|signe]
horo [nick|signe]
A dedicated test 805 locks this parity.
The final pre-commit validation targets the affected contracts:
803_mb620_horoscope_sign_api
804_mb621_wire_encoding
805_mb622_horoscope_alias_async
Test 805 passes independently with all six async/help assertions green.
The complete project suite does not need to be repeated merely for the renumbering and alias-parity cleanup; the server-side targeted tests are sufficient for this final pass.
The crystal ball now has two layers.
One is carved into the castle itself and never stops working.
The other looks outside for today’s stars when the sky is clear.
Whether a student asks for Lion, Leo or ♌, whether they call the spell horoscope or simply horo, and whether the Common Room speaks English or French, the same guarded path now leads to the answer.
And the accents finally arrive exactly once.
You must be logged in to reply.