项目文件夹

文件
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

541 行
19 KiB
TypeScript

/**
* Discord Plugin Settings Banner
* Beautiful ANSI art display for configuration on startup
* Includes tiered permission system for invite URLs
*/
import type { IAgentRuntime } from "@elizaos/core";
import { lifeOpsPassiveConnectorsEnabled } from "@elizaos/core";
import { listEnabledDiscordAccounts } from "./accounts";
import { getDiscordSettings } from "./environment";
import {
type DiscordPermissionValues,
getPermissionValues,
} from "./permissions";
import type { DiscordSettings } from "./types";
/** Per-account reply-config overrides used by the suppression diagnostic. */
type AccountReplyOverrides = {
autoReply?: boolean;
shouldIgnoreDirectMessages?: boolean;
/** DM block (mirrors `DiscordAccountConfig.dm` in service.ts resolution). */
dm?: {
policy?: "open" | "allowlist" | "pairing" | "disabled";
allowFrom?: Array<string | number>;
};
};
/** DM policies that silently drop replies from un-allowlisted/unpaired senders. */
type RestrictiveDmPolicy = "allowlist" | "pairing" | "disabled";
/**
* Resolve an account's effective DM allowlist, mirroring
* `resolveDiscordSettingsForAccount` in service.ts (per-account `dm.allowFrom`
* wins when non-empty, otherwise the base `allowFrom`).
*/
function resolveEffectiveAllowFrom(
config: AccountReplyOverrides,
base: DiscordSettings,
): string[] {
const dmAllowFrom = config.dm?.allowFrom
?.map((value) => String(value).trim())
.filter((value) => value.length > 0);
if (dmAllowFrom && dmAllowFrom.length > 0) {
return dmAllowFrom;
}
return base.allowFrom ?? [];
}
/**
* Build the remediation message for a restrictive DM policy value. Every variant
* points at DISCORD_DM_POLICY (and the allowFrom escape hatch) so an operator is
* not misled into flipping only one var.
*/
function dmPolicyReason(policy: RestrictiveDmPolicy): string {
if (policy === "disabled") {
return (
"DISCORD_DM_POLICY='disabled' drops ALL direct messages; set " +
"DISCORD_DM_POLICY=open (or character.settings.discord.dmPolicy) to reply in DMs"
);
}
if (policy === "allowlist") {
return (
"DISCORD_DM_POLICY='allowlist' only replies to senders in DISCORD_ALLOW_FROM " +
"or the dynamic pairing allowlist; set DISCORD_DM_POLICY=open, or add senders to " +
"DISCORD_ALLOW_FROM (or character.settings.discord.allowFrom), to reply in DMs"
);
}
// "pairing" is the default and is issue #10216 root cause #1.
return (
"DISCORD_DM_POLICY='pairing' (the default) requires each new DM sender to " +
"complete pairing before the agent replies, so unpaired senders are silently " +
"dropped; set DISCORD_DM_POLICY=open to reply to all DMs, or add allowed senders " +
"to DISCORD_ALLOW_FROM (or character.settings.discord.allowFrom)"
);
}
/**
* Inspect the effective Discord reply configuration and emit up to one startup
* warning per distinct failure mode, naming the specific active reason(s) and
* the exact env var(s) an operator must flip to re-enable replies.
*
* Two independent diagnostics, because the suppressors live at different scopes:
*
* 1. GLOBAL — when EVERY enabled account is globally suppressed (passive mode
* on, or every account's `autoReply` off) the bot replies to NOTHING. One
* "will NOT auto-reply to ANY messages" warning, naming passive/autoReply.
*
* 2. DM-ONLY — when the bot DOES reply in channels but DM replies are still
* silently dropped by `shouldIgnoreDirectMessages` and/or a restrictive
* `dmPolicy` (the default 'pairing' blocks unpaired senders — issue #10216
* root cause #1). A SEPARATE "channel replies are enabled but DM replies
* will be suppressed" warning. The two are mutually exclusive: if the bot
* replies to nothing, the DM detail is moot, so only the global one fires.
*
* Diagnostics only: this reads resolved settings and logs. It never mutates
* configuration or reply behavior. The gates it mirrors live in
* `MessageManager.handleMessage` (messages.ts): the channel/global gate is
* `!autoReply || lifeOpsPassiveConnectorsEnabled(runtime)`; the DM gate is
* `shouldIgnoreDirectMessages` (with an allowFrom/dynamic-allowlist exception)
* plus the `dmPolicy` access check in `checkDmAccess`.
*
* Multi-account aware: per-account configs can override `autoReply`,
* `shouldIgnoreDirectMessages`, and `dm.policy`/`dm.allowFrom` (mirrors
* `resolveDiscordSettingsForAccount` in service.ts: `config.X ?? base.X`). DM
* suppression is only flagged on accounts that actually reply in channels, so a
* fully-silent account is reported once (global), not twice.
*
* @param runtime - The agent runtime used to resolve settings and log.
*/
export function warnIfRepliesSuppressed(runtime: IAgentRuntime): void {
// Skip the diagnostic when no enabled account has a token: index.ts still
// runs the banner in that path, but the bot can never connect, so a
// "Discord is connected but will NOT reply" warning would be misleading.
// listEnabledDiscordAccounts is the same connection gate the service uses
// (env DISCORD_API_TOKEN/DISCORD_BOT_TOKENS, character.settings.discord, and
// per-account tokens all flow through it), so this covers every supported
// credential path. The existing missing-token warning in index.ts owns the
// no-credentials case.
const accounts = listEnabledDiscordAccounts(runtime);
if (accounts.length === 0) {
return;
}
const base = getDiscordSettings(runtime);
// Passive mode is a runtime-global gate (not per-account); when on it
// suppresses replies for every account.
const passiveEnabled = lifeOpsPassiveConnectorsEnabled(runtime);
// Resolve each enabled account's effective reply settings, mirroring
// resolveDiscordSettingsForAccount's `config.X ?? base.X` precedence.
let anyAutoReplyOff = false;
let anyReplyEnabled = false;
// DM-suppression tracking, scoped to accounts that DO reply in channels: an
// account that replies to nothing is covered by the global warning instead.
let dmIgnoredOnReplyEnabled = false;
const restrictiveDmPolicies = new Set<RestrictiveDmPolicy>();
for (const account of accounts) {
const config = (account.config ?? {}) as AccountReplyOverrides;
const effectiveAutoReply = config.autoReply ?? base.autoReply;
const effectiveIgnoreDms =
config.shouldIgnoreDirectMessages ?? base.shouldIgnoreDirectMessages;
const effectiveDmPolicy = config.dm?.policy ?? base.dmPolicy ?? "pairing";
const hasAllowFrom = resolveEffectiveAllowFrom(config, base).length > 0;
if (!effectiveAutoReply) {
anyAutoReplyOff = true;
}
// An account replies in channels unless passive mode is on or its
// effective autoReply is off. DM suppression only matters for accounts
// that reply in channels — otherwise the global gate already silences it.
const replyEnabledInChannels = !passiveEnabled && effectiveAutoReply;
if (!replyEnabledInChannels) {
continue;
}
anyReplyEnabled = true;
if (effectiveIgnoreDms) {
dmIgnoredOnReplyEnabled = true;
}
// A restrictive dmPolicy with no static allowlist drops unpaired/
// non-allowlisted senders. With an allowFrom list configured the operator
// has deliberately scoped DM access, so the policy itself isn't flagged.
if (effectiveDmPolicy !== "open" && !hasAllowFrom) {
restrictiveDmPolicies.add(effectiveDmPolicy);
}
}
// Case 1 (GLOBAL): every enabled account is globally suppressed, so the bot
// will not reply to ANY message — channel or DM. A lone reply-enabled account
// means the bot is not silent overall, so this branch is skipped then.
if (!anyReplyEnabled) {
const reasons: string[] = [];
if (passiveEnabled) {
reasons.push(
"passive-connectors mode is ON (inbound persisted, replies suppressed); " +
"set ELIZA_LIFEOPS_PASSIVE_CONNECTORS=false " +
"(or LIFEOPS_PASSIVE_CONNECTORS=false) to allow replies",
);
}
if (anyAutoReplyOff) {
reasons.push(
"autoReply is OFF; set DISCORD_AUTO_REPLY=true " +
"(or character.settings.discord.autoReply=true, or the per-account " +
"autoReply) to allow replies",
);
}
runtime.logger.warn(
{ src: "plugin:discord", agentId: runtime.agentId },
`Discord is connected but will NOT auto-reply to any messages. Active reason(s): ${reasons
.map((r, i) => `(${i + 1}) ${r}`)
.join("; ")}.`,
);
return;
}
// Case 2 (DM-ONLY): channel replies work, but DM replies are silently dropped
// by the DM-ignore flag and/or a restrictive dmPolicy (the default 'pairing'
// blocks unpaired senders — issue #10216 root cause #1). Case 1's global gate
// returns above, so this branch handles the DM-only sub-case it does not cover.
const dmReasons: string[] = [];
if (dmIgnoredOnReplyEnabled) {
dmReasons.push(
"DISCORD_SHOULD_IGNORE_DIRECT_MESSAGES is ON, so DMs are dropped except " +
"from senders in DISCORD_ALLOW_FROM or the dynamic pairing allowlist; set " +
"DISCORD_SHOULD_IGNORE_DIRECT_MESSAGES=false to reply in DMs — note this " +
"alone is not enough, as DISCORD_DM_POLICY (default 'pairing') still gates " +
"DMs, so also set DISCORD_DM_POLICY=open or add senders to DISCORD_ALLOW_FROM",
);
}
for (const policy of restrictiveDmPolicies) {
dmReasons.push(dmPolicyReason(policy));
}
if (dmReasons.length === 0) {
return;
}
runtime.logger.warn(
{ src: "plugin:discord", agentId: runtime.agentId },
`Discord channel replies are enabled but DM replies will be suppressed. Active reason(s): ${dmReasons
.map((r, i) => `(${i + 1}) ${r}`)
.join("; ")}.`,
);
}
const ANSI = {
reset: "\x1b[0m",
bold: "\x1b[1m",
dim: "\x1b[2m",
blue: "\x1b[34m",
brightRed: "\x1b[91m",
brightGreen: "\x1b[92m",
brightYellow: "\x1b[93m",
brightBlue: "\x1b[94m",
brightMagenta: "\x1b[95m",
brightCyan: "\x1b[96m",
brightWhite: "\x1b[97m",
};
export interface PluginSetting {
name: string;
value: unknown;
defaultValue?: unknown;
sensitive?: boolean;
required?: boolean;
}
export interface BannerOptions {
pluginName: string;
description?: string;
settings: PluginSetting[];
runtime: IAgentRuntime;
/** Discord Application ID for generating invite URLs */
applicationId?: string;
/** Permission values for the 3x2 tier matrix */
discordPermissions?: DiscordPermissionValues;
}
function mask(v: string): string {
if (!v || v.length <= 8) {
return "••••••••";
}
return `${v.slice(0, 4)}${"•".repeat(Math.min(12, v.length - 8))}${v.slice(-4)}`;
}
/**
* Format a value for display in the banner.
*
* @param value - The value to format; may be `undefined`, `null`, or an empty string.
* @param sensitive - Whether the value should be obfuscated for display.
* @param maxLen - Maximum allowed length of the returned string; longer values are truncated with an ellipsis.
* @returns A display string: `'(not set)'` if `value` is `undefined`, `null`, or an empty string; a masked representation if `sensitive` is true; otherwise the stringified value truncated to at most `maxLen` characters (truncated strings end with `'...'`).
*/
function fmtVal(value: unknown, sensitive: boolean, maxLen: number): string {
let s: string;
if (value === undefined || value === null || value === "") {
s = "(not set)";
} else if (sensitive) {
s = mask(String(value));
} else {
s = String(value);
}
if (s.length > maxLen) {
s = `${s.slice(0, maxLen - 3)}...`;
}
return s;
}
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape codes are required for terminal formatting
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
/**
* Pads a string with trailing spaces until its visible (ANSI-stripped) length is at least the given width.
*
* @param s - The input string which may contain ANSI escape sequences.
* @param n - The target visible width (number of characters) after padding.
* @returns The original string if its visible length is >= `n`, otherwise the string with trailing spaces appended so its visible length equals `n`.
*/
function pad(s: string, n: number): string {
const len = s.replace(ANSI_PATTERN, "").length;
if (len >= n) {
return s;
}
return s + " ".repeat(n - len);
}
function line(content: string): string {
const len = content.replace(ANSI_PATTERN, "").length;
if (len <= 78) {
return content + " ".repeat(78 - len);
}
// Truncate based on visible character count, not raw string position
// This avoids cutting in the middle of ANSI escape sequences
let visibleCount = 0;
let result = "";
let i = 0;
while (i < content.length && visibleCount < 78) {
const remaining = content.slice(i);
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape codes are required for terminal formatting
const match = remaining.match(/^\x1b\[[0-9;]*m/);
if (match) {
// Include ANSI sequence without counting toward visible length
result += match[0];
i += match[0].length;
} else {
// Regular visible character
result += content[i];
visibleCount++;
i++;
}
}
// Reset any unclosed ANSI sequences after truncation
return result + ANSI.reset;
}
/**
* Render a framed ANSI banner that displays plugin settings and, when available, tiered Discord invite URLs.
*
* The banner lists each setting with masked or truncated values, a status (custom/default/unset/required),
* and an optional Discord invite section generated from `applicationId` and `discordPermissions`.
*
* @param options - Configuration for the banner, including `settings`, the `runtime` used to emit the banner,
* and optional Discord invite data (`applicationId`, `discordPermissions`).
*/
export function printBanner(options: BannerOptions): void {
const { settings, runtime } = options;
const R = ANSI.reset,
D = ANSI.dim,
B = ANSI.bold;
const c1 = ANSI.brightBlue,
c2 = ANSI.brightCyan,
c3 = ANSI.brightMagenta;
const top = `${c1}${"═".repeat(78)}${R}`;
const mid = `${c1}${"═".repeat(78)}${R}`;
const bot = `${c1}${"═".repeat(78)}${R}`;
const row = (s: string) => `${c1}${R}${line(s)}${c1}${R}`;
const lines: string[] = [""];
lines.push(top);
lines.push(row(` ${B}Character: ${runtime.character.name}${R}`));
lines.push(mid);
lines.push(
row(
`${c2} ██████╗ ██╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ${c3}◖ ◗${R}`,
),
);
lines.push(
row(
`${c2} ██╔══██╗██║██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔══██╗ ${c3}◖===◗${R}`,
),
);
lines.push(
row(
`${c2} ██║ ██║██║███████╗██║ ██║ ██║██████╔╝██║ ██║ ${c3}╰─╯${R}`,
),
);
lines.push(
row(
`${c2} ██████╔╝██║╚════██║╚██████╗╚██████╔╝██║ ██║██████╔╝ ${c3}(◠◠)${R}`,
),
);
lines.push(
row(
`${c2} ╚═════╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ${c3}‿‿${R}`,
),
);
lines.push(
row(
`${D} Bot Integration • Servers • Channels • Voice${R}`,
),
);
lines.push(mid);
const NW = 34,
VW = 26,
SW = 8;
lines.push(
row(
` ${B}${pad("ENV VARIABLE", NW)} ${pad("VALUE", VW)} ${pad("STATUS", SW)}${R}`,
),
);
lines.push(
row(` ${D}${"-".repeat(NW)} ${"-".repeat(VW)} ${"-".repeat(SW)}${R}`),
);
for (const s of settings) {
const set = s.value !== undefined && s.value !== null && s.value !== "";
// Normalize to string for comparison (e.g., boolean false vs string 'false')
const isDefault =
set &&
s.defaultValue !== undefined &&
String(s.value) === String(s.defaultValue);
let ico: string, st: string;
if (!set && s.required) {
ico = `${ANSI.brightRed}${R}`;
st = `${ANSI.brightRed}REQUIRED${R}`;
} else if (!set) {
ico = `${D}${R}`;
st = `${D}unset${R}`;
} else if (isDefault) {
ico = `${ANSI.brightBlue}${R}`;
st = `${ANSI.brightBlue}default${R}`;
} else {
ico = `${ANSI.brightGreen}${R}`;
st = `${ANSI.brightGreen}custom${R}`;
}
const name = pad(s.name, NW - 2);
const val = pad(
fmtVal(s.value ?? s.defaultValue, s.sensitive ?? false, VW),
VW,
);
const status = pad(st, SW);
lines.push(row(` ${ico} ${c2}${name}${R} ${val} ${status}`));
}
lines.push(mid);
lines.push(
row(
` ${D}${ANSI.brightGreen}${D} custom ${ANSI.brightBlue}${D} default ○ unset ${ANSI.brightRed}${D} required → Set in .env${R}`,
),
);
lines.push(bot);
// Add Discord invite links organized by voice capability
if (options.applicationId && options.discordPermissions) {
const p = options.discordPermissions;
const baseUrl = `https://discord.com/api/oauth2/authorize?client_id=${options.applicationId}&scope=bot%20applications.commands&permissions=`;
lines.push("");
lines.push(`${B}${ANSI.brightCyan}🔗 Discord Bot Invite${R}`);
lines.push("");
lines.push(` ${B}🎙️ With Voice:${R}`);
lines.push(
` ${ANSI.brightGreen}● Basic${R} ${baseUrl}${p.basicVoice}`,
);
lines.push(
` ${ANSI.brightYellow}● Moderator${R} ${baseUrl}${p.moderatorVoice}`,
);
lines.push(
` ${ANSI.brightRed}● Admin${R} ${baseUrl}${p.adminVoice}`,
);
lines.push("");
lines.push(` ${B}💬 Without Voice:${R}`);
lines.push(` ${ANSI.brightCyan}○ Basic${R} ${baseUrl}${p.basic}`);
lines.push(
` ${ANSI.brightMagenta}○ Moderator${R} ${baseUrl}${p.moderator}`,
);
lines.push(` ${ANSI.brightBlue}○ Admin${R} ${baseUrl}${p.admin}`);
}
lines.push("");
runtime.logger.info(lines.join("\n"));
// Diagnostics: if the effective config will suppress all auto-replies, warn
// the operator with the exact reason(s) instead of failing silently.
warnIfRepliesSuppressed(runtime);
}
/**
* Print the Discord plugin banner with current settings.
*/
export function printDiscordBanner(runtime: IAgentRuntime): void {
// Get settings
const apiToken = runtime.getSetting("DISCORD_API_TOKEN");
const applicationId = runtime.getSetting("DISCORD_APPLICATION_ID");
const ignoreBots = runtime.getSetting("DISCORD_SHOULD_IGNORE_BOT_MESSAGES");
const ignoreDMs = runtime.getSetting("DISCORD_SHOULD_IGNORE_DIRECT_MESSAGES");
const onlyMentions = runtime.getSetting(
"DISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS",
);
const listenChannels = runtime.getSetting("DISCORD_LISTEN_CHANNEL_IDS");
const voiceChannelId = runtime.getSetting("DISCORD_VOICE_CHANNEL_ID");
printBanner({
pluginName: "plugin-discord",
description: "Discord bot integration for servers and channels",
applicationId: applicationId ? String(applicationId) : undefined,
discordPermissions: applicationId ? getPermissionValues() : undefined,
settings: [
{
name: "DISCORD_API_TOKEN",
value: apiToken,
sensitive: true,
required: true,
},
{ name: "DISCORD_APPLICATION_ID", value: applicationId },
{ name: "DISCORD_VOICE_CHANNEL_ID", value: voiceChannelId },
{ name: "DISCORD_LISTEN_CHANNEL_IDS", value: listenChannels },
{
name: "DISCORD_SHOULD_IGNORE_BOT_MESSAGES",
value: ignoreBots,
defaultValue: "true",
},
{
name: "DISCORD_SHOULD_IGNORE_DIRECT_MESSAGES",
value: ignoreDMs,
defaultValue: "true",
},
{
name: "DISCORD_SHOULD_RESPOND_ONLY_TO_MENTIONS",
value: onlyMentions,
defaultValue: "true",
},
],
runtime,
});
}