import type {
ChannelDirectoryEntry,
DirectoryConfigParams,
} from "openclaw/plugin-sdk/directory-runtime" ;
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime" ;
import { resolveDiscordAccount } from "./accounts.js" ;
import { fetchDiscord } from "./api.js" ;
import { rememberDiscordDirectoryUser } from "./directory-cache.js" ;
import { normalizeDiscordSlug } from "./monitor/allow-list.js" ;
import { normalizeDiscordToken } from "./token.js" ;
type DiscordGuild = { id: string; name: string };
type DiscordUser = { id: string; username: string; global_name?: string; bot?: boolean };
type DiscordMember = { user: DiscordUser; nick?: string | null };
type DiscordChannel = { id: string; name?: string | null };
type DiscordDirectoryAccess = { token: string; query: string; accountId: string };
function normalizeQuery(value?: string | null ): string {
return normalizeOptionalLowercaseString(value) ?? "" ;
}
function buildUserRank(user: DiscordUser): number {
return user.bot ? 0 : 1 ;
}
function resolveDiscordDirectoryAccess(
params: DirectoryConfigParams,
): DiscordDirectoryAccess | null {
const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
const token = normalizeDiscordToken(account.token, "channels.discord.token" );
if (!token) {
return null ;
}
return { token, query: normalizeQuery(params.query), accountId: account.accountId };
}
async function listDiscordGuilds(token: string): Promise<DiscordGuild[]> {
const rawGuilds = await fetchDiscord<DiscordGuild[]>("/users/@me/guilds" , token);
return rawGuilds.filter((guild) => guild.id && guild.name);
}
export async function listDiscordDirectoryGroupsLive(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const access = resolveDiscordDirectoryAccess(params);
if (!access) {
return [];
}
const { token, query } = access;
const guilds = await listDiscordGuilds(token);
const rows: ChannelDirectoryEntry[] = [];
for (const guild of guilds) {
const channels = await fetchDiscord<DiscordChannel[]>(`/guilds/${guild.id}/channels`, token);
for (const channel of channels) {
const name = channel.name?.trim();
if (!name) {
continue ;
}
if (query && !normalizeDiscordSlug(name).includes(normalizeDiscordSlug(query))) {
continue ;
}
rows.push({
kind: "group" ,
id: `channel:${channel.id}`,
name,
handle: `#${name}`,
raw: channel,
});
if (typeof params.limit === "number" && params.limit > 0 && rows.length >= params.limit) {
return rows;
}
}
}
return rows;
}
export async function listDiscordDirectoryPeersLive(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const access = resolveDiscordDirectoryAccess(params);
if (!access) {
return [];
}
const { token, query, accountId } = access;
if (!query) {
return [];
}
const guilds = await listDiscordGuilds(token);
const rows: ChannelDirectoryEntry[] = [];
const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : 25 ;
for (const guild of guilds) {
const paramsObj = new URLSearchParams({
query,
limit: String(Math.min(limit, 100 )),
});
const members = await fetchDiscord<DiscordMember[]>(
`/guilds/${guild.id}/members/search?${paramsObj.toString()}`,
token,
);
for (const member of members) {
const user = member.user;
if (!user?.id) {
continue ;
}
rememberDiscordDirectoryUser({
accountId,
userId: user.id,
handles: [
user.username,
user.global_name,
member.nick,
user.username ? `@${user.username}` : null ,
],
});
const name = member.nick?.trim() || user.global_name?.trim() || user.username?.trim();
rows.push({
kind: "user" ,
id: `user:${user.id}`,
name: name || undefined,
handle: user.username ? `@${user.username}` : undefined,
rank: buildUserRank(user),
raw: member,
});
if (rows.length >= limit) {
return rows;
}
}
}
return rows;
}
Messung V0.5 in Prozent C=100 H=90 G=95
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland