@openclaw/nextcloud-talk 2026.8.1-beta.2 → 2026.8.1
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/dist/api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as nextcloudTalkPlugin } from "./channel-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-C7Xe0Nkb.js";
|
|
2
2
|
export { nextcloudTalkPlugin };
|
|
@@ -4,7 +4,7 @@ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries
|
|
|
4
4
|
import { createAccountListHelpers, describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
5
5
|
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
6
6
|
import { createLoggedPairingApprovalNotifier, createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
|
|
7
|
-
import { createAllowlistProviderRouteAllowlistWarningCollector, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
7
|
+
import { createAllowlistProviderRouteAllowlistWarningCollector, createConditionalWarningCollector, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
8
8
|
import { buildWebhookChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
9
9
|
import { FormatCapabilityProfile, convertMarkdownTables, renderMarkdownWithMarkers, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
10
10
|
import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
@@ -12,22 +12,22 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, norm
|
|
|
12
12
|
import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, normalizeAccountId, resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
|
|
13
13
|
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
|
14
14
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
15
|
-
import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
15
|
+
import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
16
16
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
17
17
|
import { parseStrictNonNegativeInteger, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
18
|
-
import { readProviderJsonResponse,
|
|
18
|
+
import { readProviderJsonResponse, readProviderTextResponse } from "openclaw/plugin-sdk/provider-http";
|
|
19
19
|
import { resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
20
20
|
import { bindIngressLifecycleToReplyOptions, createAccountStatusSink, createChannelIngressMonitor, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveChannelStreamingBlockEnabled, runPassiveAccountLifecycle } from "openclaw/plugin-sdk/channel-outbound";
|
|
21
21
|
import { ssrfPolicyFromPrivateNetworkOptIn, ssrfPolicyFromPrivateNetworkOptIn as ssrfPolicyFromPrivateNetworkOptIn$1 } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
22
|
+
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
22
23
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
23
24
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
24
25
|
import { createHmac, randomBytes } from "node:crypto";
|
|
25
26
|
import { fileExists, safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
26
|
-
import { clearAccountEntryFields } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
27
27
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2 } from "openclaw/plugin-sdk/account-id";
|
|
28
|
-
import { DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
|
|
28
|
+
import { DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, ReplyToModeSchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
|
|
29
29
|
import { formatAllowFromLowercase, resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
|
|
30
|
-
import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
30
|
+
import { adaptScopedAccountAccessor, clearAccountFieldsFromConfigSection, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
31
31
|
import { requireChannelOpenAllowFrom, resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
|
|
32
32
|
import { z } from "zod";
|
|
33
33
|
import os from "node:os";
|
|
@@ -40,7 +40,7 @@ import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMat
|
|
|
40
40
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
41
41
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
42
42
|
import { createServer } from "node:http";
|
|
43
|
-
import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
|
|
43
|
+
import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
44
44
|
import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
45
45
|
import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
|
|
46
46
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
@@ -97,18 +97,26 @@ function listNextcloudTalkAccountIds(cfg) {
|
|
|
97
97
|
debugAccounts("listNextcloudTalkAccountIds", ids);
|
|
98
98
|
return ids;
|
|
99
99
|
}
|
|
100
|
-
function resolveNextcloudTalkSecret(
|
|
101
|
-
const
|
|
102
|
-
|
|
100
|
+
function resolveNextcloudTalkSecret(accountId, merged) {
|
|
101
|
+
const configuredSecret = resolveSecretInputString({
|
|
102
|
+
value: merged.botSecret,
|
|
103
|
+
path: `channels.nextcloud-talk.accounts.${accountId}.botSecret`,
|
|
104
|
+
mode: "inspect"
|
|
105
|
+
});
|
|
106
|
+
if (configuredSecret.status === "configured_unavailable") return {
|
|
107
|
+
secret: "",
|
|
108
|
+
source: "config",
|
|
109
|
+
status: "configured_unavailable"
|
|
110
|
+
};
|
|
103
111
|
const envSecret = normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET);
|
|
104
|
-
if (envSecret &&
|
|
112
|
+
if (envSecret && accountId === DEFAULT_ACCOUNT_ID) return {
|
|
105
113
|
secret: envSecret,
|
|
106
114
|
source: "env",
|
|
107
115
|
status: "available"
|
|
108
116
|
};
|
|
109
117
|
const botSecretFile = normalizeOptionalString(merged.botSecretFile);
|
|
110
118
|
if (botSecretFile) {
|
|
111
|
-
const result = tryReadSecretFileSync(botSecretFile, "Nextcloud Talk bot secret file", { rejectSymlink: true }, { configPath: `channels.nextcloud-talk.accounts.${
|
|
119
|
+
const result = tryReadSecretFileSync(botSecretFile, "Nextcloud Talk bot secret file", { rejectSymlink: true }, { configPath: `channels.nextcloud-talk.accounts.${accountId}.botSecretFile` });
|
|
112
120
|
return result.status === "available" ? {
|
|
113
121
|
secret: result.value,
|
|
114
122
|
source: "secretFile",
|
|
@@ -120,12 +128,8 @@ function resolveNextcloudTalkSecret(cfg, opts) {
|
|
|
120
128
|
diagnostic: result.diagnostic
|
|
121
129
|
};
|
|
122
130
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
path: `channels.nextcloud-talk.accounts.${resolvedAccountId}.botSecret`
|
|
126
|
-
});
|
|
127
|
-
if (inlineSecret) return {
|
|
128
|
-
secret: inlineSecret,
|
|
131
|
+
if (configuredSecret.status === "available") return {
|
|
132
|
+
secret: configuredSecret.value,
|
|
129
133
|
source: "config",
|
|
130
134
|
status: "available"
|
|
131
135
|
};
|
|
@@ -142,14 +146,7 @@ function resolveNextcloudTalkAccount(params) {
|
|
|
142
146
|
const merged = mergeNextcloudTalkAccountConfig(params.cfg, accountId);
|
|
143
147
|
const accountEnabled = merged.enabled !== false;
|
|
144
148
|
const enabled = baseEnabled && accountEnabled;
|
|
145
|
-
const secretResolution = resolveNextcloudTalkSecret(
|
|
146
|
-
const apiCredentialResolution = resolveNextcloudTalkApiCredentialsResult({
|
|
147
|
-
apiUser: merged.apiUser,
|
|
148
|
-
apiPassword: merged.apiPassword,
|
|
149
|
-
apiPasswordFile: merged.apiPasswordFile,
|
|
150
|
-
configPath: `channels.nextcloud-talk.accounts.${accountId}.apiPasswordFile`
|
|
151
|
-
});
|
|
152
|
-
const diagnostics = [secretResolution.diagnostic, apiCredentialResolution.status === "configured_unavailable" ? apiCredentialResolution.diagnostic : void 0].filter((diagnostic) => Boolean(diagnostic));
|
|
149
|
+
const secretResolution = resolveNextcloudTalkSecret(accountId, merged);
|
|
153
150
|
const baseUrl = merged.baseUrl?.trim()?.replace(/\/$/, "") ?? "";
|
|
154
151
|
debugAccounts("resolve", {
|
|
155
152
|
accountId,
|
|
@@ -165,8 +162,7 @@ function resolveNextcloudTalkAccount(params) {
|
|
|
165
162
|
secret: secretResolution.secret,
|
|
166
163
|
secretSource: secretResolution.source,
|
|
167
164
|
tokenStatus: secretResolution.status,
|
|
168
|
-
|
|
169
|
-
...diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {},
|
|
165
|
+
...secretResolution.diagnostic ? { credentialDiagnostics: [secretResolution.diagnostic] } : {},
|
|
170
166
|
config: merged
|
|
171
167
|
};
|
|
172
168
|
};
|
|
@@ -178,6 +174,21 @@ function resolveNextcloudTalkAccount(params) {
|
|
|
178
174
|
resolveDefaultAccountId: () => resolveDefaultNextcloudTalkAccountId(params.cfg)
|
|
179
175
|
});
|
|
180
176
|
}
|
|
177
|
+
function inspectNextcloudTalkAccount(params) {
|
|
178
|
+
const account = resolveNextcloudTalkAccount(params);
|
|
179
|
+
const apiCredentialResolution = resolveNextcloudTalkApiCredentialsResult({
|
|
180
|
+
apiUser: account.config.apiUser,
|
|
181
|
+
apiPassword: account.config.apiPassword,
|
|
182
|
+
apiPasswordFile: account.config.apiPasswordFile,
|
|
183
|
+
configPath: `channels.nextcloud-talk.accounts.${account.accountId}.apiPasswordFile`
|
|
184
|
+
});
|
|
185
|
+
const credentialDiagnostics = [...account.credentialDiagnostics ?? [], ...apiCredentialResolution.status === "configured_unavailable" ? [apiCredentialResolution.diagnostic] : []];
|
|
186
|
+
return {
|
|
187
|
+
...account,
|
|
188
|
+
apiCredentialStatus: apiCredentialResolution.status,
|
|
189
|
+
...credentialDiagnostics.length > 0 ? { credentialDiagnostics } : {}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
181
192
|
//#endregion
|
|
182
193
|
//#region extensions/nextcloud-talk/src/approval-auth.ts
|
|
183
194
|
function normalizeNextcloudTalkApproverId(value) {
|
|
@@ -199,6 +210,18 @@ async function releaseNextcloudTalkGuardedResponse(params) {
|
|
|
199
210
|
if (!params.response.bodyUsed) await params.response.body?.cancel().catch(() => void 0);
|
|
200
211
|
await params.release();
|
|
201
212
|
}
|
|
213
|
+
async function readNextcloudTalkErrorBody(response, ...credentials) {
|
|
214
|
+
try {
|
|
215
|
+
let body = await readProviderTextResponse(response, "Nextcloud Talk error", {
|
|
216
|
+
maxBytes: 8 * 1024,
|
|
217
|
+
chunkTimeoutMs: 1e4
|
|
218
|
+
});
|
|
219
|
+
for (const credential of credentials) body = body.replaceAll(credential, "***");
|
|
220
|
+
return redactToolPayloadText(body);
|
|
221
|
+
} catch {
|
|
222
|
+
return "";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
202
225
|
//#endregion
|
|
203
226
|
//#region extensions/nextcloud-talk/src/signature.ts
|
|
204
227
|
const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
|
|
@@ -245,7 +268,6 @@ function generateNextcloudTalkSignature(params) {
|
|
|
245
268
|
//#endregion
|
|
246
269
|
//#region extensions/nextcloud-talk/src/bot-preflight.ts
|
|
247
270
|
const BOT_FEATURE_RESPONSE = 2;
|
|
248
|
-
const BOT_PREFLIGHT_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
|
249
271
|
function normalizeUrlForMatch(value) {
|
|
250
272
|
if (!value?.trim()) return "";
|
|
251
273
|
try {
|
|
@@ -310,7 +332,7 @@ async function probeNextcloudTalkBotResponseFeature(params) {
|
|
|
310
332
|
});
|
|
311
333
|
try {
|
|
312
334
|
if (!response.ok) {
|
|
313
|
-
const body = await
|
|
335
|
+
const body = await readNextcloudTalkErrorBody(response, auth, credentials.apiPassword);
|
|
314
336
|
return {
|
|
315
337
|
ok: false,
|
|
316
338
|
code: "api_error",
|
|
@@ -362,6 +384,7 @@ const nextcloudTalkConfigAdapter = createScopedChannelConfigAdapter({
|
|
|
362
384
|
sectionKey: "nextcloud-talk",
|
|
363
385
|
listAccountIds: listNextcloudTalkAccountIds,
|
|
364
386
|
resolveAccount: adaptScopedAccountAccessor(resolveNextcloudTalkAccount),
|
|
387
|
+
inspectAccount: adaptScopedAccountAccessor(inspectNextcloudTalkAccount),
|
|
365
388
|
defaultAccountId: resolveDefaultNextcloudTalkAccountId,
|
|
366
389
|
clearBaseFields: [
|
|
367
390
|
"botSecret",
|
|
@@ -397,6 +420,7 @@ const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
|
|
|
397
420
|
name: z.string().optional(),
|
|
398
421
|
enabled: z.boolean().optional(),
|
|
399
422
|
configWrites: z.boolean().optional(),
|
|
423
|
+
replyToMode: ReplyToModeSchema.optional(),
|
|
400
424
|
markdown: MarkdownConfigSchema,
|
|
401
425
|
baseUrl: z.string().optional(),
|
|
402
426
|
botSecret: buildSecretInputSchema().optional(),
|
|
@@ -692,7 +716,6 @@ function looksLikeNextcloudTalkTargetId(raw) {
|
|
|
692
716
|
}
|
|
693
717
|
//#endregion
|
|
694
718
|
//#region extensions/nextcloud-talk/src/send.ts
|
|
695
|
-
const NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_BYTES = 8 * 1024;
|
|
696
719
|
const NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS = 200;
|
|
697
720
|
const NEXTCLOUD_TALK_SEND_TIMEOUT_MS = 3e4;
|
|
698
721
|
const NEXTCLOUD_TALK_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
@@ -713,20 +736,12 @@ function renderNextcloudTalkMarkdown(markdown) {
|
|
|
713
736
|
escapeText: (text) => text
|
|
714
737
|
}, NEXTCLOUD_TALK_FORMAT_PROFILE);
|
|
715
738
|
}
|
|
716
|
-
/** Collapses
|
|
739
|
+
/** Collapses and caps an already-redacted error body for display. */
|
|
717
740
|
function collapseErrorSnippet(text) {
|
|
718
741
|
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
719
742
|
if (collapsed.length > NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS) return `${truncateUtf16Safe(collapsed, NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS)}…`;
|
|
720
743
|
return collapsed;
|
|
721
744
|
}
|
|
722
|
-
/** Reads a bounded, collapsed error-body snippet without buffering hostile responses. */
|
|
723
|
-
async function readNextcloudTalkErrorSnippet(response) {
|
|
724
|
-
try {
|
|
725
|
-
return collapseErrorSnippet(await readResponseTextLimited(response, NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_BYTES));
|
|
726
|
-
} catch {
|
|
727
|
-
return "";
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
745
|
function resolveCredentials(explicit, account) {
|
|
731
746
|
const baseUrl = explicit.baseUrl?.trim() ?? account.baseUrl;
|
|
732
747
|
const secret = explicit.secret?.trim() ?? account.secret;
|
|
@@ -817,7 +832,7 @@ async function sendMessageNextcloudTalk(to, text, opts) {
|
|
|
817
832
|
});
|
|
818
833
|
try {
|
|
819
834
|
if (!response.ok) {
|
|
820
|
-
const errorBody = await
|
|
835
|
+
const errorBody = collapseErrorSnippet(await readNextcloudTalkErrorBody(response, signature));
|
|
821
836
|
const status = response.status;
|
|
822
837
|
let errorMsg = `Nextcloud Talk send failed (${status})`;
|
|
823
838
|
if (status === 400) errorMsg = `Nextcloud Talk: bad request - ${errorBody || "invalid message format"}`;
|
|
@@ -879,7 +894,7 @@ async function sendReactionNextcloudTalk(roomToken, messageId, reaction, opts) {
|
|
|
879
894
|
});
|
|
880
895
|
try {
|
|
881
896
|
if (!response.ok) {
|
|
882
|
-
const errorBody = await
|
|
897
|
+
const errorBody = collapseErrorSnippet(await readNextcloudTalkErrorBody(response, signature));
|
|
883
898
|
throw new Error(`Nextcloud Talk reaction failed: ${response.status} ${errorBody}`.trim());
|
|
884
899
|
}
|
|
885
900
|
return { ok: true };
|
|
@@ -950,7 +965,15 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
950
965
|
accountId: account.accountId
|
|
951
966
|
});
|
|
952
967
|
const rawBody = message.text?.trim() ?? "";
|
|
953
|
-
if (!rawBody)
|
|
968
|
+
if (!rawBody) {
|
|
969
|
+
logInboundDrop({
|
|
970
|
+
log: (messageLocal) => runtime.log?.(messageLocal),
|
|
971
|
+
channel: CHANNEL_ID,
|
|
972
|
+
reason: `empty message body (mediaType=${message.mediaType})`,
|
|
973
|
+
target: message.senderId
|
|
974
|
+
});
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
954
977
|
const roomKind = await resolveNextcloudTalkRoomKind({
|
|
955
978
|
account,
|
|
956
979
|
roomToken: message.roomToken,
|
|
@@ -1352,7 +1375,7 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1352
1375
|
res.end();
|
|
1353
1376
|
return;
|
|
1354
1377
|
}
|
|
1355
|
-
const clientIp = req.socket.remoteAddress ?? "unknown";
|
|
1378
|
+
const clientIp = resolveRequestClientIp(req, opts.trustedProxies, opts.allowRealIpFallback) ?? req.socket.remoteAddress ?? "unknown";
|
|
1356
1379
|
if (!webhookAuthRateLimiter.check(clientIp, WEBHOOK_AUTH_RATE_LIMIT_SCOPE).allowed) {
|
|
1357
1380
|
res.writeHead(429);
|
|
1358
1381
|
res.end("Too Many Requests");
|
|
@@ -1410,6 +1433,7 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1410
1433
|
const stop = async () => {
|
|
1411
1434
|
stopRequested = true;
|
|
1412
1435
|
await closeIfListening();
|
|
1436
|
+
webhookAuthRateLimiter.dispose();
|
|
1413
1437
|
};
|
|
1414
1438
|
const start = () => {
|
|
1415
1439
|
if (stopRequested) return Promise.resolve();
|
|
@@ -1436,6 +1460,12 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1436
1460
|
//#endregion
|
|
1437
1461
|
//#region extensions/nextcloud-talk/src/webhook-spool.ts
|
|
1438
1462
|
const NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS = 500;
|
|
1463
|
+
function describeIgnoredWebhookEvent(rawEvent) {
|
|
1464
|
+
const envelope = parseRawObject(rawEvent);
|
|
1465
|
+
const type = typeof envelope.type === "string" ? envelope.type : "unknown";
|
|
1466
|
+
const object = isRecord(envelope.object) ? envelope.object : null;
|
|
1467
|
+
return `type=${type} objectType=${typeof object?.type === "string" ? object.type : "unknown"}`;
|
|
1468
|
+
}
|
|
1439
1469
|
const NextcloudTalkWebhookPayloadSchema = z.object({
|
|
1440
1470
|
type: z.enum([
|
|
1441
1471
|
"Create",
|
|
@@ -1562,7 +1592,11 @@ function createNextcloudTalkWebhookSpool(options) {
|
|
|
1562
1592
|
if (stopping) return Promise.reject(/* @__PURE__ */ new Error("Nextcloud Talk ingress stopped"));
|
|
1563
1593
|
const receiveTask = (async () => {
|
|
1564
1594
|
await startAfterMigration;
|
|
1565
|
-
|
|
1595
|
+
if ((await monitor.admit(rawEvent)).kind === "ignored") {
|
|
1596
|
+
options.runtime.log?.(`nextcloud-talk: ignored non-message webhook event (${describeIgnoredWebhookEvent(rawEvent)})`);
|
|
1597
|
+
return "ignored";
|
|
1598
|
+
}
|
|
1599
|
+
return "accepted";
|
|
1566
1600
|
})();
|
|
1567
1601
|
inFlightReceives.add(receiveTask);
|
|
1568
1602
|
receiveTask.then(() => inFlightReceives.delete(receiveTask), () => inFlightReceives.delete(receiveTask));
|
|
@@ -1647,6 +1681,8 @@ async function monitorNextcloudTalkProvider(opts) {
|
|
|
1647
1681
|
onError: (error) => {
|
|
1648
1682
|
logger.error(`[nextcloud-talk:${account.accountId}] webhook error: ${error.message}`);
|
|
1649
1683
|
},
|
|
1684
|
+
trustedProxies: cfg.gateway?.trustedProxies,
|
|
1685
|
+
allowRealIpFallback: cfg.gateway?.allowRealIpFallback,
|
|
1650
1686
|
abortSignal: opts.abortSignal
|
|
1651
1687
|
});
|
|
1652
1688
|
let stopPromise;
|
|
@@ -1704,44 +1740,18 @@ const nextcloudTalkGatewayAdapter = {
|
|
|
1704
1740
|
});
|
|
1705
1741
|
},
|
|
1706
1742
|
logoutAccount: async ({ accountId, cfg }) => {
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
delete nextSection.botSecret;
|
|
1714
|
-
cleared = true;
|
|
1715
|
-
changed = true;
|
|
1716
|
-
}
|
|
1717
|
-
const accountCleanup = clearAccountEntryFields({
|
|
1718
|
-
accounts: nextSection.accounts,
|
|
1719
|
-
accountId,
|
|
1720
|
-
fields: ["botSecret"]
|
|
1721
|
-
});
|
|
1722
|
-
if (accountCleanup.changed) {
|
|
1723
|
-
changed = true;
|
|
1724
|
-
if (accountCleanup.cleared) cleared = true;
|
|
1725
|
-
if (accountCleanup.nextAccounts) nextSection.accounts = accountCleanup.nextAccounts;
|
|
1726
|
-
else delete nextSection.accounts;
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
if (changed) if (nextSection && Object.keys(nextSection).length > 0) nextCfg.channels = {
|
|
1730
|
-
...nextCfg.channels,
|
|
1731
|
-
"nextcloud-talk": nextSection
|
|
1732
|
-
};
|
|
1733
|
-
else {
|
|
1734
|
-
const nextChannels = { ...nextCfg.channels };
|
|
1735
|
-
delete nextChannels["nextcloud-talk"];
|
|
1736
|
-
if (Object.keys(nextChannels).length > 0) nextCfg.channels = nextChannels;
|
|
1737
|
-
else delete nextCfg.channels;
|
|
1738
|
-
}
|
|
1743
|
+
const { nextConfig, changed, cleared } = clearAccountFieldsFromConfigSection({
|
|
1744
|
+
cfg,
|
|
1745
|
+
sectionKey: "nextcloud-talk",
|
|
1746
|
+
accountId,
|
|
1747
|
+
fields: ["botSecret"]
|
|
1748
|
+
});
|
|
1739
1749
|
const loggedOut = resolveNextcloudTalkAccount({
|
|
1740
|
-
cfg:
|
|
1750
|
+
cfg: nextConfig,
|
|
1741
1751
|
accountId
|
|
1742
1752
|
}).secretSource === "none";
|
|
1743
1753
|
if (changed) await getNextcloudTalkRuntime().config.replaceConfigFile({
|
|
1744
|
-
nextConfig
|
|
1754
|
+
nextConfig,
|
|
1745
1755
|
afterWrite: { mode: "auto" }
|
|
1746
1756
|
});
|
|
1747
1757
|
return {
|
|
@@ -2192,6 +2202,12 @@ const collectNextcloudTalkSecurityWarnings = createAllowlistProviderRouteAllowli
|
|
|
2192
2202
|
groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom"
|
|
2193
2203
|
}
|
|
2194
2204
|
});
|
|
2205
|
+
const collectNextcloudTalkOpenGroupFindings = createConditionalWarningCollector.findings({
|
|
2206
|
+
collectWarnings: collectNextcloudTalkSecurityWarnings,
|
|
2207
|
+
checkId: "channels.nextcloud-talk.groups.open",
|
|
2208
|
+
severity: "critical",
|
|
2209
|
+
title: "Nextcloud Talk security warning"
|
|
2210
|
+
});
|
|
2195
2211
|
const nextcloudTalkPlugin = createChatChannelPlugin({
|
|
2196
2212
|
base: {
|
|
2197
2213
|
id: "nextcloud-talk",
|
|
@@ -2288,7 +2304,7 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
|
|
|
2288
2304
|
} },
|
|
2289
2305
|
security: {
|
|
2290
2306
|
...nextcloudTalkSecurityAdapter,
|
|
2291
|
-
collectWarnings:
|
|
2307
|
+
collectWarnings: collectNextcloudTalkOpenGroupFindings
|
|
2292
2308
|
},
|
|
2293
2309
|
outbound: {
|
|
2294
2310
|
base: {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as nextcloudTalkPlugin } from "./channel-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-C7Xe0Nkb.js";
|
|
2
2
|
export { nextcloudTalkPlugin };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, mergeAccountConfig } from "openclaw/plugin-sdk/account-core";
|
|
2
|
+
//#region extensions/nextcloud-talk/configured-state.ts
|
|
3
|
+
function hasConfiguredNextcloudAccount(account, env) {
|
|
4
|
+
return Boolean(account?.baseUrl?.trim() && (hasConfiguredAccountValue(account.botSecret) || hasConfiguredAccountValue(account.botSecretFile) || hasConfiguredAccountValue(env.NEXTCLOUD_TALK_BOT_SECRET)));
|
|
5
|
+
}
|
|
6
|
+
/** Require a Nextcloud server plus its account-owned bot credential. */
|
|
7
|
+
function hasConfiguredNextcloudTalkChannelState(params) {
|
|
8
|
+
const channel = params.cfg.channels?.["nextcloud-talk"];
|
|
9
|
+
if (channel?.enabled === false) return false;
|
|
10
|
+
const defaultAccount = channel?.accounts?.[DEFAULT_ACCOUNT_ID];
|
|
11
|
+
if (defaultAccount?.enabled !== false) {
|
|
12
|
+
if (hasConfiguredNextcloudAccount(defaultAccount ? mergeAccountConfig({
|
|
13
|
+
channelConfig: channel,
|
|
14
|
+
accountConfig: defaultAccount,
|
|
15
|
+
omitKeys: ["defaultAccount"]
|
|
16
|
+
}) : channel, params.env ?? process.env)) return true;
|
|
17
|
+
}
|
|
18
|
+
return Object.entries(channel?.accounts ?? {}).some(([accountId, account]) => accountId !== DEFAULT_ACCOUNT_ID && account.enabled !== false && hasConfiguredNextcloudAccount(mergeAccountConfig({
|
|
19
|
+
channelConfig: channel,
|
|
20
|
+
accountConfig: account,
|
|
21
|
+
omitKeys: ["defaultAccount"]
|
|
22
|
+
}), {}));
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { hasConfiguredNextcloudTalkChannelState };
|
package/openclaw.plugin.json
CHANGED
|
@@ -32,6 +32,26 @@
|
|
|
32
32
|
"configWrites": {
|
|
33
33
|
"type": "boolean"
|
|
34
34
|
},
|
|
35
|
+
"replyToMode": {
|
|
36
|
+
"anyOf": [
|
|
37
|
+
{
|
|
38
|
+
"type": "string",
|
|
39
|
+
"const": "off"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "string",
|
|
43
|
+
"const": "first"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "string",
|
|
47
|
+
"const": "all"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "string",
|
|
51
|
+
"const": "batched"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
35
55
|
"markdown": {
|
|
36
56
|
"type": "object",
|
|
37
57
|
"properties": {
|
|
@@ -479,6 +499,26 @@
|
|
|
479
499
|
"configWrites": {
|
|
480
500
|
"type": "boolean"
|
|
481
501
|
},
|
|
502
|
+
"replyToMode": {
|
|
503
|
+
"anyOf": [
|
|
504
|
+
{
|
|
505
|
+
"type": "string",
|
|
506
|
+
"const": "off"
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"type": "string",
|
|
510
|
+
"const": "first"
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"type": "string",
|
|
514
|
+
"const": "all"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"type": "string",
|
|
518
|
+
"const": "batched"
|
|
519
|
+
}
|
|
520
|
+
]
|
|
521
|
+
},
|
|
482
522
|
"markdown": {
|
|
483
523
|
"type": "object",
|
|
484
524
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/nextcloud-talk",
|
|
3
|
-
"version": "2026.8.1
|
|
3
|
+
"version": "2026.8.1",
|
|
4
4
|
"description": "OpenClaw Nextcloud Talk channel plugin for conversations.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"openclaw": ">=2026.8.1
|
|
11
|
+
"openclaw": ">=2026.8.1"
|
|
12
12
|
},
|
|
13
13
|
"peerDependenciesMeta": {
|
|
14
14
|
"openclaw": {
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"NEXTCLOUD_TALK_BOT_SECRET",
|
|
32
32
|
"NEXTCLOUD_TALK_API_PASSWORD"
|
|
33
33
|
]
|
|
34
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"specifier": "./dist/configured-state.js",
|
|
36
|
+
"exportName": "hasConfiguredNextcloudTalkChannelState"
|
|
35
37
|
},
|
|
36
38
|
"label": "Nextcloud Talk",
|
|
37
39
|
"selectionLabel": "Nextcloud Talk (self-hosted)",
|
|
@@ -127,10 +129,10 @@
|
|
|
127
129
|
"minHostVersion": ">=2026.4.10"
|
|
128
130
|
},
|
|
129
131
|
"compat": {
|
|
130
|
-
"pluginApi": ">=2026.8.1
|
|
132
|
+
"pluginApi": ">=2026.8.1"
|
|
131
133
|
},
|
|
132
134
|
"build": {
|
|
133
|
-
"openclawVersion": "2026.8.1
|
|
135
|
+
"openclawVersion": "2026.8.1"
|
|
134
136
|
},
|
|
135
137
|
"release": {
|
|
136
138
|
"publishToClawHub": true,
|