openclaw/src/channels/plugins/directory-config.ts
2026-01-18 00:31:42 +00:00

215 lines
8.3 KiB
TypeScript

import type { ClawdbotConfig } from "../../config/types.js";
import type { ChannelDirectoryEntry } from "./types.js";
import { resolveSlackAccount } from "../../slack/accounts.js";
import { resolveDiscordAccount } from "../../discord/accounts.js";
import { resolveTelegramAccount } from "../../telegram/accounts.js";
import { resolveWhatsAppAccount } from "../../web/accounts.js";
import { normalizeSlackMessagingTarget } from "./normalize/slack.js";
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js";
export type DirectoryConfigParams = {
cfg: ClawdbotConfig;
accountId?: string | null;
query?: string | null;
limit?: number | null;
};
export async function listSlackDirectoryPeersFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
const ids = new Set<string>();
for (const entry of account.dm?.allowFrom ?? []) {
const raw = String(entry).trim();
if (!raw || raw === "*") continue;
ids.add(raw);
}
for (const id of Object.keys(account.config.dms ?? {})) {
const trimmed = id.trim();
if (trimmed) ids.add(trimmed);
}
for (const channel of Object.values(account.config.channels ?? {})) {
for (const user of channel.users ?? []) {
const raw = String(user).trim();
if (raw) ids.add(raw);
}
}
return Array.from(ids)
.map((raw) => raw.trim())
.filter(Boolean)
.map((raw) => {
const mention = raw.match(/^<@([A-Z0-9]+)>$/i);
const normalizedUserId = (mention?.[1] ?? raw).replace(/^(slack|user):/i, "").trim();
if (!normalizedUserId) return null;
const target = `user:${normalizedUserId}`;
return normalizeSlackMessagingTarget(target) ?? target.toLowerCase();
})
.filter((id): id is string => Boolean(id))
.filter((id) => id.startsWith("user:"))
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "user", id }) as const);
}
export async function listSlackDirectoryGroupsFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
return Object.keys(account.config.channels ?? {})
.map((raw) => raw.trim())
.filter(Boolean)
.map((raw) => normalizeSlackMessagingTarget(raw) ?? raw.toLowerCase())
.filter((id) => id.startsWith("channel:"))
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "group", id }) as const);
}
export async function listDiscordDirectoryPeersFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
const ids = new Set<string>();
for (const entry of account.config.dm?.allowFrom ?? []) {
const raw = String(entry).trim();
if (!raw || raw === "*") continue;
ids.add(raw);
}
for (const id of Object.keys(account.config.dms ?? {})) {
const trimmed = id.trim();
if (trimmed) ids.add(trimmed);
}
for (const guild of Object.values(account.config.guilds ?? {})) {
for (const entry of guild.users ?? []) {
const raw = String(entry).trim();
if (raw) ids.add(raw);
}
for (const channel of Object.values(guild.channels ?? {})) {
for (const user of channel.users ?? []) {
const raw = String(user).trim();
if (raw) ids.add(raw);
}
}
}
return Array.from(ids)
.map((raw) => raw.trim())
.filter(Boolean)
.map((raw) => {
const mention = raw.match(/^<@!?(\d+)>$/);
const cleaned = (mention?.[1] ?? raw).replace(/^(discord|user):/i, "").trim();
if (!/^\d+$/.test(cleaned)) return null;
return `user:${cleaned}`;
})
.filter((id): id is string => Boolean(id))
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "user", id }) as const);
}
export async function listDiscordDirectoryGroupsFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveDiscordAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
const ids = new Set<string>();
for (const guild of Object.values(account.config.guilds ?? {})) {
for (const channelId of Object.keys(guild.channels ?? {})) {
const trimmed = channelId.trim();
if (trimmed) ids.add(trimmed);
}
}
return Array.from(ids)
.map((raw) => raw.trim())
.filter(Boolean)
.map((raw) => {
const mention = raw.match(/^<#(\d+)>$/);
const cleaned = (mention?.[1] ?? raw).replace(/^(discord|channel|group):/i, "").trim();
if (!/^\d+$/.test(cleaned)) return null;
return `channel:${cleaned}`;
})
.filter((id): id is string => Boolean(id))
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "group", id }) as const);
}
export async function listTelegramDirectoryPeersFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveTelegramAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
const raw = [
...(account.config.allowFrom ?? []).map((entry) => String(entry)),
...Object.keys(account.config.dms ?? {}),
];
return Array.from(
new Set(
raw
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => entry.replace(/^(telegram|tg):/i, "")),
),
)
.map((entry) => {
const trimmed = entry.trim();
if (!trimmed) return null;
if (/^-?\d+$/.test(trimmed)) return trimmed;
const withAt = trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
return withAt;
})
.filter((id): id is string => Boolean(id))
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "user", id }) as const);
}
export async function listTelegramDirectoryGroupsFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveTelegramAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
return Object.keys(account.config.groups ?? {})
.map((id) => id.trim())
.filter((id) => Boolean(id) && id !== "*")
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "group", id }) as const);
}
export async function listWhatsAppDirectoryPeersFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
return (account.allowFrom ?? [])
.map((entry) => String(entry).trim())
.filter((entry) => Boolean(entry) && entry !== "*")
.map((entry) => normalizeWhatsAppTarget(entry) ?? "")
.filter(Boolean)
.filter((id) => !isWhatsAppGroupJid(id))
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "user", id }) as const);
}
export async function listWhatsAppDirectoryGroupsFromConfig(
params: DirectoryConfigParams,
): Promise<ChannelDirectoryEntry[]> {
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId });
const q = params.query?.trim().toLowerCase() || "";
return Object.keys(account.groups ?? {})
.map((id) => id.trim())
.filter((id) => Boolean(id) && id !== "*")
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
.map((id) => ({ kind: "group", id }) as const);
}