157 lines
4.7 KiB
TypeScript
157 lines
4.7 KiB
TypeScript
import type {
|
|
AllowlistMatch,
|
|
GroupPolicy,
|
|
MSTeamsChannelConfig,
|
|
MSTeamsConfig,
|
|
MSTeamsReplyStyle,
|
|
MSTeamsTeamConfig,
|
|
} from "clawdbot/plugin-sdk";
|
|
import {
|
|
buildChannelKeyCandidates,
|
|
normalizeChannelSlug,
|
|
resolveChannelEntryMatchWithFallback,
|
|
resolveNestedAllowlistDecision,
|
|
} from "clawdbot/plugin-sdk";
|
|
|
|
export type MSTeamsResolvedRouteConfig = {
|
|
teamConfig?: MSTeamsTeamConfig;
|
|
channelConfig?: MSTeamsChannelConfig;
|
|
allowlistConfigured: boolean;
|
|
allowed: boolean;
|
|
teamKey?: string;
|
|
channelKey?: string;
|
|
channelMatchKey?: string;
|
|
channelMatchSource?: "direct" | "wildcard";
|
|
};
|
|
|
|
export function resolveMSTeamsRouteConfig(params: {
|
|
cfg?: MSTeamsConfig;
|
|
teamId?: string | null | undefined;
|
|
teamName?: string | null | undefined;
|
|
conversationId?: string | null | undefined;
|
|
channelName?: string | null | undefined;
|
|
}): MSTeamsResolvedRouteConfig {
|
|
const teamId = params.teamId?.trim();
|
|
const teamName = params.teamName?.trim();
|
|
const conversationId = params.conversationId?.trim();
|
|
const channelName = params.channelName?.trim();
|
|
const teams = params.cfg?.teams ?? {};
|
|
const allowlistConfigured = Object.keys(teams).length > 0;
|
|
const teamCandidates = buildChannelKeyCandidates(
|
|
teamId,
|
|
teamName,
|
|
teamName ? normalizeChannelSlug(teamName) : undefined,
|
|
);
|
|
const teamMatch = resolveChannelEntryMatchWithFallback({
|
|
entries: teams,
|
|
keys: teamCandidates,
|
|
wildcardKey: "*",
|
|
normalizeKey: normalizeChannelSlug,
|
|
});
|
|
const teamConfig = teamMatch.entry;
|
|
const channels = teamConfig?.channels ?? {};
|
|
const channelAllowlistConfigured = Object.keys(channels).length > 0;
|
|
const channelCandidates = buildChannelKeyCandidates(
|
|
conversationId,
|
|
channelName,
|
|
channelName ? normalizeChannelSlug(channelName) : undefined,
|
|
);
|
|
const channelMatch = resolveChannelEntryMatchWithFallback({
|
|
entries: channels,
|
|
keys: channelCandidates,
|
|
wildcardKey: "*",
|
|
normalizeKey: normalizeChannelSlug,
|
|
});
|
|
const channelConfig = channelMatch.entry;
|
|
|
|
const allowed = resolveNestedAllowlistDecision({
|
|
outerConfigured: allowlistConfigured,
|
|
outerMatched: Boolean(teamConfig),
|
|
innerConfigured: channelAllowlistConfigured,
|
|
innerMatched: Boolean(channelConfig),
|
|
});
|
|
|
|
return {
|
|
teamConfig,
|
|
channelConfig,
|
|
allowlistConfigured,
|
|
allowed,
|
|
teamKey: teamMatch.matchKey ?? teamMatch.key,
|
|
channelKey: channelMatch.matchKey ?? channelMatch.key,
|
|
channelMatchKey: channelMatch.matchKey,
|
|
channelMatchSource:
|
|
channelMatch.matchSource === "direct" || channelMatch.matchSource === "wildcard"
|
|
? channelMatch.matchSource
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
export type MSTeamsReplyPolicy = {
|
|
requireMention: boolean;
|
|
replyStyle: MSTeamsReplyStyle;
|
|
};
|
|
|
|
export type MSTeamsAllowlistMatch = AllowlistMatch<"wildcard" | "id" | "name">;
|
|
|
|
export function resolveMSTeamsAllowlistMatch(params: {
|
|
allowFrom: Array<string | number>;
|
|
senderId: string;
|
|
senderName?: string | null;
|
|
}): MSTeamsAllowlistMatch {
|
|
const allowFrom = params.allowFrom
|
|
.map((entry) => String(entry).trim().toLowerCase())
|
|
.filter(Boolean);
|
|
if (allowFrom.length === 0) return { allowed: false };
|
|
if (allowFrom.includes("*")) {
|
|
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
}
|
|
const senderId = params.senderId.toLowerCase();
|
|
if (allowFrom.includes(senderId)) {
|
|
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
}
|
|
const senderName = params.senderName?.toLowerCase();
|
|
if (senderName && allowFrom.includes(senderName)) {
|
|
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
|
}
|
|
return { allowed: false };
|
|
}
|
|
|
|
export function resolveMSTeamsReplyPolicy(params: {
|
|
isDirectMessage: boolean;
|
|
globalConfig?: MSTeamsConfig;
|
|
teamConfig?: MSTeamsTeamConfig;
|
|
channelConfig?: MSTeamsChannelConfig;
|
|
}): MSTeamsReplyPolicy {
|
|
if (params.isDirectMessage) {
|
|
return { requireMention: false, replyStyle: "thread" };
|
|
}
|
|
|
|
const requireMention =
|
|
params.channelConfig?.requireMention ??
|
|
params.teamConfig?.requireMention ??
|
|
params.globalConfig?.requireMention ??
|
|
true;
|
|
|
|
const explicitReplyStyle =
|
|
params.channelConfig?.replyStyle ??
|
|
params.teamConfig?.replyStyle ??
|
|
params.globalConfig?.replyStyle;
|
|
|
|
const replyStyle: MSTeamsReplyStyle =
|
|
explicitReplyStyle ?? (requireMention ? "thread" : "top-level");
|
|
|
|
return { requireMention, replyStyle };
|
|
}
|
|
|
|
export function isMSTeamsGroupAllowed(params: {
|
|
groupPolicy: GroupPolicy;
|
|
allowFrom: Array<string | number>;
|
|
senderId: string;
|
|
senderName?: string | null;
|
|
}): boolean {
|
|
const { groupPolicy } = params;
|
|
if (groupPolicy === "disabled") return false;
|
|
if (groupPolicy === "open") return true;
|
|
return resolveMSTeamsAllowlistMatch(params).allowed;
|
|
}
|