arizuko

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:

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:

platformkindshapeexample
telegramDMtelegram:<bot_id>/user/<user_id>telegram:111/user/12345
sendertelegram:user/<user_id>telegram:user/12345
grouptelegram:group/<chat_id>telegram:group/67890
discord<guild_id>discord:<guild_id>/<channel_id>discord:5678/9012
dmdiscord:dm/<channel_id>discord:dm/54321
userdiscord:user/<user_id>discord:user/u123
whatsapp— (server suffix)whatsapp:<id>@<server>whatsapp:1234@g.us, whatsapp:1234@s.whatsapp.net, whatsapp:1234@lid
emailaddressemail:address/<addr>email:address/foo@bar.com
threademail:thread/<msgid>email:thread/<Message-ID>
webfolderweb:<folder>[/<suffix>]web:acme/support
hookfolderhook:<folder>/<source>[/<suffix>]hook:acme/eng/github
slackchannel, dm, groupslack:<workspace>/<kind>/<id>slack:T012ABCD/channel/C0HJKL456
githubaccountgithub:<owner>github:kronael
repogithub:<owner>/<repo>github:kronael/arizuko
prgithub:<owner>/<repo>/pr/<n>github:kronael/arizuko/pr/12
issuegithub:<owner>/<repo>/issue/<n>github:kronael/arizuko/issue/34
gdrivefilegdrive:<file_id>gdrive:1AbC_dEf
commentgdrive:<file_id>/c/<comment_id>gdrive:1AbC_dEf/c/Xy-9

Discord placeholder. A guild-channel row with no known guild id reads discord:_/<channel>; discd accepts that shape and writes the real guild id on every new inbound (discd/bot.go chanID).

Web is folder-keyed. The chat JID is the folder (with optional /<suffix>); the URL token is a separate row in route_tokens that maps to this JID. See route tokens and reference/tokens.

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:

keyextractssource
platformJidPlatform(msg.ChatJID) — everything before the first :core/types.go
roomJidRoom(msg.ChatJID) — everything after the first :core/types.go
chat_jidmsg.ChatJID — full canonical string
sendermsg.Sender — full sender JID
verbmsg.Verb — "message" default; "join", "edit", …

Glob match uses Go's path.Match semantics:

From the RouteMatches contract (router.go:322):

predicatematches 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 keyunconstrained

Malformed tokens (no = or empty key) are skipped, not errored. The empty match expression matches everything.

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

Four sources give the details