dIRC Services β Services Suite
The 14 pseudoclients implemented by the dircservices binary, grouped by milestone. Each service is a single services/<svc>/<svc>.go package registered with services.Registry and started in cmd/dircservices/main.go. Per-service command docs live in dircservices/docs/EN.<Svc>.md (and per-language translations), surfaced end-user via the HelpServ service.
See the canonical overview for architecture, build, runtime, and the S2S wire protocol. This page is the per-service command/feature reference.
M2 services (foundational)
Global β network notices
Real name: dLAN Global Noticer Β· Source: services/global/global.go
GLOBAL <text>β broadcast to every online user (oper-only)LOGONNEWS ADD/DEL/LISTβ in-memory v1 welcome noticesOPERNEWS ADD/DEL/LISTβ oper-class network news (added 07-24)HELP
NickServ β nickname registration & profiles
Real name: dLAN Nickname Service Β· Source: services/nickserv/nickserv.go Β· Store: store/store.go (NickRow, NickGet/NickCreate/NickUpdateField/NickDelete/NickListByPattern/NickAccess*/NickAjoin*/NickListChannels), store/schema.go (nicks, nick_aliases)
Full 17-command implementation (rewrite 2026-07-24 from 47-line M2 skeleton):
- Registration lifecycle:
REGISTER <pw> [email],IDENTIFY <pw>(one-arg self) orIDENTIFY <nick> <pw>(two-arg),LOGOUT,GHOST,RECOVER,RELEASE,REGAIN,DROP,FDROP(forced, oper-only),INFO [nick],STATUS,ACC SETsub-commands (oper/admin class + profiles):DISPLAYNAME,DOB(ISO YYYY-MM-DD),FAVCHANNEL,TWITTER,FACEBOOK,LINKEDIN,WEBSITE,TIMEZONE,LANGUAGE(aliasLANG),KILL QUICKmode,AUTOOP ON|OFF,VHOST ON|OFFACCESS(ADD/DEL/LIST/CLEAR) β alternate auth masksAJOIN(ADD/DEL/LIST) β auto-join listLISTCHANSβ channels you own via ChanServLIST [pattern](oper-only) β registered nick searchHELP [cmd]
Identified state lives in a shared pseudoclient.IdentifiedMap (sync.Map) keyed by protocol.UID, set on REGISTER/IDENTIFY, read by ChanServ + GroupServ + HostServ.
Profile schema (8 columns added 2026-07-24 session 2): display_name, dob, fav_channel, twitter, facebook, linkedin, website, timezone. Consumed by HelpServ's NickGetLanguage for per-user language resolution.
OperServ β operator toolkit
Real name: dLAN Operator Service Β· Source: services/operserv/operserv.go Β· Store: store/store.go (AkillRow/ForbidRow/OperRow)
Essentials (M2) + AKILL/SNLINE/SQLINE/FORBID/OPER expansion (2026-07-24):
INFO,STATS [AKILL|UPLINK|UPTIME|ALL|RESET]SHUTDOWN/RESTART/QUITβ signalsstopCh(main.go reads SIGINT/SIGTERM/stopCh)UPDATEβstore.ForceBackup()(snapshot + prune)SET <key> <on|off>β in-memory toggles:READONLY,DEBUG,NOEXPIRE,SUPERADMINAKILL ADD/DEL/LIST/VIEW/CLEAR,CHANKILL(store+ member iteration via ChannelRegistry)SNLINE ADD/DEL/LIST,SQLINE ADD/DEL/LIST,FORBID ADD/DEL/LISTMODE,KICK(uses Protocol interfaceKickadded commite210c2eb)OPER ADD/DEL/LIST+OPER INFO <nick>LOGSEARCHβ grep of services log fileSVSNICK/SVSJOIN/SVSPART/SVSMODE(resolve UID via UserRegistry)HELP
Permissions via access.OperType glob (path.Match) + pseudoclient.IsOper(cfg, nick).
ChanServ β channel registration & ACL
Real name: dLAN Channel Service Β· Source: services/chanserv/chanserv.go Β· Store: store/chanserv.go
Roughly 95% coverage after the 2026-07-24 SET-system expansion. Access levels: 0=voice, 100=halfop, 200=op, 300=protect, 400=founder/owner.
- Mgmt:
REGISTER,DROP,INFO,FDROP(oper),LIST(oper),HELP - ACCESS:
ADD/DEL/LIST/SET/CLEAR, withAOP/HOP/VOP/SOP/FSOPshortcuts - AKICK:
ADD/DEL/LIST/VIEW/ENFORCE/CLEAR - User modes:
OP/DEOP/HALFOP/DEHALFOP/VOICE/DEVOICE/OWNER/DEOWNER/PROTECT/DEPROTECT - Channel ops:
MODE,KICK,KICKBAN,BAN,UNBAN,TOPIC,TOPICAPPEND,TOPICPREPEND,INVITE,GETKEY SET(19 sub-commands):FOUNDER,SUCCESSOR,PASSWORD(bcrypt),DESC,URL,EMAIL,ENTRYMSG,MLOCK,TOPICLOCK,KEEPTOPIC,SECUREOPS,RESTRICTED,VERBOSE,PRIVATE,FANTASY,GAMESERV,PREFIX,GUARD,PROPERTY.MLOCK/TOPICLOCK/KEEPTOPICare now actually enforced (2026-07-25 fix β all three were previously stored and silently ignored). Also fixed:DESCandTOPICAPPEND/TOPICPREPENDused to share one DB column (description), so setting one clobbered the other βtopicnow has its own column.- Utility:
ENFORCE,SYNC,STATUS,WHY,UP,DOWN,RECOVER
NickServ integration via shared IdentifiedMap; REGISTER/DROP require identified caller. REGISTER also requires the caller currently be a member and opped in the channel (ChannelRegistry.IsMember/IsOp, 2026-07-25) β previously missing entirely, and the old syntax additionally (confusingly) re-verified the caller's NickServ password as a second REGISTER argument, which is what "password incorrect" on REGISTER actually meant. Channel modes/raw ops use direct SendMessage protocol.Message.
HostServ β virtual hosts
Real name: dLAN VHost Service Β· Source: services/hostserv/hostserv.go Β· Store: store/vhost.go (VhostRow, VhostRequest, VhostOffer)
Expanded to ~100% coverage in 2026-07-24 session 3:
- Self-service:
SET/GET/DEL/LIST/ON/OFF/ASSIGN(SET/ASSIGN are aliases) - Approval queue:
REQUEST,WAITING,ACTIVATE,REJECT - Oper bulk:
SETALL,DELALL,GROUP,VHOSTNICK - Shared vhost pool:
OFFER,OFFERLIST,TAKE,UNOFFER HELP
Schema additions: vhost_requests + vhost_offers tables. v1 stores vhosts only β actual SVSMODE-based Unreal vhost application is not wired yet (per design Β§6.6).
M3 services
BotServ β channel bot assignment & kick enforcement
Real name: dLAN Bot Service Β· Source: services/botserv/botserv.go Β· Store: store/botserv.go (BotRow, BotAssign, BadwordRow, KickRow, channel setting TEXT in channel_property)
- Bot mgmt (oper):
BOT ADD,BOT CHANGE,BOT DEL,BOTLIST - Assignment:
ASSIGN,UNASSIGN,ACT(ACTION via assigned bot),SAY(PRIVMSG via assigned bot) - Badwords:
BADWORDS ADD/DEL/LIST/CLEAR(match types SINGLE/START/END) - KICK settings: 9 types β
CAPS,FLOOD,REPEAT,BOLDS,COLORS,ITALICS,REVERSES,UNDERLINES,AMSG SET:FANTASY,NOBOT,PRIVATEINFO,HELP
Kick Enforcement Engine
Wired via proto.OnPrivmsg hook β fires on every channel PRIVMSG:
| Detection | Mechanism |
|---|---|
| CAPS | Uppercase-char ratio vs percent threshold + min-chars floor |
| FLOOD | Per-user-per-channel sliding timestamp window vs lines threshold |
| REPEAT | Last-N messages ring buffer per user, identical-message count |
| BOLDS/COLORS/ITALICS/REVERSES/UNDERLINES | IRC formatting code count per message |
| AMSG | Cross-channel same-message detection within time window |
In-memory trackers (no DB rows for live state): floodTracker (timestamp slices), repeatTracker (string slices), amsgTracker (nickβchanβtime).
Bot-join tracking via SVSJOIN on ASSIGN, SVSPART on UNASSIGN/DEL β joinedBots map. SVSJOIN alone grants no channel status, so ASSIGN follows it with a MODE +o on the bot (2026-07-25) β otherwise it can't enforce KICK settings. SAY/ACT send via PrivmsgAs(botUID, channel, text) so the message visibly comes from the assigned bot, not the bare server SID (was previously misdirected at the bot's own UID instead of the channel β never actually worked).
Fantasy routing (wired 2026-07-24): SET FANTASY ON + channel message starting !cmd β dispatches to GameServ.HandleFantasyCommand (set via bsSvc.SetFantasyHandler in main.go).
ALIS β channel list service
Real name: dLAN Advanced List Service Β· Source: services/alis/alis.go Β· State: state/channels.go ChannelRegistry
Single LIST command with filters β pattern glob, -min/-max member count, -topic substring, -mode all-must-match glob, -noinvite/-nosecret exclusions. 10s rate limit per UID; HELP.
ALIS reads live channel state from the shared ChannelRegistry (populated from the protocol's OnChannelJoin/Part/Kick/Mode/Topic hooks β not from ChanServ DB).
GroupServ β account groups
Real name: dLAN Group Service Β· Source: services/groupserv/groupserv.go Β· Store: store/groupserv.go (GroupRow, GroupMember, GroupInvite)
Case-insensitive (lower()-based) lookups; permission model uses string flags column (bare runes "rFc"):
REGISTER <group>β identified-only; inserts groups row + GroupMember(founder, "F")DROP <group>β founder OR oper; cascades members + invites in a txJOIN <group>β open group auto-add, closed group consumes aGroupInviterowPART <group>β founder cannot PART own groupINVITE <group> <nick>β closed only; founder OR+rFLAGS <group> <nick> <r|F|c>β founder OR+F;+Fonly by founderSET:OPEN|PUBLIC|DESCRIPTION|PASSWORD|JOINFLAGS|GROUPFLAGSINFO,LIST [pattern],HELP
Flags: r=invite/register, F=founder (single, founder-gated), c=chanserv-acl-binding (stored only, not enforced v1). 2026-07-24 added group_members.groupβgroup_name rename to dodge the SQLite reserved word.
M4 services
StatServ β network telemetry
Real name: dLAN Statistics Service Β· Source: services/statserv/statserv.go Β· Store: store/statserv.go (StatServSnapshot, ChannelSnapshot, UserSnapshot)
Snapshot loop: every N minutes (default 5m) β count online users/channels/ops β INSERT into statserv_snapshots + per-channel + per-user rows; resets per-user lines_spoken counters. Wires OnPrivmsg to count lines.
Commands: NETWORKINFO, CHANNELINFO <#chan>, TOP [channels|users] [by=messages|time|users] [limit], REPORT <#chan> [limit], HELP.
SecureServ β anti-abuse defense
Real name: dLAN Security Service Β· Source: services/secureserv/secureserv.go (+ clone.go, dnsbl.go, flood.go, proxyscan.go) Β· Store: store/secureserv.go
Four subsystems wired at startup via services' OnUIDIntroduce/OnPrivmsg hooks:
| Subsystem | Hook | Behavior |
|---|---|---|
| DNSBL | OnUIDIntroduce |
Reverse-DNS lookup of user IP against configured providers (3 defaults). Hit β AKILL via TKLAdd. Respects exempt CIDRs. |
| Clone detection | OnUIDIntroduce + OnQuit |
Per-host connection counter (sync.Mutex map). Thresholds: warn (oper notify), kill (SVSKILL), akill (TKLAdd). |
| Flood defense | OnPrivmsg |
Sliding-window per-user + per-channel line counters. Escalation: warn β mute (SVSMODE +m, auto-lift) β kill β akill. |
| Proxyscan | OnUIDIntroduce |
(off by default) Dial user IP on common proxy ports; AKILL on accept. |
Commands: STATS, EXEMPT ADD/DEL/LIST, PROVIDER ADD/DEL/LIST, CLONE LIST/SET, FLOOD STATS/SET, HELP.
Notification by WALLOPS oper broadcast (replaces direct NOTICE β see session 3 devlog).
Default thresholds (from config/defaults.go): clone warn=3/kill=5/akill=8; flood user=5 lines/3s, channel=20/5s.
M5 services
GameServ β channel games
Real name: dLAN Game Service Β· Source: services/gameserv/ (8 files: gameserv.go, calc.go, eightball.go, roll.go, rps.go, namegen.go, lottery.go, trivia.go) Β· Store: store/gameserv.go (Lottery/Ticket/TriviaScore) Β· Data: dircservices/data/trivia/{general,movies,science}.txt
Commands (8):
CALC <expr>β recursive-descent math parser (numbers, +/-/*//^, parens; noeval/reflect)EIGHTBALLβ 20 classic Magic 8-Ball answersROLL <spec>βXdY+Zdice +4dFfudge; shows individual rolls + totalRPSβ rock/paper/scissors w/ emojiNAMEGENβ Markov-chain fantasy names from embedded syllable bankLOTTERY BUY/STATUS/DRAWβ per-channel rolling jackpot; weighted random drawTRIVIA START/STOP/SCORE/CATEGORIESβ text-file quiz (Question|Answerper line)HELP
2026-07-24 fix: fantasy + trivia/lottery now keyed by actual IRC channel, not virtual nick-context β option for chanserv SET FANTASY ON + !cmd to fire GameServ.
IdleRPGServ β idle-to-level RPG
Real name: dLAN IdleRPG Β· Source: services/idlerpg/ (idlerpg.go, register.go, events.go, quest.go, top.go, story.go, admin.go) Β· Store: store/idlerpg.go (IdleRPGChar, Item, Quest, Story)
Characters idle in #IdleRPG to level up. Mechanics:
- 30s idle-tracking tick loop; event roll timer (base + jitter)
- Talking/parting/quitting channel = penalty
- Event rolls: battle (attack vs monster defense), treasure (find item), quest (start timed 30-90min quest)
- Wires
OnChannelJoin/Part/Kick/Privmsgfor#IdleRPG - IdleRPGServ itself joins (and self-ops in)
#IdleRPGat startup (2026-07-25);REGISTERnow SVSJoins the caller straight into the channel instead of just telling them to go there - Battle/treasure/quest outcomes are announced in
#IdleRPG(viaReplyChannel, so they visibly come from IdleRPGServ) in addition to the existing private NOTICE to the player - Auto-recognize on join (2026-07-25): anyone (re)joining
#IdleRPGwith an existing registered character gets a "Welcome back" NOTICE with their class/level/idle time β piggybacks on the existingOnChannelJoinidle-timer wiring, no new event hook needed
Commands: REGISTER, WHOAMI, STATUS, QUEST, PENALTY, TOP (leaderboard by level), STORY (recent event log), ADMIN FREEZE/UNFREEZE/RESET/SETLEVEL (oper-only), HELP.
Bcrypt for REGISTER. Standalone β no coupling to the dmud MUD package.
LoveServ β fun gestures
Real name: dLAN Love Service Β· Source: services/loveserv/ (loveserv.go, gestures.go, leaderboard.go) Β· Store: store/loveserv.go (LeaderboardRow)
16 gesture commands (3 random response variants each): ROSE, HUG, KISS, SLAP, POKE, BEER, COFFEE, COOKIE, PIZZA, HEART, FORTUNE, LOVE, HIGHFIVE, FACEPALM, SHRUG, DANCE.
Tracking: given/received counts per nick. Tracks muted channels (per-channel MUTE added 2026-07-24). LOVE STATS + LOVE HELP + most-slapped leaderboard category + Weekly reset loop.
HelpServ β centralized documentation (added 2026-07-24)
Real name: dLAN Help Service Β· Source: services/helpserv/helpserv.go
The 14th service, wired last in main.go. Centralizes all per-service documentation under dircservices/docs/:
LISTβ shows available docs, hides<LANG>.prefixed files (e.g. hidesEN.NickServ.md, only shows baseNickServ.md)HELP <service> [command]β loads doc file, parses## Commandheaders, returns the matching section (or whole doc)- Multi-language: resolves caller UID β nick β
NickGetLanguage(nick)β try{LANG}.{Service}.mdthen base{Service}.md(case-insensitive) thenEN.{Service}.mdfallback
13 EN.*.md canonical files (one per non-HelpServ service) + FR.NickServ.md, ES.NickServ.md skeletons shipped. All docs rewritten in the 2026-07-24 session 3 to cover every command + sub-command in compact 1-line-per-command format.
Build & Dev Workflow
# Build full services binary
go build -o dircservices ./dircservices/cmd/dircservices/
# Tests (4 unreal4 dispatch/parse round-trip tests)
go test ./dircservices/...
# Vet
go vet ./dircservices/...
# Smoke against live irc.dlan:6700 (verify link + 14 UID bursts)
timeout 90 ./dircservices
Configure via dircservices/config.example.json (LAN plaintext creds shipped β set::plaintext-policy::server allow on ircd). Operator types live in cfg.OperTypes and are installed via access.InstallOperTypes at startup.
Adding a New Service
- Implement
Serviceinterface inservices/<svc>/<svc>.go(use a recent one likeloveservas template β embed*pseudoclient.Pseudoclient) - Wire
SetUserRegistry/SetChannelRegistry/SetIdentifiedMapsetter pattern (consistent across all existing services) - Add
store/<svc>.gofor persistence; bumpschema.gomigrateSchema(schema_versionINSERT OR IGNORE) - Write
docs/EN.<Svc>.md(and translations as desired) - Import + register in
cmd/dircservices/main.go, bump the count in the start log - Document here (overview table + a section above)
The protocol's On* setters allow multiple subscribers safely β per-event RLock + snapshot-copy + dispatch loop means new services can append event handlers without race risk.
Cross-References
- Key-Packages/dircservices/overview.md β package architecture, wire protocol, store design
- Key-Packages/dirc/overview.md β IRC client (separate from services daemon)
dircservices/docs/EN.*.mdβ authoritative per-service command docs (13 files)docs/dLAN_Services_DevLog.mdβ chronological build logdocs/dLAN_Services_Plan.mdβ original spec- Recent-Changes-and-Evolution.md β July 2026 dircservices section