openclaw/src/slack/monitor/channel-config.ts
2026-01-21 18:21:19 +00:00

132 lines
4.1 KiB
TypeScript

import type { SlackReactionNotificationMode } from "../../config/config.js";
import type { SlackMessageEvent } from "../types.js";
import {
applyChannelMatchMeta,
buildChannelKeyCandidates,
resolveChannelEntryMatchWithFallback,
type ChannelMatchSource,
} from "../../channels/channel-config.js";
import { allowListMatches, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";
export type SlackChannelConfigResolved = {
allowed: boolean;
requireMention: boolean;
allowBots?: boolean;
users?: Array<string | number>;
skills?: string[];
systemPrompt?: string;
matchKey?: string;
matchSource?: ChannelMatchSource;
};
function firstDefined<T>(...values: Array<T | undefined>) {
for (const value of values) {
if (typeof value !== "undefined") return value;
}
return undefined;
}
export function shouldEmitSlackReactionNotification(params: {
mode: SlackReactionNotificationMode | undefined;
botId?: string | null;
messageAuthorId?: string | null;
userId: string;
userName?: string | null;
allowlist?: Array<string | number> | null;
}) {
const { mode, botId, messageAuthorId, userId, userName, allowlist } = params;
const effectiveMode = mode ?? "own";
if (effectiveMode === "off") return false;
if (effectiveMode === "own") {
if (!botId || !messageAuthorId) return false;
return messageAuthorId === botId;
}
if (effectiveMode === "allowlist") {
if (!Array.isArray(allowlist) || allowlist.length === 0) return false;
const users = normalizeAllowListLower(allowlist);
return allowListMatches({
allowList: users,
id: userId,
name: userName ?? undefined,
});
}
return true;
}
export function resolveSlackChannelLabel(params: { channelId?: string; channelName?: string }) {
const channelName = params.channelName?.trim();
if (channelName) {
const slug = normalizeSlackSlug(channelName);
return `#${slug || channelName}`;
}
const channelId = params.channelId?.trim();
return channelId ? `#${channelId}` : "unknown channel";
}
export function resolveSlackChannelConfig(params: {
channelId: string;
channelName?: string;
channels?: Record<
string,
{
enabled?: boolean;
allow?: boolean;
requireMention?: boolean;
allowBots?: boolean;
users?: Array<string | number>;
skills?: string[];
systemPrompt?: string;
}
>;
defaultRequireMention?: boolean;
}): SlackChannelConfigResolved | null {
const { channelId, channelName, channels, defaultRequireMention } = params;
const entries = channels ?? {};
const keys = Object.keys(entries);
const normalizedName = channelName ? normalizeSlackSlug(channelName) : "";
const directName = channelName ? channelName.trim() : "";
const candidates = buildChannelKeyCandidates(
channelId,
channelName ? `#${directName}` : undefined,
directName,
normalizedName,
);
const match = resolveChannelEntryMatchWithFallback({
entries,
keys: candidates,
wildcardKey: "*",
});
const { entry: matched, wildcardEntry: fallback } = match;
const requireMentionDefault = defaultRequireMention ?? true;
if (keys.length === 0) {
return { allowed: true, requireMention: requireMentionDefault };
}
if (!matched && !fallback) {
return { allowed: false, requireMention: requireMentionDefault };
}
const resolved = matched ?? fallback ?? {};
const allowed =
firstDefined(resolved.enabled, resolved.allow, fallback?.enabled, fallback?.allow, true) ??
true;
const requireMention =
firstDefined(resolved.requireMention, fallback?.requireMention, requireMentionDefault) ??
requireMentionDefault;
const allowBots = firstDefined(resolved.allowBots, fallback?.allowBots);
const users = firstDefined(resolved.users, fallback?.users);
const skills = firstDefined(resolved.skills, fallback?.skills);
const systemPrompt = firstDefined(resolved.systemPrompt, fallback?.systemPrompt);
const result: SlackChannelConfigResolved = {
allowed,
requireMention,
allowBots,
users,
skills,
systemPrompt,
};
return applyChannelMatchMeta(result, match);
}
export type { SlackMessageEvent };