diff --git a/src/config/types.discord.ts b/src/config/types.discord.ts index 73a84383ff8..ba65d1c8d1b 100644 --- a/src/config/types.discord.ts +++ b/src/config/types.discord.ts @@ -175,6 +175,14 @@ export type DiscordAccountConfig = { pluralkit?: DiscordPluralKitConfig; /** Outbound response prefix override for this channel/account. */ responsePrefix?: string; + /** Bot activity status text (e.g. "Watching X"). */ + activity?: string; + /** Bot status (online|dnd|idle|invisible). Default: online. */ + status?: "online" | "dnd" | "idle" | "invisible" | "offline"; + /** Activity type (0=Game, 1=Streaming, 2=Listening, 3=Watching, 5=Competing). Default: 3 (Watching). */ + activityType?: number; + /** Streaming URL (Twitch/YouTube). Required if activityType=1. */ + activityUrl?: string; }; export type DiscordConfig = { diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index 7e2c4bd0f47..dfd2fb0ba30 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -332,6 +332,10 @@ export const DiscordAccountSchema = z .strict() .optional(), responsePrefix: z.string().optional(), + activity: z.string().optional(), + status: z.enum(["online", "dnd", "idle", "invisible", "offline"]).optional(), + activityType: z.number().int().min(0).max(5).optional(), + activityUrl: z.string().optional(), }) .strict(); diff --git a/src/discord/monitor/provider.ts b/src/discord/monitor/provider.ts index 24391c17314..06365b1fd97 100644 --- a/src/discord/monitor/provider.ts +++ b/src/discord/monitor/provider.ts @@ -1,4 +1,4 @@ -import { Client, type BaseMessageInteractiveComponent } from "@buape/carbon"; +import { Client, ReadyListener, type BaseMessageInteractiveComponent } from "@buape/carbon"; import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway"; import { Routes } from "discord-api-types/v10"; import { HttpsProxyAgent } from "https-proxy-agent"; @@ -40,6 +40,7 @@ import { registerDiscordListener, } from "./listeners.js"; import { createDiscordMessageHandler } from "./message-handler.js"; +import { resolveDiscordPresenceUpdate } from "./presence.js"; import { createDiscordCommandArgFallbackButton, createDiscordNativeCommand, @@ -557,6 +558,22 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { ); } + class DiscordStatusReadyListener extends ReadyListener { + async handle(_data: unknown, client: Client) { + const gateway = client.getPlugin("gateway"); + if (!gateway) { + return; + } + + const presence = resolveDiscordPresenceUpdate(discordCfg); + if (!presence) { + return; + } + + gateway.updatePresence(presence); + } + } + const client = new Client( { baseUrl: "http://localhost", @@ -568,7 +585,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { }, { commands, - listeners: [], + listeners: [new DiscordStatusReadyListener()], components, }, [createDiscordGatewayPlugin({ discordConfig: discordCfg, runtime })],