mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-19 22:10:51 +00:00
Plugins: move config-backed directories behind channel plugins
This commit is contained in:
@@ -18,6 +18,10 @@ import {
|
||||
type ResolvedDiscordAccount,
|
||||
} from "./accounts.js";
|
||||
import { auditDiscordChannelPermissions, collectDiscordAuditChannelIds } from "./audit.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
import {
|
||||
isDiscordExecApprovalClientEnabled,
|
||||
shouldSuppressLocalDiscordExecApprovalPrompt,
|
||||
@@ -41,8 +45,6 @@ import {
|
||||
type ChannelPlugin,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
getChatChannelMeta,
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
|
||||
57
extensions/discord/src/directory-config.ts
Normal file
57
extensions/discord/src/directory-config.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import {
|
||||
applyDirectoryQueryAndLimit,
|
||||
collectNormalizedDirectoryIds,
|
||||
toDirectoryEntries,
|
||||
type DirectoryConfigParams,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import type { InspectedDiscordAccount } from "../../../src/channels/read-only-account-inspect.discord.runtime.js";
|
||||
import { inspectReadOnlyChannelAccount } from "../../../src/channels/read-only-account-inspect.js";
|
||||
|
||||
export async function listDiscordDirectoryPeersFromConfig(params: DirectoryConfigParams) {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "discord",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedDiscordAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allowFrom = account.config.allowFrom ?? account.config.dm?.allowFrom ?? [];
|
||||
const guildUsers = Object.values(account.config.guilds ?? {}).flatMap((guild) => [
|
||||
...(guild.users ?? []),
|
||||
...Object.values(guild.channels ?? {}).flatMap((channel) => channel.users ?? []),
|
||||
]);
|
||||
const ids = collectNormalizedDirectoryIds({
|
||||
sources: [allowFrom, Object.keys(account.config.dms ?? {}), guildUsers],
|
||||
normalizeId: (raw) => {
|
||||
const mention = raw.match(/^<@!?(\d+)>$/);
|
||||
const cleaned = (mention?.[1] ?? raw).replace(/^(discord|user):/i, "").trim();
|
||||
return /^\d+$/.test(cleaned) ? `user:${cleaned}` : null;
|
||||
},
|
||||
});
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listDiscordDirectoryGroupsFromConfig(params: DirectoryConfigParams) {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "discord",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedDiscordAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ids = collectNormalizedDirectoryIds({
|
||||
sources: Object.values(account.config.guilds ?? {}).map((guild) =>
|
||||
Object.keys(guild.channels ?? {}),
|
||||
),
|
||||
normalizeId: (raw) => {
|
||||
const mention = raw.match(/^<#(\d+)>$/);
|
||||
const cleaned = (mention?.[1] ?? raw).replace(/^(discord|channel|group):/i, "").trim();
|
||||
return /^\d+$/.test(cleaned) ? `channel:${cleaned}` : null;
|
||||
},
|
||||
});
|
||||
return toDirectoryEntries("group", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { DirectoryConfigParams } from "../../../src/channels/plugins/directory-config.js";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import type { DirectoryConfigParams } from "../../../src/plugin-sdk/directory-runtime.js";
|
||||
import { listDiscordDirectoryGroupsLive, listDiscordDirectoryPeersLive } from "./directory-live.js";
|
||||
|
||||
function makeParams(overrides: Partial<DirectoryConfigParams> = {}): DirectoryConfigParams {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
export {
|
||||
buildComputedAccountStatusSnapshot,
|
||||
buildTokenChannelStatusSummary,
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
@@ -20,6 +18,10 @@ export {
|
||||
} from "openclaw/plugin-sdk/discord-core";
|
||||
export { DiscordConfigSchema } from "openclaw/plugin-sdk/discord-core";
|
||||
export { readBooleanParam } from "openclaw/plugin-sdk/boolean-param";
|
||||
export {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
export {
|
||||
createScopedAccountConfigAccessors,
|
||||
createScopedChannelConfigBase,
|
||||
|
||||
@@ -13,8 +13,6 @@ import { resolveThreadSessionKeys, type RoutePeer } from "openclaw/plugin-sdk/ro
|
||||
import {
|
||||
buildComputedAccountStatusSnapshot,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
looksLikeSlackTargetId,
|
||||
normalizeSlackMessagingTarget,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
@@ -34,6 +32,10 @@ import {
|
||||
import { parseSlackBlocksInput } from "./blocks-input.js";
|
||||
import { createSlackActions } from "./channel-actions.js";
|
||||
import { createSlackWebClient } from "./client.js";
|
||||
import {
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
import { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy } from "./group-policy.js";
|
||||
import { isSlackInteractiveRepliesEnabled } from "./interactive-replies.js";
|
||||
import { normalizeAllowListLower } from "./monitor/allow-list.js";
|
||||
|
||||
60
extensions/slack/src/directory-config.ts
Normal file
60
extensions/slack/src/directory-config.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
applyDirectoryQueryAndLimit,
|
||||
collectNormalizedDirectoryIds,
|
||||
listDirectoryGroupEntriesFromMapKeys,
|
||||
toDirectoryEntries,
|
||||
type DirectoryConfigParams,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { normalizeSlackMessagingTarget } from "../../../src/channels/plugins/normalize/slack.js";
|
||||
import { inspectReadOnlyChannelAccount } from "../../../src/channels/read-only-account-inspect.js";
|
||||
import type { InspectedSlackAccount } from "../../../src/channels/read-only-account-inspect.slack.runtime.js";
|
||||
|
||||
export async function listSlackDirectoryPeersFromConfig(params: DirectoryConfigParams) {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "slack",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedSlackAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allowFrom = account.config.allowFrom ?? account.dm?.allowFrom ?? [];
|
||||
const channelUsers = Object.values(account.config.channels ?? {}).flatMap(
|
||||
(channel) => channel.users ?? [],
|
||||
);
|
||||
const ids = collectNormalizedDirectoryIds({
|
||||
sources: [allowFrom, Object.keys(account.config.dms ?? {}), channelUsers],
|
||||
normalizeId: (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}`;
|
||||
const normalized = normalizeSlackMessagingTarget(target) ?? target.toLowerCase();
|
||||
return normalized.startsWith("user:") ? normalized : null;
|
||||
},
|
||||
});
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listSlackDirectoryGroupsFromConfig(params: DirectoryConfigParams) {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "slack",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedSlackAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
return listDirectoryGroupEntriesFromMapKeys({
|
||||
groups: account.config.channels,
|
||||
query: params.query,
|
||||
limit: params.limit,
|
||||
normalizeId: (raw) => {
|
||||
const normalized = normalizeSlackMessagingTarget(raw) ?? raw.toLowerCase();
|
||||
return normalized.startsWith("channel:") ? normalized : null;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
buildTokenChannelStatusSummary,
|
||||
clearAccountEntryFields,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
@@ -32,6 +30,10 @@ import {
|
||||
import { buildTelegramExecApprovalButtons } from "./approval-buttons.js";
|
||||
import { auditTelegramGroupMembership, collectTelegramUnmentionedGroupIds } from "./audit.js";
|
||||
import { buildTelegramGroupPeerId } from "./bot/helpers.js";
|
||||
import {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
import {
|
||||
isTelegramExecApprovalClientEnabled,
|
||||
resolveTelegramExecApprovalTarget,
|
||||
|
||||
52
extensions/telegram/src/directory-config.ts
Normal file
52
extensions/telegram/src/directory-config.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { mapAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import {
|
||||
applyDirectoryQueryAndLimit,
|
||||
collectNormalizedDirectoryIds,
|
||||
listDirectoryGroupEntriesFromMapKeys,
|
||||
toDirectoryEntries,
|
||||
type DirectoryConfigParams,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { inspectReadOnlyChannelAccount } from "../../../src/channels/read-only-account-inspect.js";
|
||||
import type { InspectedTelegramAccount } from "../../../src/channels/read-only-account-inspect.telegram.runtime.js";
|
||||
|
||||
export async function listTelegramDirectoryPeersFromConfig(params: DirectoryConfigParams) {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "telegram",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedTelegramAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ids = collectNormalizedDirectoryIds({
|
||||
sources: [mapAllowFromEntries(account.config.allowFrom), Object.keys(account.config.dms ?? {})],
|
||||
normalizeId: (entry) => {
|
||||
const trimmed = entry.replace(/^(telegram|tg):/i, "").trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
if (/^-?\d+$/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
return trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
|
||||
},
|
||||
});
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listTelegramDirectoryGroupsFromConfig(params: DirectoryConfigParams) {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "telegram",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedTelegramAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
return listDirectoryGroupEntriesFromMapKeys({
|
||||
groups: account.config.groups,
|
||||
query: params.query,
|
||||
limit: params.limit,
|
||||
});
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
createWhatsAppOutboundBase,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
formatWhatsAppConfigAllowFromEntries,
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
readStringParam,
|
||||
resolveWhatsAppOutboundTarget,
|
||||
resolveWhatsAppHeartbeatRecipients,
|
||||
@@ -16,6 +14,10 @@ import {
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "openclaw/plugin-sdk/whatsapp";
|
||||
// WhatsApp-specific imports from local extension code (moved from src/web/ and src/channels/plugins/)
|
||||
import { resolveWhatsAppAccount, type ResolvedWhatsAppAccount } from "./accounts.js";
|
||||
import {
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
import { looksLikeWhatsAppTargetId, normalizeWhatsAppMessagingTarget } from "./normalize.js";
|
||||
import { getWhatsAppRuntime } from "./runtime.js";
|
||||
import { resolveWhatsAppOutboundSessionRoute } from "./session-route.js";
|
||||
|
||||
32
extensions/whatsapp/src/directory-config.ts
Normal file
32
extensions/whatsapp/src/directory-config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
listDirectoryGroupEntriesFromMapKeys,
|
||||
listDirectoryUserEntriesFromAllowFrom,
|
||||
type DirectoryConfigParams,
|
||||
} from "openclaw/plugin-sdk/directory-runtime";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../src/whatsapp/normalize.js";
|
||||
import { resolveWhatsAppAccount } from "./accounts.js";
|
||||
|
||||
export async function listWhatsAppDirectoryPeersFromConfig(params: DirectoryConfigParams) {
|
||||
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
return listDirectoryUserEntriesFromAllowFrom({
|
||||
allowFrom: account.allowFrom,
|
||||
query: params.query,
|
||||
limit: params.limit,
|
||||
normalizeId: (entry) => {
|
||||
const normalized = normalizeWhatsAppTarget(entry);
|
||||
if (!normalized || isWhatsAppGroupJid(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function listWhatsAppDirectoryGroupsFromConfig(params: DirectoryConfigParams) {
|
||||
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId });
|
||||
return listDirectoryGroupEntriesFromMapKeys({
|
||||
groups: account.groups,
|
||||
query: params.query,
|
||||
limit: params.limit,
|
||||
});
|
||||
}
|
||||
@@ -60,6 +60,27 @@ function dedupeDirectoryIds(ids: string[]): string[] {
|
||||
return Array.from(new Set(ids));
|
||||
}
|
||||
|
||||
export function collectNormalizedDirectoryIds(params: {
|
||||
sources: Iterable<unknown>[];
|
||||
normalizeId: (entry: string) => string | null | undefined;
|
||||
}): string[] {
|
||||
const ids = new Set<string>();
|
||||
for (const source of params.sources) {
|
||||
for (const value of source) {
|
||||
const raw = String(value).trim();
|
||||
if (!raw || raw === "*") {
|
||||
continue;
|
||||
}
|
||||
const normalized = params.normalizeId(raw);
|
||||
const trimmed = typeof normalized === "string" ? normalized.trim() : "";
|
||||
if (trimmed) {
|
||||
ids.add(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(ids);
|
||||
}
|
||||
|
||||
export function listDirectoryUserEntriesFromAllowFrom(params: {
|
||||
allowFrom?: readonly unknown[];
|
||||
query?: string | null;
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
import type { OpenClawConfig } from "../../config/types.js";
|
||||
import { mapAllowFromEntries } from "../../plugin-sdk/channel-config-helpers.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js";
|
||||
import type { InspectedDiscordAccount } from "../read-only-account-inspect.discord.runtime.js";
|
||||
import { inspectReadOnlyChannelAccount } from "../read-only-account-inspect.js";
|
||||
import type { InspectedSlackAccount } from "../read-only-account-inspect.slack.runtime.js";
|
||||
import type { InspectedTelegramAccount } from "../read-only-account-inspect.telegram.runtime.js";
|
||||
import { applyDirectoryQueryAndLimit, toDirectoryEntries } from "./directory-config-helpers.js";
|
||||
import { normalizeSlackMessagingTarget } from "./normalize/slack.js";
|
||||
import { getChannelPlugin } from "./registry.js";
|
||||
import type { ChannelDirectoryEntry } from "./types.js";
|
||||
|
||||
export type DirectoryConfigParams = {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
query?: string | null;
|
||||
limit?: number | null;
|
||||
};
|
||||
|
||||
function addAllowFromAndDmsIds(
|
||||
ids: Set<string>,
|
||||
allowFrom: readonly unknown[] | undefined,
|
||||
dms: Record<string, unknown> | undefined,
|
||||
) {
|
||||
for (const entry of allowFrom ?? []) {
|
||||
const raw = String(entry).trim();
|
||||
if (!raw || raw === "*") {
|
||||
continue;
|
||||
}
|
||||
ids.add(raw);
|
||||
}
|
||||
addTrimmedEntries(ids, Object.keys(dms ?? {}));
|
||||
}
|
||||
|
||||
function addTrimmedId(ids: Set<string>, value: unknown) {
|
||||
const trimmed = String(value).trim();
|
||||
if (trimmed) {
|
||||
ids.add(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
function addTrimmedEntries(ids: Set<string>, values: Iterable<unknown>) {
|
||||
for (const value of values) {
|
||||
addTrimmedId(ids, value);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTrimmedSet(
|
||||
ids: Set<string>,
|
||||
normalize: (raw: string) => string | null,
|
||||
): string[] {
|
||||
return Array.from(ids)
|
||||
.map((raw) => raw.trim())
|
||||
.filter(Boolean)
|
||||
.map((raw) => normalize(raw))
|
||||
.filter((id): id is string => Boolean(id));
|
||||
}
|
||||
|
||||
function objectValues<T>(value: Record<string, T> | undefined): T[] {
|
||||
return Object.values(value ?? {});
|
||||
}
|
||||
|
||||
export async function listSlackDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "slack",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedSlackAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
|
||||
addAllowFromAndDmsIds(ids, account.config.allowFrom ?? account.dm?.allowFrom, account.config.dms);
|
||||
for (const channel of Object.values(account.config.channels ?? {})) {
|
||||
addTrimmedEntries(ids, channel.users ?? []);
|
||||
}
|
||||
|
||||
const normalizedIds = normalizeTrimmedSet(ids, (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.startsWith("user:"));
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(normalizedIds, params));
|
||||
}
|
||||
|
||||
export async function listSlackDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "slack",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedSlackAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = Object.keys(account.config.channels ?? {})
|
||||
.map((raw) => raw.trim())
|
||||
.filter(Boolean)
|
||||
.map((raw) => normalizeSlackMessagingTarget(raw) ?? raw.toLowerCase())
|
||||
.filter((id) => id.startsWith("channel:"));
|
||||
return toDirectoryEntries("group", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listDiscordDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "discord",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedDiscordAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
|
||||
addAllowFromAndDmsIds(
|
||||
ids,
|
||||
account.config.allowFrom ?? account.config.dm?.allowFrom,
|
||||
account.config.dms,
|
||||
);
|
||||
for (const guild of objectValues(account.config.guilds)) {
|
||||
addTrimmedEntries(ids, guild.users ?? []);
|
||||
for (const channel of objectValues(guild.channels)) {
|
||||
addTrimmedEntries(ids, channel.users ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedIds = normalizeTrimmedSet(ids, (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}`;
|
||||
});
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(normalizedIds, params));
|
||||
}
|
||||
|
||||
export async function listDiscordDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "discord",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedDiscordAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = new Set<string>();
|
||||
for (const guild of objectValues(account.config.guilds)) {
|
||||
addTrimmedEntries(ids, Object.keys(guild.channels ?? {}));
|
||||
}
|
||||
|
||||
const normalizedIds = normalizeTrimmedSet(ids, (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}`;
|
||||
});
|
||||
return toDirectoryEntries("group", applyDirectoryQueryAndLimit(normalizedIds, params));
|
||||
}
|
||||
|
||||
export async function listTelegramDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "telegram",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedTelegramAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const raw = [
|
||||
...mapAllowFromEntries(account.config.allowFrom),
|
||||
...Object.keys(account.config.dms ?? {}),
|
||||
];
|
||||
const ids = 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));
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listTelegramDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = (await inspectReadOnlyChannelAccount({
|
||||
channelId: "telegram",
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
})) as InspectedTelegramAccount | null;
|
||||
if (!account || !("config" in account)) {
|
||||
return [];
|
||||
}
|
||||
const ids = Object.keys(account.config.groups ?? {})
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => Boolean(id) && id !== "*");
|
||||
return toDirectoryEntries("group", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listWhatsAppDirectoryPeersFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = getChannelPlugin("whatsapp")?.config.resolveAccount(
|
||||
params.cfg,
|
||||
params.accountId,
|
||||
) as { allowFrom?: unknown[] } | null | undefined;
|
||||
if (!account || typeof account !== "object") {
|
||||
return [];
|
||||
}
|
||||
const ids = (account.allowFrom ?? [])
|
||||
.map((entry: unknown) => String(entry).trim())
|
||||
.filter((entry) => Boolean(entry) && entry !== "*")
|
||||
.map((entry) => normalizeWhatsAppTarget(entry) ?? "")
|
||||
.filter(Boolean)
|
||||
.filter((id) => !isWhatsAppGroupJid(id));
|
||||
return toDirectoryEntries("user", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
|
||||
export async function listWhatsAppDirectoryGroupsFromConfig(
|
||||
params: DirectoryConfigParams,
|
||||
): Promise<ChannelDirectoryEntry[]> {
|
||||
const account = getChannelPlugin("whatsapp")?.config.resolveAccount(
|
||||
params.cfg,
|
||||
params.accountId,
|
||||
) as { groups?: Record<string, unknown> } | null | undefined;
|
||||
if (!account || typeof account !== "object") {
|
||||
return [];
|
||||
}
|
||||
const ids = Object.keys(account.groups ?? {})
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => Boolean(id) && id !== "*");
|
||||
return toDirectoryEntries("group", applyDirectoryQueryAndLimit(ids, params));
|
||||
}
|
||||
8
src/channels/plugins/directory-types.ts
Normal file
8
src/channels/plugins/directory-types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { OpenClawConfig } from "../../config/types.js";
|
||||
|
||||
export type DirectoryConfigParams = {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
query?: string | null;
|
||||
limit?: number | null;
|
||||
};
|
||||
@@ -1,14 +1,4 @@
|
||||
export { getChannelPlugin, listChannelPlugins, normalizeChannelId } from "./registry.js";
|
||||
export {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
export {
|
||||
applyChannelMatchMeta,
|
||||
buildChannelKeyCandidates,
|
||||
|
||||
@@ -2,13 +2,29 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, expectTypeOf, it } from "vitest";
|
||||
import {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "../../../extensions/discord/src/directory-config.js";
|
||||
import type { DiscordProbe } from "../../../extensions/discord/src/probe.js";
|
||||
import type { DiscordTokenResolution } from "../../../extensions/discord/src/token.js";
|
||||
import type { IMessageProbe } from "../../../extensions/imessage/src/probe.js";
|
||||
import type { SignalProbe } from "../../../extensions/signal/src/probe.js";
|
||||
import {
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
} from "../../../extensions/slack/src/directory-config.js";
|
||||
import type { SlackProbe } from "../../../extensions/slack/src/probe.js";
|
||||
import {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
} from "../../../extensions/telegram/src/directory-config.js";
|
||||
import type { TelegramProbe } from "../../../extensions/telegram/src/probe.js";
|
||||
import type { TelegramTokenResolution } from "../../../extensions/telegram/src/token.js";
|
||||
import {
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
} from "../../../extensions/whatsapp/src/directory-config.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { LineProbeResult } from "../../line/types.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
@@ -29,16 +45,6 @@ import {
|
||||
resolveChannelConfigWrites,
|
||||
resolveConfigWriteTargetFromPath,
|
||||
} from "./config-writes.js";
|
||||
import {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
} from "./directory-config.js";
|
||||
import { listChannelPlugins } from "./index.js";
|
||||
import { loadChannelPlugin } from "./load.js";
|
||||
import { loadChannelOutboundAdapter } from "./outbound/load.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type { DirectoryConfigParams } from "./plugins/directory-config.js";
|
||||
export type { DirectoryConfigParams } from "./plugins/directory-types.js";
|
||||
export type { ChannelDirectoryEntry } from "./plugins/types.js";
|
||||
|
||||
export type MessagingTargetKind = "user" | "channel";
|
||||
|
||||
@@ -32,7 +32,6 @@ export * from "../channels/plugins/actions/reaction-message-id.js";
|
||||
export * from "../channels/plugins/actions/shared.js";
|
||||
export type * from "../channels/plugins/types.js";
|
||||
export * from "../channels/plugins/config-writes.js";
|
||||
export * from "../channels/plugins/directory-config.js";
|
||||
export * from "../channels/plugins/media-payload.js";
|
||||
export * from "../channels/plugins/message-tool-schema.js";
|
||||
export * from "../channels/plugins/normalize/signal.js";
|
||||
@@ -46,6 +45,7 @@ export * from "../polls.js";
|
||||
export * from "../utils/message-channel.js";
|
||||
export { createActionGate, jsonResult, readStringParam } from "../agents/tools/common.js";
|
||||
export * from "./channel-lifecycle.js";
|
||||
export * from "./directory-runtime.js";
|
||||
export type {
|
||||
InteractiveButtonStyle,
|
||||
InteractiveReplyButton,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** Shared directory listing helpers for plugins that derive users/groups from config maps. */
|
||||
export type { DirectoryConfigParams } from "../channels/plugins/directory-types.js";
|
||||
export {
|
||||
applyDirectoryQueryAndLimit,
|
||||
collectNormalizedDirectoryIds,
|
||||
listDirectoryGroupEntriesFromMapKeys,
|
||||
listDirectoryGroupEntriesFromMapKeysAndAllowFrom,
|
||||
listDirectoryUserEntriesFromAllowFrom,
|
||||
|
||||
@@ -45,15 +45,14 @@ export {
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
} from "../channels/account-snapshot-fields.js";
|
||||
export {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "../channels/plugins/directory-config.js";
|
||||
|
||||
export {
|
||||
resolveDefaultGroupPolicy,
|
||||
resolveOpenProviderRuntimeGroupPolicy,
|
||||
} from "../config/runtime-group-policy.js";
|
||||
export {
|
||||
listDiscordDirectoryGroupsFromConfig,
|
||||
listDiscordDirectoryPeersFromConfig,
|
||||
} from "../../extensions/discord/src/directory-config.js";
|
||||
export {
|
||||
resolveDiscordGroupRequireMention,
|
||||
resolveDiscordGroupToolPolicy,
|
||||
|
||||
@@ -28,14 +28,14 @@ export {
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
resolveConfiguredFromRequiredCredentialStatuses,
|
||||
} from "../channels/account-snapshot-fields.js";
|
||||
export {
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
} from "../channels/plugins/directory-config.js";
|
||||
export {
|
||||
looksLikeSlackTargetId,
|
||||
normalizeSlackMessagingTarget,
|
||||
} from "../channels/plugins/normalize/slack.js";
|
||||
export {
|
||||
listSlackDirectoryGroupsFromConfig,
|
||||
listSlackDirectoryPeersFromConfig,
|
||||
} from "../../extensions/slack/src/directory-config.js";
|
||||
export {
|
||||
resolveDefaultGroupPolicy,
|
||||
resolveOpenProviderRuntimeGroupPolicy,
|
||||
|
||||
@@ -45,15 +45,14 @@ export {
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
} from "../channels/account-snapshot-fields.js";
|
||||
export {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
} from "../channels/plugins/directory-config.js";
|
||||
|
||||
export {
|
||||
resolveAllowlistProviderRuntimeGroupPolicy,
|
||||
resolveDefaultGroupPolicy,
|
||||
} from "../config/runtime-group-policy.js";
|
||||
export {
|
||||
listTelegramDirectoryGroupsFromConfig,
|
||||
listTelegramDirectoryPeersFromConfig,
|
||||
} from "../../extensions/telegram/src/directory-config.js";
|
||||
export {
|
||||
resolveTelegramGroupRequireMention,
|
||||
resolveTelegramGroupToolPolicy,
|
||||
|
||||
@@ -32,11 +32,11 @@ export {
|
||||
resolveWhatsAppConfigAllowFrom,
|
||||
resolveWhatsAppConfigDefaultTo,
|
||||
} from "./channel-config-helpers.js";
|
||||
export { normalizeWhatsAppAllowFromEntries } from "../channels/plugins/normalize/whatsapp.js";
|
||||
export {
|
||||
listWhatsAppDirectoryGroupsFromConfig,
|
||||
listWhatsAppDirectoryPeersFromConfig,
|
||||
} from "../channels/plugins/directory-config.js";
|
||||
export { normalizeWhatsAppAllowFromEntries } from "../channels/plugins/normalize/whatsapp.js";
|
||||
} from "../../extensions/whatsapp/src/directory-config.js";
|
||||
export {
|
||||
collectAllowlistProviderGroupPolicyWarnings,
|
||||
collectOpenGroupPolicyRouteAllowlistWarnings,
|
||||
|
||||
Reference in New Issue
Block a user