Hackware

Switch to dark theme

Connecting to the IRC

How a bot or mod authenticates to irc.hackware.me, opens the event stream, and speaks presence onto the mesh.

The IRC mesh lives at https://irc.hackware.me/. It is one endpoint speaking two verbs: GET opens a Server-Sent Events stream that delivers everything said on your channel, and POST sends a payload onto it. The service is the mesh, so the bare root and /irc answer identically; a client needs no path to find it.

A channel is a multiplayer server: every client that names the same server hears each other, and nobody else.

This connection method is the same for players and bots alike: both authenticate with a Minecraft session and a scoped API key, exactly as described below. Bridge connections (services that connect without a session and act across identities) work differently and are documented separately.

Authentication

Required headers

Every request carries three credentials and two routing headers:

HeaderValue
AuthorizationBearer <bot session JWT>: the bot account's Minecraft access token, exactly the token the game itself holds after Microsoft/Mojang login. It is validated against the Mojang profile API, so it must belong to a real, current session.
Client-ID / Client-SecretYour issued client credentials. These identify the software (your bot or mod build), where the JWT identifies the account. Also accepted spelled X-Client-ID, Hackware-Client-ID, or X-Hackware-Client-ID (and matching -Secret forms).
User-Agent<mod id>/<mod version>+<git commit full hash>, e.g. hackware/1.3.0+113c3f7065abe1eb0326b5677ff3a2494a7d1541.
X-IRC-Multiplayer-ServerThe multiplayer server the client is on: the SRV-resolved host, so every client on that server names it identically. This is what selects the channel.
Content-Type / Acceptapplication/json on POST; text/event-stream on GET.

Client credentials

Client credentials are an API key, issued per project. Bake them into the build, in gradle.properties or equivalent, rather than fetching them at runtime:

CLIENT_ID=your-client-id
CLIENT_SECRET=your-client-secret
Get the client credentials right

A request presenting a valid Mojang session with missing or wrong client credentials is treated as a cracked client, and the account is banned from the mesh. Wire the Client-ID/Client-Secret headers before pointing a real bot account at the endpoint. A key that has merely expired or been revoked is refused without a ban, but the safe order is still: credentials first, session second.

Scopes

Access to the mesh is scoped: every API key carries a set of scopes, and they decide everything the connection may do. Your key must hold the scopes your integration needs:

ScopeGrants
irc.connection.establishOpen the stream, and receive your own frames on it
irc.messages.sendSend payloads as the account behind the session. Narrowable to specific types as irc.messages.send.<type>, e.g. irc.messages.send.presence
irc.messages.receiveBe delivered other players' payloads. Narrowable the same way as irc.messages.receive.<type>, and fixed at connect for the life of the stream

A key that lacks a scope is refused plainly, never banned: without the connection scope the stream will not open, a payload type outside the key's send scopes is rejected, and types outside its receive scopes are simply never delivered. If the mesh refuses your connection or messages are not arriving, check the key's scopes before anything else.

Receiving

Opening the stream

Open the stream with a GET and hold it for the life of the session:

curl --request GET \
  --url https://irc.hackware.me/ \
  --header 'Accept: text/event-stream' \
  --header 'Authorization: Bearer <BOT SESSION JWT>' \
  --header 'Cache-Control: no-cache' \
  --header 'Client-ID: <CLIENT ID>' \
  --header 'Client-Secret: <CLIENT SECRET>' \
  --header 'User-Agent: <MOD ID>/<MOD VERSION>+<GIT COMMIT FULL HASH>' \
  --header 'X-IRC-Multiplayer-Server: <SERVER IP SRV HOST>'

The channel may also be named in the URL as GET /?host=<SERVER IP SRV HOST>, in which case the query wins over the header.

One stream per (account, channel) holds across the whole mesh: opening a second stream as the same bot on the same server evicts the first, wherever it is connected.

Wire format

Everything arrives as SSE. Each delivered payload is a pair of frames: a meta event naming who sent it and on which channel, immediately followed by an unnamed data event carrying the payload itself:

event: meta
data: {"uuid":"299617aa-f176-482c-b9ea-44e305321fd0","agent":"hackware/1.3.0+113c3f7065abe1eb0326b5677ff3a2494a7d1541","channel":"ingress.2b2t.org"}

data: {"type":"presence","inventory":[],"attributes":{"absorption":0,"health":20,"hunger":15,"oxygen":300,"saturation":8.6}}

Read the sender's identity from the meta frame's uuid, never from the payload. Payload types about the sender (presence, chat) deliberately carry no player field of their own. The payload is always one of the types in the schema reference, discriminated on type.

Sending

POST a payload to the same endpoint, with the same headers plus Content-Type: application/json. The body is any schema payload; here, a presence snapshot:

curl --request POST \
  --url https://irc.hackware.me/ \
  --header 'Authorization: Bearer <BOT SESSION JWT>' \
  --header 'Client-ID: <CLIENT ID>' \
  --header 'Client-Secret: <CLIENT SECRET>' \
  --header 'Content-Type: application/json' \
  --header 'User-Agent: <MOD ID>/<MOD VERSION>+<GIT COMMIT FULL HASH>' \
  --header 'X-IRC-Multiplayer-Server: <SERVER IP SRV HOST>' \
  --data '{"type":"presence","inventory":[],"attributes":{"absorption":0,"health":20,"hunger":15,"oxygen":300,"saturation":8.6}}'

The body may also be a JSON array of payloads: a batch is delivered in order, one meta + data pair per entry. Unknown type values are rejected at ingress with a 400.

Schema reference

Every payload is one of eleven types, discriminated on its type field. The schema reference lists them all, and documents the meta frame and the common shapes the payloads share.