@openclaw/nextcloud-talk 2026.2.1 → 2026.2.6
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.
- package/package.json +1 -1
- package/src/config-schema.ts +1 -0
- package/src/inbound.ts +10 -3
- package/src/monitor.ts +1 -1
- package/src/policy.test.ts +33 -0
- package/src/policy.ts +1 -9
- package/src/send.ts +7 -2
- package/src/types.ts +2 -0
package/package.json
CHANGED
package/src/config-schema.ts
CHANGED
|
@@ -47,6 +47,7 @@ export const NextcloudTalkAccountSchemaBase = z
|
|
|
47
47
|
chunkMode: z.enum(["length", "newline"]).optional(),
|
|
48
48
|
blockStreaming: z.boolean().optional(),
|
|
49
49
|
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
|
50
|
+
responsePrefix: z.string().optional(),
|
|
50
51
|
mediaMaxMb: z.number().positive().optional(),
|
|
51
52
|
})
|
|
52
53
|
.strict();
|
package/src/inbound.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createReplyPrefixOptions,
|
|
2
3
|
logInboundDrop,
|
|
3
4
|
resolveControlCommandGate,
|
|
4
5
|
type OpenClawConfig,
|
|
@@ -121,7 +122,6 @@ export async function handleNextcloudTalkInbound(params: {
|
|
|
121
122
|
const senderAllowedForCommands = resolveNextcloudTalkAllowlistMatch({
|
|
122
123
|
allowFrom: isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom,
|
|
123
124
|
senderId,
|
|
124
|
-
senderName,
|
|
125
125
|
}).allowed;
|
|
126
126
|
const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config as OpenClawConfig);
|
|
127
127
|
const commandGate = resolveControlCommandGate({
|
|
@@ -143,7 +143,6 @@ export async function handleNextcloudTalkInbound(params: {
|
|
|
143
143
|
outerAllowFrom: effectiveGroupAllowFrom,
|
|
144
144
|
innerAllowFrom: roomAllowFrom,
|
|
145
145
|
senderId,
|
|
146
|
-
senderName,
|
|
147
146
|
});
|
|
148
147
|
if (!groupAllow.allowed) {
|
|
149
148
|
runtime.log?.(`nextcloud-talk: drop group sender ${senderId} (policy=${groupPolicy})`);
|
|
@@ -158,7 +157,6 @@ export async function handleNextcloudTalkInbound(params: {
|
|
|
158
157
|
const dmAllowed = resolveNextcloudTalkAllowlistMatch({
|
|
159
158
|
allowFrom: effectiveAllowFrom,
|
|
160
159
|
senderId,
|
|
161
|
-
senderName,
|
|
162
160
|
}).allowed;
|
|
163
161
|
if (!dmAllowed) {
|
|
164
162
|
if (dmPolicy === "pairing") {
|
|
@@ -288,10 +286,18 @@ export async function handleNextcloudTalkInbound(params: {
|
|
|
288
286
|
},
|
|
289
287
|
});
|
|
290
288
|
|
|
289
|
+
const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
|
|
290
|
+
cfg: config as OpenClawConfig,
|
|
291
|
+
agentId: route.agentId,
|
|
292
|
+
channel: CHANNEL_ID,
|
|
293
|
+
accountId: account.accountId,
|
|
294
|
+
});
|
|
295
|
+
|
|
291
296
|
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
292
297
|
ctx: ctxPayload,
|
|
293
298
|
cfg: config as OpenClawConfig,
|
|
294
299
|
dispatcherOptions: {
|
|
300
|
+
...prefixOptions,
|
|
295
301
|
deliver: async (payload) => {
|
|
296
302
|
await deliverNextcloudTalkReply({
|
|
297
303
|
payload: payload as {
|
|
@@ -311,6 +317,7 @@ export async function handleNextcloudTalkInbound(params: {
|
|
|
311
317
|
},
|
|
312
318
|
replyOptions: {
|
|
313
319
|
skillFilter: roomConfig?.skills,
|
|
320
|
+
onModelSelected,
|
|
314
321
|
disableBlockStreaming:
|
|
315
322
|
typeof account.config.blockStreaming === "boolean"
|
|
316
323
|
? !account.config.blockStreaming
|
package/src/monitor.ts
CHANGED
|
@@ -54,7 +54,7 @@ function payloadToInboundMessage(
|
|
|
54
54
|
roomToken: payload.target.id,
|
|
55
55
|
roomName: payload.target.name,
|
|
56
56
|
senderId: payload.actor.id,
|
|
57
|
-
senderName: payload.actor.name,
|
|
57
|
+
senderName: payload.actor.name ?? "",
|
|
58
58
|
text: payload.object.content || payload.object.name || "",
|
|
59
59
|
mediaType: payload.object.mediaType || "text/plain",
|
|
60
60
|
timestamp: Date.now(),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveNextcloudTalkAllowlistMatch } from "./policy.js";
|
|
3
|
+
|
|
4
|
+
describe("nextcloud-talk policy", () => {
|
|
5
|
+
describe("resolveNextcloudTalkAllowlistMatch", () => {
|
|
6
|
+
it("allows wildcard", () => {
|
|
7
|
+
expect(
|
|
8
|
+
resolveNextcloudTalkAllowlistMatch({
|
|
9
|
+
allowFrom: ["*"],
|
|
10
|
+
senderId: "user-id",
|
|
11
|
+
}).allowed,
|
|
12
|
+
).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("allows sender id match with normalization", () => {
|
|
16
|
+
expect(
|
|
17
|
+
resolveNextcloudTalkAllowlistMatch({
|
|
18
|
+
allowFrom: ["nc:User-Id"],
|
|
19
|
+
senderId: "user-id",
|
|
20
|
+
}),
|
|
21
|
+
).toEqual({ allowed: true, matchKey: "user-id", matchSource: "id" });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("blocks when sender id does not match", () => {
|
|
25
|
+
expect(
|
|
26
|
+
resolveNextcloudTalkAllowlistMatch({
|
|
27
|
+
allowFrom: ["allowed"],
|
|
28
|
+
senderId: "other",
|
|
29
|
+
}).allowed,
|
|
30
|
+
).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
package/src/policy.ts
CHANGED
|
@@ -29,8 +29,7 @@ export function normalizeNextcloudTalkAllowlist(
|
|
|
29
29
|
export function resolveNextcloudTalkAllowlistMatch(params: {
|
|
30
30
|
allowFrom: Array<string | number> | undefined;
|
|
31
31
|
senderId: string;
|
|
32
|
-
|
|
33
|
-
}): AllowlistMatch<"wildcard" | "id" | "name"> {
|
|
32
|
+
}): AllowlistMatch<"wildcard" | "id"> {
|
|
34
33
|
const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
|
|
35
34
|
if (allowFrom.length === 0) {
|
|
36
35
|
return { allowed: false };
|
|
@@ -42,10 +41,6 @@ export function resolveNextcloudTalkAllowlistMatch(params: {
|
|
|
42
41
|
if (allowFrom.includes(senderId)) {
|
|
43
42
|
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
44
43
|
}
|
|
45
|
-
const senderName = params.senderName ? normalizeAllowEntry(params.senderName) : "";
|
|
46
|
-
if (senderName && allowFrom.includes(senderName)) {
|
|
47
|
-
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
|
48
|
-
}
|
|
49
44
|
return { allowed: false };
|
|
50
45
|
}
|
|
51
46
|
|
|
@@ -132,7 +127,6 @@ export function resolveNextcloudTalkGroupAllow(params: {
|
|
|
132
127
|
outerAllowFrom: Array<string | number> | undefined;
|
|
133
128
|
innerAllowFrom: Array<string | number> | undefined;
|
|
134
129
|
senderId: string;
|
|
135
|
-
senderName?: string | null;
|
|
136
130
|
}): { allowed: boolean; outerMatch: AllowlistMatch; innerMatch: AllowlistMatch } {
|
|
137
131
|
if (params.groupPolicy === "disabled") {
|
|
138
132
|
return { allowed: false, outerMatch: { allowed: false }, innerMatch: { allowed: false } };
|
|
@@ -150,12 +144,10 @@ export function resolveNextcloudTalkGroupAllow(params: {
|
|
|
150
144
|
const outerMatch = resolveNextcloudTalkAllowlistMatch({
|
|
151
145
|
allowFrom: params.outerAllowFrom,
|
|
152
146
|
senderId: params.senderId,
|
|
153
|
-
senderName: params.senderName,
|
|
154
147
|
});
|
|
155
148
|
const innerMatch = resolveNextcloudTalkAllowlistMatch({
|
|
156
149
|
allowFrom: params.innerAllowFrom,
|
|
157
150
|
senderId: params.senderId,
|
|
158
|
-
senderName: params.senderName,
|
|
159
151
|
});
|
|
160
152
|
const allowed = resolveNestedAllowlistDecision({
|
|
161
153
|
outerConfigured: outerAllow.length > 0 || innerAllow.length > 0,
|
package/src/send.ts
CHANGED
|
@@ -93,8 +93,12 @@ export async function sendMessageNextcloudTalk(
|
|
|
93
93
|
}
|
|
94
94
|
const bodyStr = JSON.stringify(body);
|
|
95
95
|
|
|
96
|
+
// Nextcloud Talk verifies signature against the extracted message text,
|
|
97
|
+
// not the full JSON body. See ChecksumVerificationService.php:
|
|
98
|
+
// hash_hmac('sha256', $random . $data, $secret)
|
|
99
|
+
// where $data is the "message" parameter, not the raw request body.
|
|
96
100
|
const { random, signature } = generateNextcloudTalkSignature({
|
|
97
|
-
body:
|
|
101
|
+
body: message,
|
|
98
102
|
secret,
|
|
99
103
|
});
|
|
100
104
|
|
|
@@ -183,8 +187,9 @@ export async function sendReactionNextcloudTalk(
|
|
|
183
187
|
const normalizedToken = normalizeRoomToken(roomToken);
|
|
184
188
|
|
|
185
189
|
const body = JSON.stringify({ reaction });
|
|
190
|
+
// Sign only the reaction string, not the full JSON body
|
|
186
191
|
const { random, signature } = generateNextcloudTalkSignature({
|
|
187
|
-
body,
|
|
192
|
+
body: reaction,
|
|
188
193
|
secret,
|
|
189
194
|
});
|
|
190
195
|
|
package/src/types.ts
CHANGED
|
@@ -68,6 +68,8 @@ export type NextcloudTalkAccountConfig = {
|
|
|
68
68
|
blockStreaming?: boolean;
|
|
69
69
|
/** Merge streamed block replies before sending. */
|
|
70
70
|
blockStreamingCoalesce?: BlockStreamingCoalesceConfig;
|
|
71
|
+
/** Outbound response prefix override for this channel/account. */
|
|
72
|
+
responsePrefix?: string;
|
|
71
73
|
/** Media upload max size in MB. */
|
|
72
74
|
mediaMaxMb?: number;
|
|
73
75
|
};
|