189 lines
5.9 KiB
TypeScript
189 lines
5.9 KiB
TypeScript
import type {
|
|
AllowlistMatch,
|
|
ChannelGroupContext,
|
|
GroupPolicy,
|
|
GroupToolPolicyConfig,
|
|
} from "openclaw/plugin-sdk/nextcloud-talk";
|
|
import {
|
|
buildChannelKeyCandidates,
|
|
evaluateMatchedGroupAccessForPolicy,
|
|
normalizeChannelSlug,
|
|
resolveChannelEntryMatchWithFallback,
|
|
resolveMentionGatingWithBypass,
|
|
resolveNestedAllowlistDecision,
|
|
} from "openclaw/plugin-sdk/nextcloud-talk";
|
|
import type { NextcloudTalkRoomConfig } from "./types.js";
|
|
|
|
function normalizeAllowEntry(raw: string): string {
|
|
return raw
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/^(nextcloud-talk|nc-talk|nc):/i, "");
|
|
}
|
|
|
|
export function normalizeNextcloudTalkAllowlist(
|
|
values: Array<string | number> | undefined,
|
|
): string[] {
|
|
return (values ?? []).map((value) => normalizeAllowEntry(String(value))).filter(Boolean);
|
|
}
|
|
|
|
export function resolveNextcloudTalkAllowlistMatch(params: {
|
|
allowFrom: Array<string | number> | undefined;
|
|
senderId: string;
|
|
}): AllowlistMatch<"wildcard" | "id"> {
|
|
const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
|
|
if (allowFrom.length === 0) {
|
|
return { allowed: false };
|
|
}
|
|
if (allowFrom.includes("*")) {
|
|
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
}
|
|
const senderId = normalizeAllowEntry(params.senderId);
|
|
if (allowFrom.includes(senderId)) {
|
|
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
}
|
|
return { allowed: false };
|
|
}
|
|
|
|
export type NextcloudTalkRoomMatch = {
|
|
roomConfig?: NextcloudTalkRoomConfig;
|
|
wildcardConfig?: NextcloudTalkRoomConfig;
|
|
roomKey?: string;
|
|
matchSource?: "direct" | "parent" | "wildcard";
|
|
allowed: boolean;
|
|
allowlistConfigured: boolean;
|
|
};
|
|
|
|
export function resolveNextcloudTalkRoomMatch(params: {
|
|
rooms?: Record<string, NextcloudTalkRoomConfig>;
|
|
roomToken: string;
|
|
roomName?: string | null;
|
|
}): NextcloudTalkRoomMatch {
|
|
const rooms = params.rooms ?? {};
|
|
const allowlistConfigured = Object.keys(rooms).length > 0;
|
|
const roomName = params.roomName?.trim() || undefined;
|
|
const roomCandidates = buildChannelKeyCandidates(
|
|
params.roomToken,
|
|
roomName,
|
|
roomName ? normalizeChannelSlug(roomName) : undefined,
|
|
);
|
|
const match = resolveChannelEntryMatchWithFallback({
|
|
entries: rooms,
|
|
keys: roomCandidates,
|
|
wildcardKey: "*",
|
|
normalizeKey: normalizeChannelSlug,
|
|
});
|
|
const roomConfig = match.entry;
|
|
const allowed = resolveNestedAllowlistDecision({
|
|
outerConfigured: allowlistConfigured,
|
|
outerMatched: Boolean(roomConfig),
|
|
innerConfigured: false,
|
|
innerMatched: false,
|
|
});
|
|
|
|
return {
|
|
roomConfig,
|
|
wildcardConfig: match.wildcardEntry,
|
|
roomKey: match.matchKey ?? match.key,
|
|
matchSource: match.matchSource,
|
|
allowed,
|
|
allowlistConfigured,
|
|
};
|
|
}
|
|
|
|
export function resolveNextcloudTalkGroupToolPolicy(
|
|
params: ChannelGroupContext,
|
|
): GroupToolPolicyConfig | undefined {
|
|
const cfg = params.cfg as {
|
|
channels?: { "nextcloud-talk"?: { rooms?: Record<string, NextcloudTalkRoomConfig> } };
|
|
};
|
|
const roomToken = params.groupId?.trim();
|
|
if (!roomToken) {
|
|
return undefined;
|
|
}
|
|
const roomName = params.groupChannel?.trim() || undefined;
|
|
const match = resolveNextcloudTalkRoomMatch({
|
|
rooms: cfg.channels?.["nextcloud-talk"]?.rooms,
|
|
roomToken,
|
|
roomName,
|
|
});
|
|
return match.roomConfig?.tools ?? match.wildcardConfig?.tools;
|
|
}
|
|
|
|
export function resolveNextcloudTalkRequireMention(params: {
|
|
roomConfig?: NextcloudTalkRoomConfig;
|
|
wildcardConfig?: NextcloudTalkRoomConfig;
|
|
}): boolean {
|
|
if (typeof params.roomConfig?.requireMention === "boolean") {
|
|
return params.roomConfig.requireMention;
|
|
}
|
|
if (typeof params.wildcardConfig?.requireMention === "boolean") {
|
|
return params.wildcardConfig.requireMention;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function resolveNextcloudTalkGroupAllow(params: {
|
|
groupPolicy: GroupPolicy;
|
|
outerAllowFrom: Array<string | number> | undefined;
|
|
innerAllowFrom: Array<string | number> | undefined;
|
|
senderId: string;
|
|
}): { allowed: boolean; outerMatch: AllowlistMatch; innerMatch: AllowlistMatch } {
|
|
const outerAllow = normalizeNextcloudTalkAllowlist(params.outerAllowFrom);
|
|
const innerAllow = normalizeNextcloudTalkAllowlist(params.innerAllowFrom);
|
|
const outerMatch = resolveNextcloudTalkAllowlistMatch({
|
|
allowFrom: params.outerAllowFrom,
|
|
senderId: params.senderId,
|
|
});
|
|
const innerMatch = resolveNextcloudTalkAllowlistMatch({
|
|
allowFrom: params.innerAllowFrom,
|
|
senderId: params.senderId,
|
|
});
|
|
const access = evaluateMatchedGroupAccessForPolicy({
|
|
groupPolicy: params.groupPolicy,
|
|
allowlistConfigured: outerAllow.length > 0 || innerAllow.length > 0,
|
|
allowlistMatched: resolveNestedAllowlistDecision({
|
|
outerConfigured: outerAllow.length > 0 || innerAllow.length > 0,
|
|
outerMatched: outerAllow.length > 0 ? outerMatch.allowed : true,
|
|
innerConfigured: innerAllow.length > 0,
|
|
innerMatched: innerMatch.allowed,
|
|
}),
|
|
});
|
|
|
|
return {
|
|
allowed: access.allowed,
|
|
outerMatch:
|
|
params.groupPolicy === "open"
|
|
? { allowed: true }
|
|
: params.groupPolicy === "disabled"
|
|
? { allowed: false }
|
|
: outerMatch,
|
|
innerMatch:
|
|
params.groupPolicy === "open"
|
|
? { allowed: true }
|
|
: params.groupPolicy === "disabled"
|
|
? { allowed: false }
|
|
: innerMatch,
|
|
};
|
|
}
|
|
|
|
export function resolveNextcloudTalkMentionGate(params: {
|
|
isGroup: boolean;
|
|
requireMention: boolean;
|
|
wasMentioned: boolean;
|
|
allowTextCommands: boolean;
|
|
hasControlCommand: boolean;
|
|
commandAuthorized: boolean;
|
|
}): { shouldSkip: boolean; shouldBypassMention: boolean } {
|
|
const result = resolveMentionGatingWithBypass({
|
|
isGroup: params.isGroup,
|
|
requireMention: params.requireMention,
|
|
canDetectMention: true,
|
|
wasMentioned: params.wasMentioned,
|
|
allowTextCommands: params.allowTextCommands,
|
|
hasControlCommand: params.hasControlCommand,
|
|
commandAuthorized: params.commandAuthorized,
|
|
});
|
|
return { shouldSkip: result.shouldSkip, shouldBypassMention: result.shouldBypassMention };
|
|
}
|