arizuko › reference › JID format
jid
Every chat and every sender is a JID of the form <platform>:<rest>. Each adapter defines the path segments in <rest>. JIDs are plain strings; routing matches them with path.Match glob semantics across all routing keys.
A JID is a platform and a rest
A JID is arizuko’s universal address for who and where — one string that works across Telegram, Discord, or the web, so routing rules never need per-platform logic.
jid := platform ":" rest
platform := lowercase adapter name (everything before the first ":")
rest := opaque platform-owned path (everything after the first ":")
The platform/rest split is the only structure core enforces. Two helpers in core/types.go perform it:
core.JidPlatform(jid)— everything before the first:;""when there is no colon or the colon is at position 0.core.JidRoom(jid)— everything after the first:; the whole string when there is no colon.
A JID is a plain string in the code
A JID is a string. Message.ChatJID and Message.Sender are both string fields on core.Message. There is no parse step, no net/url wrapper, and no separate chat-vs-sender Go type — routd never interprets <rest>, it only glob-matches the full string. Adapters own the per-platform shape of <rest> (section 3).
Each adapter owns the shape of its JIDs
Each adapter owns its <rest> shape:
| platform | kind | shape | example |
|---|---|---|---|
telegram | DM | telegram:<bot_id>/user/<user_id> | telegram:111/user/12345 |
| sender | telegram:user/<user_id> | telegram:user/12345 | |
group | telegram:group/<chat_id> | telegram:group/67890 | |
discord | <guild_id> | discord:<guild_id>/<channel_id> | discord:5678/9012 |
dm | discord:dm/<channel_id> | discord:dm/54321 | |
user | discord:user/<user_id> | discord:user/u123 | |
whatsapp | — (server suffix) | whatsapp:<id>@<server> | whatsapp:1234@g.us, whatsapp:1234@s.whatsapp.net, whatsapp:1234@lid |
email | address | email:address/<addr> | email:address/foo@bar.com |
thread | email:thread/<msgid> | email:thread/<Message-ID> | |
web | folder | web:<folder>[/<suffix>] | web:acme/support |
hook | folder | hook:<folder>/<source>[/<suffix>] | hook:acme/eng/github |
slack | channel, dm, group | slack:<workspace>/<kind>/<id> | slack:T012ABCD/channel/C0HJKL456 |
github | account | github:<owner> | github:kronael |
repo | github:<owner>/<repo> | github:kronael/arizuko | |
pr | github:<owner>/<repo>/pr/<n> | github:kronael/arizuko/pr/12 | |
issue | github:<owner>/<repo>/issue/<n> | github:kronael/arizuko/issue/34 | |
gdrive | file | gdrive:<file_id> | gdrive:1AbC_dEf |
comment | gdrive:<file_id>/c/<comment_id> | gdrive:1AbC_dEf/c/Xy-9 |
Routes match JIDs with path.Match globs
The route table's match column is a space-separated list of key=glob predicates. Evaluation lives in router.RouteMatches; keys are extracted from the message via msgField:
| key | extracts | source |
|---|---|---|
platform | JidPlatform(msg.ChatJID) — everything before the first : | core/types.go |
room | JidRoom(msg.ChatJID) — everything after the first : | core/types.go |
chat_jid | msg.ChatJID — full canonical string | |
sender | msg.Sender — full sender JID | |
verb | msg.Verb — "message" default; "join", "edit", … |
Glob match uses Go's path.Match semantics:
*matches any non-/sequence (segments are first-class).?matches exactly one non-/char.[abc]/[a-z]matches a character class.\escapes the next character.
From the RouteMatches contract (router.go:322):
| predicate | matches when |
|---|---|
key=<exact> | value equals <exact> |
key=<glob> | value matches glob (*, ?, […]; * does not cross /) |
key=* | value is present (non-empty) — bare * silently rejects empty |
key= | value is absent (empty) |
| omit key | unconstrained |
The matcher is Go's path.Match applied directly to the JID string; router.RouteMatches is the only matcher. There is no separate core.MatchJID helper — a JID is a string, so path.Match(pattern, jid) is all a pattern check needs.
Eight example matches
# all telegram groups
match='platform=telegram chat_jid=telegram:group/*'
# discord guild 67890, any channel/thread
match='chat_jid=discord:67890/*'
# all Discord DMs
match='chat_jid=discord:dm/*'
# all WhatsApp groups (server suffix discriminates)
match='chat_jid=whatsapp:*@g.us'
# one Telegram group; teled drops the minus sign of the group ID (room=<rest after colon>)
match='platform=telegram room=group/100123456'
# any non-message verb on any platform (join, edit, delete, …)
match='verb=*'
match='verb=' is the negation: only verb-less (default 'message') rows
Grant globs cross slashes
The same jid=<glob> syntax appears in an acl row's params column (grants reference). The matcher there is segGlob in auth/authorize.go, where * runs to the end of the value — it crosses both : and /. That is a deliberate difference: param globs span the whole JID, route globs are segment-aware.
send(jid=telegram:group/*) # rule: only telegram groups (grants matcher: * spans /)
match='chat_jid=telegram:group/*' # route: only telegram groups (path.Match: * doesn't cross /)
Use platform=telegram to route all Telegram chats. The route glob telegram:*/* matches groups but cannot match DMs with three path segments. Use chat_jid=telegram:*/user/* for all Telegram DMs. A grant parameter jid=telegram:* matches both shapes.
Six rules keep JIDs simple
- Adapter-defined path. Each adapter defines the path segments. A Telegram DM starts with the bot id, then
userand the user id. The message field identifies the role:ChatJIDfor a chat,Senderfor a person. - Adapter owns its schema. Core only splits
<platform>:<rest>viaJidPlatform/JidRoom. Each adapter constructs and parses<rest>per its own declared shape. - URI-clean. Adapters percent-encode reserved chars in
<rest>so the platform/rest split holds. - One URL = one resource. No sign-bit hacks, no in-band server suffixes for code (WhatsApp keeps
@serverbecause its existing shape already encodes kind). - A run is not addressable. A JID has no session or run segment; the routable unit below a chat is the topic. A run segment would need every web JID rewritten, because web JIDs are keyed by folder.
- Adding a kind later is one string. A new platform kind earns its place when an adapter,
routdor the agent treats it differently from siblings. No system-wide format change and no rewrite of existing rows.
Four sources give the details
core/types.go—JidPlatform,JidRoom, theMessagestruct (stringChatJID/Senderfields)router/router.go—RouteMatches,msgField,ResolveRoute- grants reference — how
jid=…appears in grant rule params - schema reference — every column that stores a JID