Files
Moltbot/src/channels/plugins/group-mentions.ts
2026-02-17 13:36:48 +09:00

413 lines
12 KiB
TypeScript

import type { OpenClawConfig } from "../../config/config.js";
import {
resolveChannelGroupRequireMention,
resolveChannelGroupToolsPolicy,
resolveToolsBySender,
} from "../../config/group-policy.js";
import type { DiscordConfig } from "../../config/types.js";
import type {
GroupToolPolicyBySenderConfig,
GroupToolPolicyConfig,
} from "../../config/types.tools.js";
import { resolveSlackAccount } from "../../slack/accounts.js";
type GroupMentionParams = {
cfg: OpenClawConfig;
groupId?: string | null;
groupChannel?: string | null;
groupSpace?: string | null;
accountId?: string | null;
senderId?: string | null;
senderName?: string | null;
senderUsername?: string | null;
senderE164?: string | null;
};
function normalizeDiscordSlug(value?: string | null) {
if (!value) {
return "";
}
let text = value.trim().toLowerCase();
if (!text) {
return "";
}
text = text.replace(/^[@#]+/, "");
text = text.replace(/[\s_]+/g, "-");
text = text.replace(/[^a-z0-9-]+/g, "-");
text = text.replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
return text;
}
function normalizeSlackSlug(raw?: string | null) {
const trimmed = raw?.trim().toLowerCase() ?? "";
if (!trimmed) {
return "";
}
const dashed = trimmed.replace(/\s+/g, "-");
const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-");
return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "");
}
function parseTelegramGroupId(value?: string | null) {
const raw = value?.trim() ?? "";
if (!raw) {
return { chatId: undefined, topicId: undefined };
}
const parts = raw.split(":").filter(Boolean);
if (
parts.length >= 3 &&
parts[1] === "topic" &&
/^-?\d+$/.test(parts[0]) &&
/^\d+$/.test(parts[2])
) {
return { chatId: parts[0], topicId: parts[2] };
}
if (parts.length >= 2 && /^-?\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) {
return { chatId: parts[0], topicId: parts[1] };
}
return { chatId: raw, topicId: undefined };
}
function resolveTelegramRequireMention(params: {
cfg: OpenClawConfig;
chatId?: string;
topicId?: string;
}): boolean | undefined {
const { cfg, chatId, topicId } = params;
if (!chatId) {
return undefined;
}
const groupConfig = cfg.channels?.telegram?.groups?.[chatId];
const groupDefault = cfg.channels?.telegram?.groups?.["*"];
const topicConfig = topicId && groupConfig?.topics ? groupConfig.topics[topicId] : undefined;
const defaultTopicConfig =
topicId && groupDefault?.topics ? groupDefault.topics[topicId] : undefined;
if (typeof topicConfig?.requireMention === "boolean") {
return topicConfig.requireMention;
}
if (typeof defaultTopicConfig?.requireMention === "boolean") {
return defaultTopicConfig.requireMention;
}
if (typeof groupConfig?.requireMention === "boolean") {
return groupConfig.requireMention;
}
if (typeof groupDefault?.requireMention === "boolean") {
return groupDefault.requireMention;
}
return undefined;
}
function resolveDiscordGuildEntry(guilds: DiscordConfig["guilds"], groupSpace?: string | null) {
if (!guilds || Object.keys(guilds).length === 0) {
return null;
}
const space = groupSpace?.trim() ?? "";
if (space && guilds[space]) {
return guilds[space];
}
const normalized = normalizeDiscordSlug(space);
if (normalized && guilds[normalized]) {
return guilds[normalized];
}
if (normalized) {
const match = Object.values(guilds).find(
(entry) => normalizeDiscordSlug(entry?.slug ?? undefined) === normalized,
);
if (match) {
return match;
}
}
return guilds["*"] ?? null;
}
function resolveDiscordChannelEntry<TEntry>(
channelEntries: Record<string, TEntry> | undefined,
params: { groupId?: string | null; groupChannel?: string | null },
): TEntry | undefined {
if (!channelEntries || Object.keys(channelEntries).length === 0) {
return undefined;
}
const groupChannel = params.groupChannel;
const channelSlug = normalizeDiscordSlug(groupChannel);
return (
(params.groupId ? channelEntries[params.groupId] : undefined) ??
(channelSlug
? (channelEntries[channelSlug] ?? channelEntries[`#${channelSlug}`])
: undefined) ??
(groupChannel ? channelEntries[normalizeDiscordSlug(groupChannel)] : undefined)
);
}
export function resolveTelegramGroupRequireMention(
params: GroupMentionParams,
): boolean | undefined {
const { chatId, topicId } = parseTelegramGroupId(params.groupId);
const requireMention = resolveTelegramRequireMention({
cfg: params.cfg,
chatId,
topicId,
});
if (typeof requireMention === "boolean") {
return requireMention;
}
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "telegram",
groupId: chatId ?? params.groupId,
accountId: params.accountId,
});
}
export function resolveWhatsAppGroupRequireMention(params: GroupMentionParams): boolean {
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "whatsapp",
groupId: params.groupId,
accountId: params.accountId,
});
}
export function resolveIMessageGroupRequireMention(params: GroupMentionParams): boolean {
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "imessage",
groupId: params.groupId,
accountId: params.accountId,
});
}
export function resolveDiscordGroupRequireMention(params: GroupMentionParams): boolean {
const guildEntry = resolveDiscordGuildEntry(
params.cfg.channels?.discord?.guilds,
params.groupSpace,
);
const channelEntries = guildEntry?.channels;
if (channelEntries && Object.keys(channelEntries).length > 0) {
const entry = resolveDiscordChannelEntry(channelEntries, params);
if (entry && typeof entry.requireMention === "boolean") {
return entry.requireMention;
}
}
if (typeof guildEntry?.requireMention === "boolean") {
return guildEntry.requireMention;
}
return true;
}
export function resolveGoogleChatGroupRequireMention(params: GroupMentionParams): boolean {
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "googlechat",
groupId: params.groupId,
accountId: params.accountId,
});
}
export function resolveGoogleChatGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "googlechat",
groupId: params.groupId,
accountId: params.accountId,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
}
export function resolveSlackGroupRequireMention(params: GroupMentionParams): boolean {
const account = resolveSlackAccount({
cfg: params.cfg,
accountId: params.accountId,
});
const channels = account.channels ?? {};
const keys = Object.keys(channels);
if (keys.length === 0) {
return true;
}
const channelId = params.groupId?.trim();
const groupChannel = params.groupChannel;
const channelName = groupChannel?.replace(/^#/, "");
const normalizedName = normalizeSlackSlug(channelName);
const candidates = [
channelId ?? "",
channelName ? `#${channelName}` : "",
channelName ?? "",
normalizedName,
].filter(Boolean);
let matched: { requireMention?: boolean } | undefined;
for (const candidate of candidates) {
if (candidate && channels[candidate]) {
matched = channels[candidate];
break;
}
}
const fallback = channels["*"];
const resolved = matched ?? fallback;
if (typeof resolved?.requireMention === "boolean") {
return resolved.requireMention;
}
return true;
}
export function resolveBlueBubblesGroupRequireMention(params: GroupMentionParams): boolean {
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "bluebubbles",
groupId: params.groupId,
accountId: params.accountId,
});
}
export function resolveTelegramGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
const { chatId } = parseTelegramGroupId(params.groupId);
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "telegram",
groupId: chatId ?? params.groupId,
accountId: params.accountId,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
}
export function resolveWhatsAppGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "whatsapp",
groupId: params.groupId,
accountId: params.accountId,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
}
export function resolveIMessageGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "imessage",
groupId: params.groupId,
accountId: params.accountId,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
}
export function resolveDiscordGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
const guildEntry = resolveDiscordGuildEntry(
params.cfg.channels?.discord?.guilds,
params.groupSpace,
);
const channelEntries = guildEntry?.channels;
if (channelEntries && Object.keys(channelEntries).length > 0) {
const entry = resolveDiscordChannelEntry(channelEntries, params);
const senderPolicy = resolveToolsBySender({
toolsBySender: entry?.toolsBySender,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
if (senderPolicy) {
return senderPolicy;
}
if (entry?.tools) {
return entry.tools;
}
}
const guildSenderPolicy = resolveToolsBySender({
toolsBySender: guildEntry?.toolsBySender,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
if (guildSenderPolicy) {
return guildSenderPolicy;
}
if (guildEntry?.tools) {
return guildEntry.tools;
}
return undefined;
}
export function resolveSlackGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
const account = resolveSlackAccount({
cfg: params.cfg,
accountId: params.accountId,
});
const channels = account.channels ?? {};
const keys = Object.keys(channels);
if (keys.length === 0) {
return undefined;
}
const channelId = params.groupId?.trim();
const groupChannel = params.groupChannel;
const channelName = groupChannel?.replace(/^#/, "");
const normalizedName = normalizeSlackSlug(channelName);
const candidates = [
channelId ?? "",
channelName ? `#${channelName}` : "",
channelName ?? "",
normalizedName,
].filter(Boolean);
let matched:
| { tools?: GroupToolPolicyConfig; toolsBySender?: GroupToolPolicyBySenderConfig }
| undefined;
for (const candidate of candidates) {
if (candidate && channels[candidate]) {
matched = channels[candidate];
break;
}
}
const resolved = matched ?? channels["*"];
const senderPolicy = resolveToolsBySender({
toolsBySender: resolved?.toolsBySender,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
if (senderPolicy) {
return senderPolicy;
}
if (resolved?.tools) {
return resolved.tools;
}
return undefined;
}
export function resolveBlueBubblesGroupToolPolicy(
params: GroupMentionParams,
): GroupToolPolicyConfig | undefined {
return resolveChannelGroupToolsPolicy({
cfg: params.cfg,
channel: "bluebubbles",
groupId: params.groupId,
accountId: params.accountId,
senderId: params.senderId,
senderName: params.senderName,
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
}