PresenceJS is a Forge 1.20.1 client-side mod that adds Discord Rich Presence to Minecraft and exposes a broad scripting surface for KubeJS.
It uses a pure-Java Discord RPC backend designed for Minecraft mods rather than a native JNI bridge.
PresenceJS registers a client config file. The most important setting is the Discord application ID:
clientIdwebhookEnableddefaultLargeImageKeydefaultSmallImageKeymenuDetailssingleplayerDetailsmultiplayerDetailsYou can leave clientId empty and provide it entirely from KubeJS instead.
Webhook functionality is disabled by default and must be enabled explicitly through webhookEnabled before any webhook requests can be sent.
PresenceJS adds a client-side global binding named PresenceJS and an event group named PresenceJSEvents.
PresenceJS.activity() → create a new mutable activity objectPresenceJS.button(label, url) → create a button objectPresenceJS.image(key, text) → create an image objectPresenceJS.getContext() → inspect the latest client snapshotPresenceJS.getBaseActivity() / setBaseActivity(activity)PresenceJS.clearBaseActivity()PresenceJS.getLastSentActivity()PresenceJS.getCurrentDiscordUser()PresenceJS.getConnectionState() / getConnectionMessage()PresenceJS.isConnected()PresenceJS.isEnabled() / setEnabled(enabled)PresenceJS.refresh()PresenceJS.disconnect()PresenceJS.jsonObject() / jsonArray() / json(jsonString) → create or parse Gson JSON payloads for webhook requestsPresenceJS.webhookMessage() / webhookEmbed() / webhookField(name, value) → build common webhook message payloads without hand-writing raw JSONPresenceJS.webhooks() → access the Discord webhook servicePresenceJSEvents.build(event => {})PresenceJSEvents.ready(event => {})PresenceJSEvents.disconnected(event => {})PresenceJSEvents.webhookResponse(event => {})PresenceJSEvents.webhookError(event => {})Example build handler:
PresenceJSEvents.build(event => {
const presence = event.getPresence()
const context = event.getContext()
presence.setClientId('123456789012345678')
presence.setDetails('Playing Minecraft')
presence.setState(context && context.getSingleplayer() ? 'Singleplayer world' : 'In-game')
presence.setLargeImage(PresenceJS.image('minecraft', 'Minecraft 1.20.1'))
presence.setSmallImage(PresenceJS.image('kubejs', 'Customized with KubeJS'))
presence.setButtons([
PresenceJS.button('Download Pack', 'https://example.com'),
PresenceJS.button('Join Discord', 'https://discord.gg/example')
])
})
PresenceJS exposes Discord's webhook HTTP surface through PresenceJS.webhooks().
The webhook service includes typed request builders for the documented Discord webhook endpoints:
createWebhook(channelId)getChannelWebhooks(channelId)getGuildWebhooks(guildId)getWebhook(webhookId)getWebhookWithToken(webhookId, webhookToken) or getWebhookWithToken(webhookUrl)modifyWebhook(webhookId)modifyWebhookWithToken(webhookId, webhookToken) or modifyWebhookWithToken(webhookUrl)deleteWebhook(webhookId)deleteWebhookWithToken(webhookId, webhookToken) or deleteWebhookWithToken(webhookUrl)executeWebhook(webhookId, webhookToken) or executeWebhook(webhookUrl)executeSlackCompatibleWebhook(webhookId, webhookToken) or executeSlackCompatibleWebhook(webhookUrl)executeGithubCompatibleWebhook(webhookId, webhookToken) or executeGithubCompatibleWebhook(webhookUrl)getWebhookMessage(webhookId, webhookToken, messageId) or getWebhookMessage(webhookUrl, messageId)editWebhookMessage(webhookId, webhookToken, messageId) or editWebhookMessage(webhookUrl, messageId)deleteWebhookMessage(webhookId, webhookToken, messageId) or deleteWebhookMessage(webhookUrl, messageId)request(method, pathOrUrl) for raw access to Discord webhook API paths or an official Discord webhook URLFor real Discord incoming webhooks, the URL overloads are the intended convenience surface. createWebhook(channelId) still uses a channel id because that endpoint creates the webhook before a webhook URL exists.
PresenceJS rejects non-Discord hosts, inline query strings, fragments, and non-webhook API routes. Use .query(...), .waitForResponse(...), .threadId(...), and .withComponents(...) to add query parameters safely.
PresenceJS also blocks webhook execution entirely unless the client config setting webhookEnabled is turned on.
Each DiscordWebhookRequest supports:
.authorization(rawHeader).botToken(token).bearerToken(token).auditLogReason(reason).query(name, value).waitForResponse(boolean).threadId(threadId).withComponents(boolean).header(name, value).json(JsonElement) or .message(DiscordWebhookMessage).addFile(webhooks.file(path))For common execute payloads, PresenceJS also exposes typed KubeJS builders for:
content, username, avatar_url, tts, flags, and thread_nametitle, description, url, color, fields, author, footer, timestamp, image, and thumbnailRaw JSON is still supported for webhook features that are not wrapped yet.
The service supports both synchronous and asynchronous execution:
webhooks.execute(request) returns a DiscordWebhookResponsewebhooks.submit(request) queues the request on a background thread and returns a request idwebhooks.cancel(requestId) cancels a queued request when possibleExecute a webhook message and wait for the created message object:
const webhooks = PresenceJS.webhooks()
const webhookUrl = 'https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN'
const message = PresenceJS.webhookMessage()
message.setContent('Hello from PresenceJS')
message.setUsername('PresenceJS')
const embed = PresenceJS.webhookEmbed()
embed.setTitle('Webhook builder')
embed.setDescription('Built from typed KubeJS helpers')
const field = PresenceJS.webhookField('Mode', 'Typed message body')
field.setInline(true)
embed.addField(field)
message.addEmbed(embed)
const response = webhooks.execute(
webhooks.executeWebhook(webhookUrl)
.waitForResponse(true)
.message(message)
)
if (response.isSuccess()) {
console.log(response.getBody())
}
Create or manage webhooks with an authenticated bot token:
const webhooks = PresenceJS.webhooks()
const body = PresenceJS.json('{"name":"PresenceJS Hook"}')
const response = webhooks.execute(
webhooks.createWebhook('CHANNEL_ID')
.botToken('YOUR_BOT_TOKEN')
.auditLogReason('Provisioned from PresenceJS')
.json(body)
)
Send a webhook asynchronously and handle completion in KubeJS events:
const webhooks = PresenceJS.webhooks()
const webhookUrl = 'https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN'
const message = PresenceJS.webhookMessage()
message.setContent('Async message')
const requestId = webhooks.submit(
webhooks.executeWebhook(webhookUrl)
.waitForResponse(true)
.message(message)
)
PresenceJSEvents.webhookResponse(event => {
if (event.getRequestId() !== requestId) return
console.log(event.getResponse().getStatusCode())
})
PresenceJSEvents.webhookError(event => {
if (event.getRequestId() !== requestId) return
console.error(event.getMessage())
})
For payloads with files, use webhooks.file(path) or webhooks.textFile(filename, content) and add the parts to the request with .addFile(...). For unsupported webhook properties, you can still drop to raw JSON with PresenceJS.json(...) plus .json(...). JSON request bodies and response bodies use Gson types, and com.google.gson is whitelisted for client-side KubeJS scripts.