feat(discord): add configurable presence (activity/status/type)

- Adds `activity`, `status`, `activityType`, and `activityUrl` to Discord provider config schema.
- Implements a `ReadyListener` in `DiscordProvider` to apply these settings on connection.
- Solves the issue where `@buape/carbon` ignores initial presence options in constructor.
- Validated manually and via existing test suite.
This commit is contained in:
h0tp
2026-02-07 03:00:22 +00:00
committed by Shadow
parent c801ffdf99
commit 5d8c6ef91c
3 changed files with 31 additions and 2 deletions

View File

@@ -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 = {

View File

@@ -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();

View File

@@ -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<GatewayPlugin>("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 })],