import type {
DiscordGuildChannelConfig,
DiscordGuildEntry,
OpenClawConfig,
} from "openclaw/plugin-sdk/config-runtime" ;
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime" ;
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime" ;
export type DiscordChannelPermissionsAuditEntry = {
channelId: string;
ok: boolean ;
missing?: string[];
error?: string | null ;
matchKey?: string;
matchSource?: "id" ;
};
export type DiscordChannelPermissionsAudit = {
ok: boolean ;
checkedChannels: number;
unresolvedChannels: number;
channels: DiscordChannelPermissionsAuditEntry[];
elapsedMs: number;
};
const REQUIRED_CHANNEL_PERMISSIONS = ["ViewChannel" , "SendMessages" ] as const ;
function shouldAuditChannelConfig(config: DiscordGuildChannelConfig | undefined) {
if (!config) {
return true ;
}
if (config.enabled === false ) {
return false ;
}
return true ;
}
export function listConfiguredGuildChannelKeys(
guilds: Record<string, DiscordGuildEntry> | undefined,
): string[] {
if (!guilds) {
return [];
}
const ids = new Set<string>();
for (const entry of Object.values(guilds)) {
if (!entry || typeof entry !== "object" ) {
continue ;
}
const channelsRaw = (entry as { channels?: unknown }).channels;
if (!isRecord(channelsRaw)) {
continue ;
}
for (const [key, value] of Object.entries(channelsRaw)) {
const channelId = normalizeOptionalString(key) ?? "" ;
if (!channelId) {
continue ;
}
if (channelId === "*" ) {
continue ;
}
if (!shouldAuditChannelConfig(value as DiscordGuildChannelConfig | undefined)) {
continue ;
}
ids.add(channelId);
}
}
return [...ids].toSorted((a, b) => a.localeCompare(b));
}
export function collectDiscordAuditChannelIdsForGuilds(
guilds: Record<string, DiscordGuildEntry> | undefined,
) {
const keys = listConfiguredGuildChannelKeys(guilds);
const channelIds = keys.filter((key) => /^\d+$/.test(key));
const unresolvedChannels = keys.length - channelIds.length;
return { channelIds, unresolvedChannels };
}
export async function auditDiscordChannelPermissionsWithFetcher(params: {
cfg: OpenClawConfig;
token: string;
accountId?: string | null ;
channelIds: string[];
timeoutMs: number;
fetchChannelPermissions: (
channelId: string,
params: { cfg: OpenClawConfig; token: string; accountId?: string },
) => Promise<{
permissions: string[];
}>;
}): Promise<DiscordChannelPermissionsAudit> {
const started = Date.now();
const token = normalizeOptionalString(params.token) ?? "" ;
if (!token || params.channelIds.length === 0 ) {
return {
ok: true ,
checkedChannels: 0 ,
unresolvedChannels: 0 ,
channels: [],
elapsedMs: Date.now() - started,
};
}
const required = [...REQUIRED_CHANNEL_PERMISSIONS];
const channels: DiscordChannelPermissionsAuditEntry[] = [];
for (const channelId of params.channelIds) {
try {
const perms = await params.fetchChannelPermissions(channelId, {
cfg: params.cfg,
token,
accountId: params.accountId ?? undefined,
});
const missing = required.filter((p) => !perms.permissions.includes(p));
channels.push({
channelId,
ok: missing.length === 0 ,
missing: missing.length ? missing : undefined,
error: null ,
matchKey: channelId,
matchSource: "id" ,
});
} catch (err) {
channels.push({
channelId,
ok: false ,
error: formatErrorMessage(err),
matchKey: channelId,
matchSource: "id" ,
});
}
}
return {
ok: channels.every((c) => c.ok),
checkedChannels: channels.length,
unresolvedChannels: 0 ,
channels,
elapsedMs: Date.now() - started,
};
}
Messung V0.5 in Prozent C=100 H=91 G=95
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland