@openclaw/nextcloud-talk 2026.5.2 → 2026.5.3-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/api.js +2 -0
  2. package/dist/channel-BVVRsVr5.js +1788 -0
  3. package/dist/channel-plugin-api.js +2 -0
  4. package/dist/contract-api.js +2 -0
  5. package/dist/doctor-contract-CYlB-4Bf.js +7 -0
  6. package/dist/doctor-contract-api.js +2 -0
  7. package/dist/index.js +22 -0
  8. package/dist/runtime-api-BcCzeRN9.js +15 -0
  9. package/dist/runtime-api.js +2 -0
  10. package/dist/secret-contract-api.js +2 -0
  11. package/dist/secret-contract-bczDw2-2.js +86 -0
  12. package/dist/setup-entry.js +15 -0
  13. package/package.json +14 -6
  14. package/api.ts +0 -1
  15. package/channel-plugin-api.ts +0 -1
  16. package/contract-api.ts +0 -4
  17. package/doctor-contract-api.ts +0 -1
  18. package/index.ts +0 -20
  19. package/runtime-api.ts +0 -33
  20. package/secret-contract-api.ts +0 -5
  21. package/setup-entry.ts +0 -13
  22. package/src/accounts.ts +0 -139
  23. package/src/approval-auth.test.ts +0 -17
  24. package/src/approval-auth.ts +0 -27
  25. package/src/channel-api.ts +0 -5
  26. package/src/channel.adapters.ts +0 -52
  27. package/src/channel.core.test.ts +0 -75
  28. package/src/channel.lifecycle.test.ts +0 -81
  29. package/src/channel.ts +0 -195
  30. package/src/config-schema.ts +0 -79
  31. package/src/core.test.ts +0 -397
  32. package/src/doctor-contract.ts +0 -9
  33. package/src/doctor.test.ts +0 -40
  34. package/src/doctor.ts +0 -10
  35. package/src/gateway.ts +0 -109
  36. package/src/inbound.authz.test.ts +0 -149
  37. package/src/inbound.behavior.test.ts +0 -202
  38. package/src/inbound.ts +0 -320
  39. package/src/monitor-runtime.ts +0 -138
  40. package/src/monitor.replay.test.ts +0 -279
  41. package/src/monitor.test-fixtures.ts +0 -30
  42. package/src/monitor.test-harness.ts +0 -59
  43. package/src/monitor.ts +0 -385
  44. package/src/normalize.ts +0 -44
  45. package/src/policy.ts +0 -180
  46. package/src/replay-guard.ts +0 -128
  47. package/src/room-info.test.ts +0 -116
  48. package/src/room-info.ts +0 -148
  49. package/src/runtime.ts +0 -9
  50. package/src/secret-contract.ts +0 -103
  51. package/src/secret-input.ts +0 -4
  52. package/src/send.cfg-threading.test.ts +0 -153
  53. package/src/send.runtime.ts +0 -8
  54. package/src/send.ts +0 -236
  55. package/src/session-route.ts +0 -40
  56. package/src/setup-core.ts +0 -248
  57. package/src/setup-surface.ts +0 -190
  58. package/src/setup.test.ts +0 -422
  59. package/src/signature.ts +0 -82
  60. package/src/types.ts +0 -193
  61. package/tsconfig.json +0 -16
package/src/monitor.ts DELETED
@@ -1,385 +0,0 @@
1
- import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
- import { safeParseJsonWithSchema } from "openclaw/plugin-sdk/extension-shared";
3
- import {
4
- WEBHOOK_RATE_LIMIT_DEFAULTS,
5
- createAuthRateLimiter,
6
- isRequestBodyLimitError,
7
- readRequestBodyWithLimit,
8
- requestBodyErrorToText,
9
- } from "openclaw/plugin-sdk/webhook-ingress";
10
- import { z } from "zod";
11
- import type { NextcloudTalkReplayGuard } from "./replay-guard.js";
12
- import { extractNextcloudTalkHeaders, verifyNextcloudTalkSignature } from "./signature.js";
13
- import type {
14
- NextcloudTalkInboundMessage,
15
- NextcloudTalkWebhookHeaders,
16
- NextcloudTalkWebhookPayload,
17
- NextcloudTalkWebhookServerOptions,
18
- } from "./types.js";
19
-
20
- const DEFAULT_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
21
- const PREAUTH_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
22
- const PREAUTH_WEBHOOK_BODY_TIMEOUT_MS = 5_000;
23
- const HEALTH_PATH = "/healthz";
24
- const WEBHOOK_AUTH_RATE_LIMIT_SCOPE = "nextcloud-talk-webhook-auth";
25
- const NextcloudTalkWebhookPayloadSchema: z.ZodType<NextcloudTalkWebhookPayload> = z.object({
26
- type: z.enum(["Create", "Update", "Delete"]),
27
- actor: z.object({
28
- type: z.literal("Person"),
29
- id: z.string().min(1),
30
- name: z.string(),
31
- }),
32
- object: z.object({
33
- type: z.literal("Note"),
34
- id: z.string().min(1),
35
- name: z.string(),
36
- content: z.string(),
37
- mediaType: z.string(),
38
- }),
39
- target: z.object({
40
- type: z.literal("Collection"),
41
- id: z.string().min(1),
42
- name: z.string(),
43
- }),
44
- });
45
- const WEBHOOK_ERRORS = {
46
- missingSignatureHeaders: "Missing signature headers",
47
- invalidBackend: "Invalid backend",
48
- invalidSignature: "Invalid signature",
49
- invalidPayloadFormat: "Invalid payload format",
50
- payloadTooLarge: "Payload too large",
51
- internalServerError: "Internal server error",
52
- } as const;
53
-
54
- export class NextcloudTalkRetryableWebhookError extends Error {
55
- constructor(message: string, options?: ErrorOptions) {
56
- super(message, options);
57
- this.name = "NextcloudTalkRetryableWebhookError";
58
- }
59
- }
60
-
61
- export async function processNextcloudTalkReplayGuardedMessage(params: {
62
- replayGuard: NextcloudTalkReplayGuard;
63
- accountId: string;
64
- message: NextcloudTalkInboundMessage;
65
- handleMessage: () => Promise<void>;
66
- }): Promise<"processed" | "duplicate"> {
67
- const claim = await params.replayGuard.claimMessage({
68
- accountId: params.accountId,
69
- roomToken: params.message.roomToken,
70
- messageId: params.message.messageId,
71
- });
72
- if (claim !== "claimed") {
73
- return "duplicate";
74
- }
75
-
76
- try {
77
- await params.handleMessage();
78
- await params.replayGuard.commitMessage({
79
- accountId: params.accountId,
80
- roomToken: params.message.roomToken,
81
- messageId: params.message.messageId,
82
- });
83
- return "processed";
84
- } catch (error) {
85
- if (error instanceof NextcloudTalkRetryableWebhookError) {
86
- params.replayGuard.releaseMessage({
87
- accountId: params.accountId,
88
- roomToken: params.message.roomToken,
89
- messageId: params.message.messageId,
90
- error,
91
- });
92
- } else {
93
- // Generic failures are treated as non-retryable because the handler may already
94
- // have produced a visible side effect, and replaying the webhook would duplicate it.
95
- await params.replayGuard.commitMessage({
96
- accountId: params.accountId,
97
- roomToken: params.message.roomToken,
98
- messageId: params.message.messageId,
99
- });
100
- }
101
- throw error;
102
- }
103
- }
104
-
105
- function formatError(err: unknown): string {
106
- if (err instanceof Error) {
107
- return err.message;
108
- }
109
- return typeof err === "string" ? err : JSON.stringify(err);
110
- }
111
-
112
- function parseWebhookPayload(body: string): NextcloudTalkWebhookPayload | null {
113
- return safeParseJsonWithSchema(NextcloudTalkWebhookPayloadSchema, body);
114
- }
115
-
116
- function writeJsonResponse(
117
- res: ServerResponse,
118
- status: number,
119
- body?: Record<string, unknown>,
120
- ): void {
121
- if (body) {
122
- res.writeHead(status, { "Content-Type": "application/json" });
123
- res.end(JSON.stringify(body));
124
- return;
125
- }
126
- res.writeHead(status);
127
- res.end();
128
- }
129
-
130
- function writeWebhookError(res: ServerResponse, status: number, error: string): void {
131
- if (res.headersSent) {
132
- return;
133
- }
134
- writeJsonResponse(res, status, { error });
135
- }
136
-
137
- function validateWebhookHeaders(params: {
138
- req: IncomingMessage;
139
- res: ServerResponse;
140
- isBackendAllowed?: (backend: string) => boolean;
141
- }): NextcloudTalkWebhookHeaders | null {
142
- const headers = extractNextcloudTalkHeaders(
143
- params.req.headers as Record<string, string | string[] | undefined>,
144
- );
145
- if (!headers) {
146
- writeWebhookError(params.res, 400, WEBHOOK_ERRORS.missingSignatureHeaders);
147
- return null;
148
- }
149
- if (params.isBackendAllowed && !params.isBackendAllowed(headers.backend)) {
150
- writeWebhookError(params.res, 401, WEBHOOK_ERRORS.invalidBackend);
151
- return null;
152
- }
153
- return headers;
154
- }
155
-
156
- function verifyWebhookSignature(params: {
157
- headers: NextcloudTalkWebhookHeaders;
158
- body: string;
159
- secret: string;
160
- res: ServerResponse;
161
- clientIp: string;
162
- authRateLimiter: ReturnType<typeof createAuthRateLimiter>;
163
- }): boolean {
164
- const isValid = verifyNextcloudTalkSignature({
165
- signature: params.headers.signature,
166
- random: params.headers.random,
167
- body: params.body,
168
- secret: params.secret,
169
- });
170
- if (!isValid) {
171
- params.authRateLimiter.recordFailure(params.clientIp, WEBHOOK_AUTH_RATE_LIMIT_SCOPE);
172
- writeWebhookError(params.res, 401, WEBHOOK_ERRORS.invalidSignature);
173
- return false;
174
- }
175
- params.authRateLimiter.reset(params.clientIp, WEBHOOK_AUTH_RATE_LIMIT_SCOPE);
176
- return true;
177
- }
178
-
179
- function decodeWebhookCreateMessage(params: {
180
- body: string;
181
- res: ServerResponse;
182
- }):
183
- | { kind: "message"; message: NextcloudTalkInboundMessage }
184
- | { kind: "ignore" }
185
- | { kind: "invalid" } {
186
- const payload = parseWebhookPayload(params.body);
187
- if (!payload) {
188
- writeWebhookError(params.res, 400, WEBHOOK_ERRORS.invalidPayloadFormat);
189
- return { kind: "invalid" };
190
- }
191
- if (payload.type !== "Create") {
192
- return { kind: "ignore" };
193
- }
194
- return { kind: "message", message: payloadToInboundMessage(payload) };
195
- }
196
-
197
- function payloadToInboundMessage(
198
- payload: NextcloudTalkWebhookPayload,
199
- ): NextcloudTalkInboundMessage {
200
- // Payload doesn't indicate DM vs room; mark as group and let inbound handler refine.
201
- const isGroupChat = true;
202
-
203
- return {
204
- messageId: payload.object.id,
205
- roomToken: payload.target.id,
206
- roomName: payload.target.name,
207
- senderId: payload.actor.id,
208
- senderName: payload.actor.name ?? "",
209
- text: payload.object.content || payload.object.name || "",
210
- mediaType: payload.object.mediaType || "text/plain",
211
- timestamp: Date.now(),
212
- isGroupChat,
213
- };
214
- }
215
-
216
- export function readNextcloudTalkWebhookBody(
217
- req: IncomingMessage,
218
- maxBodyBytes: number,
219
- ): Promise<string> {
220
- return readRequestBodyWithLimit(req, {
221
- // This read happens before signature verification, so keep the unauthenticated
222
- // body budget bounded even if the operator-configured post-parse limit is larger.
223
- maxBytes: Math.min(maxBodyBytes, PREAUTH_WEBHOOK_MAX_BODY_BYTES),
224
- timeoutMs: PREAUTH_WEBHOOK_BODY_TIMEOUT_MS,
225
- });
226
- }
227
-
228
- export function createNextcloudTalkWebhookServer(opts: NextcloudTalkWebhookServerOptions): {
229
- server: Server;
230
- start: () => Promise<void>;
231
- stop: () => void;
232
- } {
233
- const { port, host, path, secret, onMessage, onError, abortSignal } = opts;
234
- const maxBodyBytes =
235
- typeof opts.maxBodyBytes === "number" &&
236
- Number.isFinite(opts.maxBodyBytes) &&
237
- opts.maxBodyBytes > 0
238
- ? Math.floor(opts.maxBodyBytes)
239
- : DEFAULT_WEBHOOK_MAX_BODY_BYTES;
240
- const readBody = opts.readBody ?? readNextcloudTalkWebhookBody;
241
- const isBackendAllowed = opts.isBackendAllowed;
242
- const shouldProcessMessage = opts.shouldProcessMessage;
243
- const processMessage = opts.processMessage;
244
- const authRateLimitMaxRequests =
245
- typeof opts.authRateLimit?.maxRequests === "number"
246
- ? opts.authRateLimit.maxRequests
247
- : WEBHOOK_RATE_LIMIT_DEFAULTS.maxRequests;
248
- const authRateLimitWindowMs =
249
- typeof opts.authRateLimit?.windowMs === "number"
250
- ? opts.authRateLimit.windowMs
251
- : WEBHOOK_RATE_LIMIT_DEFAULTS.windowMs;
252
- const webhookAuthRateLimiter = createAuthRateLimiter({
253
- maxAttempts: authRateLimitMaxRequests,
254
- windowMs: authRateLimitWindowMs,
255
- lockoutMs: authRateLimitWindowMs,
256
- exemptLoopback: false,
257
- pruneIntervalMs: authRateLimitWindowMs,
258
- });
259
-
260
- const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
261
- if (req.url === HEALTH_PATH) {
262
- res.writeHead(200, { "Content-Type": "text/plain" });
263
- res.end("ok");
264
- return;
265
- }
266
-
267
- if (req.url !== path || req.method !== "POST") {
268
- res.writeHead(404);
269
- res.end();
270
- return;
271
- }
272
-
273
- const clientIp = req.socket.remoteAddress ?? "unknown";
274
- if (!webhookAuthRateLimiter.check(clientIp, WEBHOOK_AUTH_RATE_LIMIT_SCOPE).allowed) {
275
- res.writeHead(429);
276
- res.end("Too Many Requests");
277
- return;
278
- }
279
-
280
- try {
281
- const headers = validateWebhookHeaders({
282
- req,
283
- res,
284
- isBackendAllowed,
285
- });
286
- if (!headers) {
287
- return;
288
- }
289
-
290
- const body = await readBody(req, maxBodyBytes);
291
-
292
- const hasValidSignature = verifyWebhookSignature({
293
- headers,
294
- body,
295
- secret,
296
- res,
297
- clientIp,
298
- authRateLimiter: webhookAuthRateLimiter,
299
- });
300
- if (!hasValidSignature) {
301
- return;
302
- }
303
-
304
- const decoded = decodeWebhookCreateMessage({
305
- body,
306
- res,
307
- });
308
- if (decoded.kind === "invalid") {
309
- return;
310
- }
311
- if (decoded.kind === "ignore") {
312
- writeJsonResponse(res, 200);
313
- return;
314
- }
315
-
316
- const message = decoded.message;
317
- if (processMessage) {
318
- writeJsonResponse(res, 200);
319
- try {
320
- await processMessage(message);
321
- } catch (err) {
322
- onError?.(err instanceof Error ? err : new Error(formatError(err)));
323
- }
324
- return;
325
- }
326
-
327
- if (shouldProcessMessage) {
328
- const shouldProcess = await shouldProcessMessage(message);
329
- if (!shouldProcess) {
330
- writeJsonResponse(res, 200);
331
- return;
332
- }
333
- }
334
-
335
- writeJsonResponse(res, 200);
336
-
337
- try {
338
- await onMessage(message);
339
- } catch (err) {
340
- onError?.(err instanceof Error ? err : new Error(formatError(err)));
341
- }
342
- } catch (err) {
343
- if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
344
- writeWebhookError(res, 413, WEBHOOK_ERRORS.payloadTooLarge);
345
- return;
346
- }
347
- if (isRequestBodyLimitError(err, "REQUEST_BODY_TIMEOUT")) {
348
- writeWebhookError(res, 408, requestBodyErrorToText("REQUEST_BODY_TIMEOUT"));
349
- return;
350
- }
351
- const error = err instanceof Error ? err : new Error(formatError(err));
352
- onError?.(error);
353
- writeWebhookError(res, 500, WEBHOOK_ERRORS.internalServerError);
354
- }
355
- });
356
-
357
- const start = (): Promise<void> => {
358
- return new Promise((resolve) => {
359
- server.listen(port, host, () => resolve());
360
- });
361
- };
362
-
363
- let stopped = false;
364
- const stop = () => {
365
- if (stopped) {
366
- return;
367
- }
368
- stopped = true;
369
- try {
370
- server.close();
371
- } catch {
372
- // ignore close races while shutting down
373
- }
374
- };
375
-
376
- if (abortSignal) {
377
- if (abortSignal.aborted) {
378
- stop();
379
- } else {
380
- abortSignal.addEventListener("abort", stop, { once: true });
381
- }
382
- }
383
-
384
- return { server, start, stop };
385
- }
package/src/normalize.ts DELETED
@@ -1,44 +0,0 @@
1
- export function stripNextcloudTalkTargetPrefix(raw: string): string | undefined {
2
- const trimmed = raw.trim();
3
- if (!trimmed) {
4
- return undefined;
5
- }
6
-
7
- let normalized = trimmed;
8
-
9
- if (normalized.startsWith("nextcloud-talk:")) {
10
- normalized = normalized.slice("nextcloud-talk:".length).trim();
11
- } else if (normalized.startsWith("nc-talk:")) {
12
- normalized = normalized.slice("nc-talk:".length).trim();
13
- } else if (normalized.startsWith("nc:")) {
14
- normalized = normalized.slice("nc:".length).trim();
15
- }
16
-
17
- if (normalized.startsWith("room:")) {
18
- normalized = normalized.slice("room:".length).trim();
19
- }
20
-
21
- if (!normalized) {
22
- return undefined;
23
- }
24
-
25
- return normalized;
26
- }
27
-
28
- export function normalizeNextcloudTalkMessagingTarget(raw: string): string | undefined {
29
- const normalized = stripNextcloudTalkTargetPrefix(raw);
30
- return normalized ? `nextcloud-talk:${normalized}`.toLowerCase() : undefined;
31
- }
32
-
33
- export function looksLikeNextcloudTalkTargetId(raw: string): boolean {
34
- const trimmed = raw.trim();
35
- if (!trimmed) {
36
- return false;
37
- }
38
-
39
- if (/^(nextcloud-talk|nc-talk|nc):/i.test(trimmed)) {
40
- return true;
41
- }
42
-
43
- return /^[a-z0-9]{8,}$/i.test(trimmed);
44
- }
package/src/policy.ts DELETED
@@ -1,180 +0,0 @@
1
- import {
2
- buildChannelKeyCandidates,
3
- normalizeChannelSlug,
4
- resolveChannelEntryMatchWithFallback,
5
- resolveNestedAllowlistDecision,
6
- } from "openclaw/plugin-sdk/channel-targets";
7
- import { evaluateMatchedGroupAccessForPolicy } from "openclaw/plugin-sdk/group-access";
8
- import type {
9
- AllowlistMatch,
10
- ChannelGroupContext,
11
- GroupPolicy,
12
- GroupToolPolicyConfig,
13
- } from "../runtime-api.js";
14
- import type { NextcloudTalkRoomConfig } from "./types.js";
15
-
16
- function normalizeAllowEntry(raw: string): string {
17
- return raw
18
- .trim()
19
- .replace(/^(nextcloud-talk|nc-talk|nc):/i, "")
20
- .toLowerCase();
21
- }
22
-
23
- export function normalizeNextcloudTalkAllowlist(
24
- values: Array<string | number> | undefined,
25
- ): string[] {
26
- return (values ?? []).map((value) => normalizeAllowEntry(String(value))).filter(Boolean);
27
- }
28
-
29
- export function resolveNextcloudTalkAllowlistMatch(params: {
30
- allowFrom: Array<string | number> | undefined;
31
- senderId: string;
32
- }): AllowlistMatch<"wildcard" | "id"> {
33
- const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
34
- if (allowFrom.length === 0) {
35
- return { allowed: false };
36
- }
37
- if (allowFrom.includes("*")) {
38
- return { allowed: true, matchKey: "*", matchSource: "wildcard" };
39
- }
40
- const senderId = normalizeAllowEntry(params.senderId);
41
- if (allowFrom.includes(senderId)) {
42
- return { allowed: true, matchKey: senderId, matchSource: "id" };
43
- }
44
- return { allowed: false };
45
- }
46
-
47
- type NextcloudTalkRoomMatch = {
48
- roomConfig?: NextcloudTalkRoomConfig;
49
- wildcardConfig?: NextcloudTalkRoomConfig;
50
- roomKey?: string;
51
- matchSource?: "direct" | "parent" | "wildcard";
52
- allowed: boolean;
53
- allowlistConfigured: boolean;
54
- };
55
-
56
- export function resolveNextcloudTalkRoomMatch(params: {
57
- rooms?: Record<string, NextcloudTalkRoomConfig>;
58
- roomToken: string;
59
- }): NextcloudTalkRoomMatch {
60
- const rooms = params.rooms ?? {};
61
- const allowlistConfigured = Object.keys(rooms).length > 0;
62
- const roomCandidates = buildChannelKeyCandidates(params.roomToken);
63
- const match = resolveChannelEntryMatchWithFallback({
64
- entries: rooms,
65
- keys: roomCandidates,
66
- wildcardKey: "*",
67
- normalizeKey: normalizeChannelSlug,
68
- });
69
- const roomConfig = match.entry;
70
- const allowed = resolveNestedAllowlistDecision({
71
- outerConfigured: allowlistConfigured,
72
- outerMatched: Boolean(roomConfig),
73
- innerConfigured: false,
74
- innerMatched: false,
75
- });
76
-
77
- return {
78
- roomConfig,
79
- wildcardConfig: match.wildcardEntry,
80
- roomKey: match.matchKey ?? match.key,
81
- matchSource: match.matchSource,
82
- allowed,
83
- allowlistConfigured,
84
- };
85
- }
86
-
87
- export function resolveNextcloudTalkGroupToolPolicy(
88
- params: ChannelGroupContext,
89
- ): GroupToolPolicyConfig | undefined {
90
- const cfg = params.cfg as {
91
- channels?: { "nextcloud-talk"?: { rooms?: Record<string, NextcloudTalkRoomConfig> } };
92
- };
93
- const roomToken = params.groupId?.trim();
94
- if (!roomToken) {
95
- return undefined;
96
- }
97
- const match = resolveNextcloudTalkRoomMatch({
98
- rooms: cfg.channels?.["nextcloud-talk"]?.rooms,
99
- roomToken,
100
- });
101
- return match.roomConfig?.tools ?? match.wildcardConfig?.tools;
102
- }
103
-
104
- export function resolveNextcloudTalkRequireMention(params: {
105
- roomConfig?: NextcloudTalkRoomConfig;
106
- wildcardConfig?: NextcloudTalkRoomConfig;
107
- }): boolean {
108
- if (typeof params.roomConfig?.requireMention === "boolean") {
109
- return params.roomConfig.requireMention;
110
- }
111
- if (typeof params.wildcardConfig?.requireMention === "boolean") {
112
- return params.wildcardConfig.requireMention;
113
- }
114
- return true;
115
- }
116
-
117
- export function resolveNextcloudTalkGroupAllow(params: {
118
- groupPolicy: GroupPolicy;
119
- outerAllowFrom: Array<string | number> | undefined;
120
- innerAllowFrom: Array<string | number> | undefined;
121
- senderId: string;
122
- }): { allowed: boolean; outerMatch: AllowlistMatch; innerMatch: AllowlistMatch } {
123
- const outerAllow = normalizeNextcloudTalkAllowlist(params.outerAllowFrom);
124
- const innerAllow = normalizeNextcloudTalkAllowlist(params.innerAllowFrom);
125
- const outerMatch = resolveNextcloudTalkAllowlistMatch({
126
- allowFrom: params.outerAllowFrom,
127
- senderId: params.senderId,
128
- });
129
- const innerMatch = resolveNextcloudTalkAllowlistMatch({
130
- allowFrom: params.innerAllowFrom,
131
- senderId: params.senderId,
132
- });
133
- const access = evaluateMatchedGroupAccessForPolicy({
134
- groupPolicy: params.groupPolicy,
135
- allowlistConfigured: outerAllow.length > 0 || innerAllow.length > 0,
136
- allowlistMatched: resolveNestedAllowlistDecision({
137
- outerConfigured: outerAllow.length > 0 || innerAllow.length > 0,
138
- outerMatched: outerAllow.length > 0 ? outerMatch.allowed : true,
139
- innerConfigured: innerAllow.length > 0,
140
- innerMatched: innerMatch.allowed,
141
- }),
142
- });
143
-
144
- return {
145
- allowed: access.allowed,
146
- outerMatch:
147
- params.groupPolicy === "open"
148
- ? { allowed: true }
149
- : params.groupPolicy === "disabled"
150
- ? { allowed: false }
151
- : outerMatch,
152
- innerMatch:
153
- params.groupPolicy === "open"
154
- ? { allowed: true }
155
- : params.groupPolicy === "disabled"
156
- ? { allowed: false }
157
- : innerMatch,
158
- };
159
- }
160
-
161
- export function resolveNextcloudTalkMentionGate(params: {
162
- isGroup: boolean;
163
- requireMention: boolean;
164
- wasMentioned: boolean;
165
- allowTextCommands: boolean;
166
- hasControlCommand: boolean;
167
- commandAuthorized: boolean;
168
- }): { shouldSkip: boolean; shouldBypassMention: boolean } {
169
- const shouldBypassMention =
170
- params.isGroup &&
171
- params.requireMention &&
172
- !params.wasMentioned &&
173
- params.allowTextCommands &&
174
- params.commandAuthorized &&
175
- params.hasControlCommand;
176
- return {
177
- shouldBypassMention,
178
- shouldSkip: params.requireMention && !params.wasMentioned && !shouldBypassMention,
179
- };
180
- }