import type { AllowlistMatch, GroupPolicy } from "clawdbot/plugin-sdk"; import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback, resolveMentionGatingWithBypass, resolveNestedAllowlistDecision, } from "clawdbot/plugin-sdk"; 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 | undefined, ): string[] { return (values ?? []).map((value) => normalizeAllowEntry(String(value))).filter(Boolean); } export function resolveNextcloudTalkAllowlistMatch(params: { allowFrom: Array | undefined; senderId: string; senderName?: string | null; }): AllowlistMatch<"wildcard" | "id" | "name"> { 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" }; } const senderName = params.senderName ? normalizeAllowEntry(params.senderName) : ""; if (senderName && allowFrom.includes(senderName)) { return { allowed: true, matchKey: senderName, matchSource: "name" }; } 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; 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 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 | undefined; innerAllowFrom: Array | undefined; senderId: string; senderName?: string | null; }): { allowed: boolean; outerMatch: AllowlistMatch; innerMatch: AllowlistMatch } { if (params.groupPolicy === "disabled") { return { allowed: false, outerMatch: { allowed: false }, innerMatch: { allowed: false } }; } if (params.groupPolicy === "open") { return { allowed: true, outerMatch: { allowed: true }, innerMatch: { allowed: true } }; } const outerAllow = normalizeNextcloudTalkAllowlist(params.outerAllowFrom); const innerAllow = normalizeNextcloudTalkAllowlist(params.innerAllowFrom); if (outerAllow.length === 0 && innerAllow.length === 0) { return { allowed: false, outerMatch: { allowed: false }, innerMatch: { allowed: false } }; } const outerMatch = resolveNextcloudTalkAllowlistMatch({ allowFrom: params.outerAllowFrom, senderId: params.senderId, senderName: params.senderName, }); const innerMatch = resolveNextcloudTalkAllowlistMatch({ allowFrom: params.innerAllowFrom, senderId: params.senderId, senderName: params.senderName, }); const allowed = resolveNestedAllowlistDecision({ outerConfigured: outerAllow.length > 0 || innerAllow.length > 0, outerMatched: outerAllow.length > 0 ? outerMatch.allowed : true, innerConfigured: innerAllow.length > 0, innerMatched: innerMatch.allowed, }); return { allowed, outerMatch, 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 }; }