Rabbot code conventions
Navigation for AI and new contributors: AGENTS.md, LIB_INDEX.md, CONTRACTS.md.
Keep plugins small and DRY โ reuse lib/ helpers instead of copying code.
Shared libraries
| Library | Use for |
|---|---|
| lib/irc_format.rb | color, decorate, bold, muted, tag, bracket_label, menu_heading, divider, nick, underline, report_header, report_footer |
| lib/irc_reply.rb | IrcReply.card โ compose Ibis-style reply cards |
| lib/flood_safe.rb | Long/multi-line IRC replies (flood_safe_reply) |
| lib/normalize.rb | Normalize.nick, Normalize.user_key, Normalize.channel |
| lib/rabbot_http.rb | RabbotHttp.fetch, RabbotHttp.fetch_json + User-Agent |
| lib/list_command.rb | ListCommand.parse_add_rem for add/rem/list plugins |
| lib/rabbot_db.rb | PostgreSQL persistence (see RABBOT_DB.md) |
| lib/game.rb | Game plugins: credits, cards, leaderboard, IRC game styles |
| lib/ai_summary.rb | AI reply footers and build-success formatting |
| lib/ai_guard.rb | Prompt-injection / untrusted-input checks for AI plugins |
| lib/text_util.rb | squish, truncate, title_words, format_count |
| lib/search_query.rb | SearchQuery.parse โ normalize catalogue search queries |
| lib/search_report.rb | SearchReport card builders + SearchLookup.format dispatch |
| lib/cursor_agent_questions.rb | Parse askQuestion events from cursor-agent stream-json |
| lib/directchat_session_codec.rb | stringify/symbolize session payloads, sanitize_line |
| lib/directchat_mirc_parse.rb | popup_item_line?, safe_read for mIRC .ibis scripts |
Do not duplicate
-
irc_color/IRC_CTRLโ useIrcFormat(already existed in many plugins; consolidate new code there) -
URI.parse(...).open("User-Agent" => ...)โ useRabbotHttp.fetch -
Nick/channel normalization โ use
NormalizeorRabbotDb.normalize_*for DB calls -
Add/rem/list parsing โ use
ListCommand.parse_add_remwhen the grammar matches -
Whitespace collapse / truncate / title-case โ use
TextUtil -
Catalogue search query parsing โ use
SearchQuery.parse -
Catalogue IRC report cards โ use
SearchReport+SearchLookup.format -
Cursor-agent plan questions โ use
CursorAgentQuestions.extract_from_event -
Directchatsession wire encoding โ useDirectchatSessionCodec -
mIRC
.ibisfile reads / popup line detection โ useDirectchatMircParse -
Database access โ use
RabbotDb/ plugin_data, not new JSON files indata/
IRC presentation
Rabbot targets Undernet with mIRC-class clients. Aim for award-winning ASCII art on the frame around a reply โ not on the content itself.
Clean body text (important)
Keep main text plain. Colour and ASCII art belong on:
-
Headings โ
ยซTAGยปbracket label,-=// title \=-menu heading -
Separators โ
===,ยท,โbetween header fields -
Footers โ muted status strip, model/timing line
-
Doodles โ opening/closing box art, ASCII bars beside stats (not inside sentences)
The readable payload (forecast text, joke punchline, wiki summary, list items) should be uncoloured plain text unless one word truly needs emphasis (e.g. a nick). Think fserve layout: flashy header, plain file list, small grey footer.
ยซBOMยป โ bracket label (IrcFormat.bracket_label / tag) -=// Brisbane ยท issued 14:30 \\=- โ menu heading (IrcFormat.menu_heading) Fine. Partly cloudy. Max 28. โ plain body (forecast ยท Bureau of Meteorology) โ accent_paren footer
Reference implementations: plugins/bom.rb, plugins/news.rb, lib/car_report.rb, lib/irc_reply.rb, plugins/silence.rb, plugins/cocktail.rb, plugins/fart.rb, plugins/cursorusage.rb.
Not: every line wrapped in \003 codes.
Prefer IrcReply.card or IrcFormat.report_header / report_footer for reply frames; use IrcFormat.divider for section breaks. Leave body lines unstyled.
Character set (ASCII-first)
Many IRC clients mishandle UTF-8 in channel text. Prefer widely compatible characters for art and borders:
| Safe (use freely) | Use with care |
|---|---|
AโZ aโz 0โ9 |
Box drawing โญโโฎโโ (pretty in mIRC/HexChat; may break elsewhere) |
= - _ | / \ [ ] ( ) < > |
Emoji and symbols outside Latin-1 |
# * @ . , : ; ' " ! ? + & % ^ ~ |
Fancy Unicode bullets, arrows, block elements |
When in doubt, build banners from +, -, and | (fserve-style) rather than Unicode box glyphs. A +---+ frame beats mojibake on a phone client.
Colour contrast
Always pair foreground and background so text stays readable:
-
Good:
(1, 8)black on yellow,(0, 4)white on red,(0, 3)white on green,(0, 10)black on aqua,(15, 1)white on black -
Weak: similar fg/bg (e.g.
(14, 15),(8, 14)), or light-on-light / dark-on-dark -
Extended colours (16โ98): use bright fg (52โ63) on dark bg (88โ92), or light bg (96โ98) with dark fg (88โ89)
Reset with IRC_RESET after every coloured segment.
Extended mIRC colours (16โ98)
Beyond the 16 theme colours (0โ15), mIRC defines 83 fixed RGB colours (16โ98) that look the same for all users. IrcFormat.color accepts any index 0โ98:
STYLE_ACCENT = IrcFormat.color(52) # bright red foreground STYLE_PANEL = IrcFormat.color(15, 88) # light grey on near-black STYLE_LINK = IrcFormat.color(59, 98) # blue on white
Highlights: 52โ63 vivid primaries, 64โ75 pastels, 76โ87 light tints, 88โ98 greyscale. See IRC formatting docs for the full table. Prefer extended colours for accents and gradients; keep body text on 0โ15 for broad client support.
Use IrcFormat (require_relative "../lib/irc_format"):
IRC_CTRL = IrcFormat::CTRL IRC_RESET = IrcFormat::RESET STYLE_TITLE = IrcFormat.named_style(:title) # extended 52,88 STYLE_CLASSIC = IrcFormat.named_style(:classic_title) # theme-dependent 0,4 def self.decorate(text, style) IrcFormat.decorate(text, style) end
Channel opt-in: !lolcat on pipes themed replies through installed lolcat (ANSIโIRC via AnsiToIrc). Body rainbow is intentional when enabled.
mIRC colour palette (0โ15)
| Code | Name | Typical use in Rabbot |
|---|---|---|
| 0 | white | foreground on coloured backgrounds |
| 1 | black | dark text on yellow highlights |
| 3 | green | plain success text |
| 4 | red | titles, errors, card suits |
| 5 | maroon | loss / negative |
| 7 | olive | push / neutral game result |
| 8 | yellow | hints |
| 10 | aqua (teal) | nicks, regions, highlighted values |
| 12 | blue | links, conditions, titles |
| 13 | fuchsia | nicks (games), duration accents |
| 14 | grey | separators (โ), empty bar slots |
| 15 | light grey | body text, labels |
Background colours use the same index range (0โ15 theme, 16โ98 fixed). Format: \003fg,bg via IrcFormat.color(fg, bg) โ codes are zero-padded to two digits.
Undernet style sheet
| Element | Convention |
|---|---|
| Tag | IrcFormat.bracket_label("BOM") or IrcFormat.tag("BOM") โ ยซBOMยป on line 1 |
| Banner | IrcFormat.menu_heading("Brisbane") โ -=// Brisbane \=- on line 2 |
| Card | IrcReply.card(tag:, title:, body_lines:, footer_parts:) โ preferred composer |
| Divider | IrcFormat.divider("Weather") โ ======== Weather ======== |
| Inline meta | ยท between fields: Queensland ยท 2 headlines ยท 14:05 (header/footer only) |
| Label โ value | In headers only: Duration โ 5 minutes with muted label |
| Nick | IrcFormat.nick("MeatAnt", op: true) โ @MeatAnt in aqua |
| Footer | IrcFormat.report_footer(...) โ accent_paren strip, last line |
| Reply size | 3โ6 lines: tag + heading + plain body + optional footer |
| Extended accents | 52โ63 brights on 88โ92 dark for headings; body stays plain |
Avoid on Undernet: emoji-heavy output, rainbow per-word colouring, Markdown, colouring entire paragraphs, Unicode art in body lines. Some channels use +c (no colours) โ plain body still works.
Classic IRC aesthetics (what to emulate)
| Source | Look and feel |
|---|---|
| Channel topics | Coloured title band only; topic text often plain after the header |
| Fserve announcers | Styled welcome box + trigger; plain file/command list; grey credit line |
| XDCC bots | Coloured #12 and headers; plain filenames; status footer |
Stats / sysinfo |
ASCII bar in margin; numbers plain (btop.rb) |
Patterns used across plugins
| Pattern | Example plugins | Notes |
|---|---|---|
| Tag + banner | bom.rb (reference) | IrcReply.card or report_header |
| Box banner | silence.rb, animal_news.rb | menu_heading with title colour on heading line |
| Label โ value | bom.rb, silence.rb, rabbot_user.rb | Label (muted) โ (grey sep) value (highlight) |
| Bracketed label | fserve/XDCC style | [Section] with bg colour; body text reset to plain white |
| Inline nick highlight | autoop.rb, rabbot_user.rb, game.rb | nick colour + IRC_RESET + body colour |
| Footer / meta line | ai_summary.rb, ai.rb | inline coloured tokens separated by ยท |
| ASCII bars / charts | btop.rb, wheel.rb | [###.....] blocks; keep width modest for IRC |
| List lines | autoop.rb, news.rb | comma-separated coloured items, reset between entries |
Recommended colour pairs (fg, bg)
| Role | Pair | Used in |
|---|---|---|
| Title / banner | (0, 4) red |
bom, silence, cursorusage |
| Win / success | (0, 3) green |
game, animal_news banner |
| Loss | (0, 5) maroon |
game |
| Nick / place | (0, 10) aqua |
autoop, rabbot_user, bom |
| Count / number | (1, 8) black on yellow |
autoop |
| Body | (15) or (12) |
descriptions, links |
| Muted meta | (15, 14) white on grey |
timestamps, issued dates |
| Separator char | (14) grey |
em dash between fields |
Always IRC_RESET after each coloured segment so later text is not tinted. Name styles STYLE_TITLE, STYLE_NICK, STYLE_SEP, etc. at class level โ see plugins/bom.rb and plugins/autoop.rb.
Plugin checklist
-
# Help: descriptionat top for!help(see plugins/help.rb). If the plugin is loaded on multiple bots, addโ responders: <scope>(see Responder scope below). -
require_relative "../lib/flood_safe"+include FloodSafefor long output; add flood_safe_theme +FloodSafeThemeandthemed_flood_safe_replyfor theme-ready plugins -
Extract testable logic into
self.methods; keepexecutethin -
Prefer
RabbotDbover in-memory hashes for data that should survive restarts -
Check
lib/before adding a helper โ extend an existing module if it fits -
Style headings, footers, separators, and doodles only โ use
IrcReply.cardorIrcFormat.report_header; keep body text plain; see IRC presentation -
Theme-ready plugins โ register
PaintSamples, wireFloodSafeTheme+themed_flood_safe_reply; see docs/THEME_MIGRATION.md
Theme migration (IRC frames)
Channel admins use !theme use <name> to recolour plugin output via PaintStore overrides. Migrated plugins call ThemeOutput.apply before sending (via FloodSafeTheme).
require_relative "../lib/flood_safe_theme" include FloodSafeTheme def execute(m, ...) themed_flood_safe_reply(m, format_reply(...)) end
Register sample output in lib/paint_samples/register_*.rb. Sprint backlog: docs/THEME_MIGRATION.md.
Progress gutter (multi-line replies)
FloodSafe defaults to with_progress: :auto โ prefixes each queued line/chunk with a 4-character gutter (01% โฆ 100 ) when there are 2+ logical lines or 2+ flood chunks. Gutter text uses uniform aqua on dark blue (IrcFormat.color(11, 2)); channel themes may override via progress_percent paint rules.
themed_flood_safe_reply(m, format_reply(...)) # auto progress flood_safe_reply(m, "error", with_progress: false) # one-liner / errors flood_safe_reply(m, header_and_body, skip_prefix_lines: 1) # e.g. !sing header
Body text after the gutter aligns at column 5 โ see ReplyLayout::PROGRESS_GUTTER_WIDTH. Use TextAlign.format_colon_rows / ReplyLayout.align_colon_body for label:value blocks.
Responder scope
Several bots share the same channels. If a plugin is included on more than one bot, declare who should reply and gate in execute with BotIdentity (silent early return โ no channel spam).
| Scope | Meaning | Example |
|---|---|---|
all_loaded |
Every bot that loads the plugin | !ai <question> |
owner |
Primary Rabbot bot only (bot_level: owner) |
!ai models |
| room_reader | Steve (bot_role: room_reader) |
!chat control |
nick_targeted |
Bot whose nick matches the command prefix |
!Dr_Weird get ai_model |
character |
Profile-driven mention replies | CharacterResponder |
require_relative "../lib/bot_identity" def execute(m, prompt) return if admin_command?(prompt) && !BotIdentity.owner_bot?(bot) # ... end
Help line examples:
-
# Help: chat with GPT โ voice (+v) or op required โ responders: all_loaded (models: owner) -
# Help: controlled autonomous chat โ responders: room_reader
AI access
User-initiated AI commands (!ai, etc.) must check AiAccess.user_allowed? before calling agents:
-
IRC channel op or voice (
+v), or -
Rabbot-registered user at
voicelevel or higher (for non-moderated channels).
Use lib/ai_access.rb. Internal system paths (room_reader refine, !chat autonomous mode, !rabbot plugin agent) are exempt but must not auto-execute builds from bots โ see ChannelTrust.
require_relative "../lib/ai_access" return flood_safe_reply(m, AiAccess.denial_message) unless AiAccess.user_allowed?( channel: m.channel, user: m.user, network: bot.config.server, nick: m.user.nick )
Tests
-
test/mirrorsplugins/(test/plugins/foo_test.rbforplugins/foo.rb) -
Pass
fetch:lambdas into search/lookup methods โ no live HTTP in tests -
Use temp DB:
RabbotDb.db_path = ...; RabbotDb.reset!(path: ...)
New plugin generation
rabbot_meta build/enhance prompts include IRC presentation, theme migration (FloodSafeTheme, PaintSamples, !theme), and column layout (ReplyLayout, progress gutter, colon alignment). Follow them when writing plugins by hand too โ see Theme migration and Progress gutter above.