@openclaw/nextcloud-talk 2026.9.1-beta.1 → 2026.9.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.
package/dist/api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as nextcloudTalkPlugin } from "./channel-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-B3OgVClw.js";
|
|
2
2
|
export { nextcloudTalkPlugin };
|
|
@@ -7,41 +7,43 @@ import { createLoggedPairingApprovalNotifier, createPairingPrefixStripper } from
|
|
|
7
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
|
-
import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
11
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
12
10
|
import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, normalizeAccountId, resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
|
|
13
11
|
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
|
14
12
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
15
|
-
import { buildSecretInputSchema, hasConfiguredSecretInput,
|
|
13
|
+
import { buildSecretInputSchema, hasConfiguredSecretInput, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
14
|
+
import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
15
|
+
import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
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
|
-
import { ssrfPolicyFromPrivateNetworkOptIn, ssrfPolicyFromPrivateNetworkOptIn as ssrfPolicyFromPrivateNetworkOptIn$1 } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
21
|
+
import { SsrFBlockedError, 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
|
-
import {
|
|
26
|
-
import { clearAccountEntryFields } from "openclaw/plugin-sdk/channel-plugin-common";
|
|
26
|
+
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
27
27
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2 } from "openclaw/plugin-sdk/account-id";
|
|
28
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";
|
|
34
34
|
import path from "node:path";
|
|
35
|
+
import { fileExists } from "openclaw/plugin-sdk/file-access-runtime";
|
|
35
36
|
import { migratePersistentDedupeLegacyJsonFile, resolvePersistentDedupePluginStateNamespace } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
36
37
|
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
37
38
|
import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
38
39
|
import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
40
|
+
import { safeParseJson, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
39
41
|
import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback } from "openclaw/plugin-sdk/channel-targets";
|
|
40
42
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
41
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
42
43
|
import { createServer } from "node:http";
|
|
43
44
|
import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
44
|
-
import {
|
|
45
|
+
import { sendHttpRequestRejection } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
46
|
+
import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
45
47
|
import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
|
|
46
48
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
47
49
|
import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
|
|
@@ -53,17 +55,19 @@ import { formatDocsLink as formatDocsLink$1 } from "openclaw/plugin-sdk/setup-to
|
|
|
53
55
|
function resolveNextcloudTalkApiCredentialsResult(params) {
|
|
54
56
|
const apiUser = params.apiUser?.trim();
|
|
55
57
|
if (!apiUser) return { status: "missing" };
|
|
56
|
-
const inlinePassword =
|
|
58
|
+
const inlinePassword = resolveSecretInputString({
|
|
57
59
|
value: params.apiPassword,
|
|
58
|
-
path: "channels.nextcloud-talk.apiPassword"
|
|
60
|
+
path: "channels.nextcloud-talk.apiPassword",
|
|
61
|
+
mode: params.mode
|
|
59
62
|
});
|
|
60
|
-
if (inlinePassword) return {
|
|
63
|
+
if (inlinePassword.status === "available") return {
|
|
61
64
|
status: "available",
|
|
62
65
|
value: {
|
|
63
66
|
apiUser,
|
|
64
|
-
apiPassword: inlinePassword
|
|
67
|
+
apiPassword: inlinePassword.value
|
|
65
68
|
}
|
|
66
69
|
};
|
|
70
|
+
if (inlinePassword.status === "configured_unavailable") return { status: "configured_unavailable" };
|
|
67
71
|
if (!params.apiPasswordFile?.trim()) return { status: "missing" };
|
|
68
72
|
const result = tryReadSecretFileSync(params.apiPasswordFile, "Nextcloud Talk API password", { rejectHardlinks: false }, { configPath: params.configPath ?? "channels.nextcloud-talk.apiPasswordFile" });
|
|
69
73
|
if (result.status === "available") return result.value ? {
|
|
@@ -180,15 +184,21 @@ function inspectNextcloudTalkAccount(params) {
|
|
|
180
184
|
apiUser: account.config.apiUser,
|
|
181
185
|
apiPassword: account.config.apiPassword,
|
|
182
186
|
apiPasswordFile: account.config.apiPasswordFile,
|
|
183
|
-
configPath: `channels.nextcloud-talk.accounts.${account.accountId}.apiPasswordFile
|
|
187
|
+
configPath: `channels.nextcloud-talk.accounts.${account.accountId}.apiPasswordFile`,
|
|
188
|
+
mode: "inspect"
|
|
184
189
|
});
|
|
185
|
-
const credentialDiagnostics = [...account.credentialDiagnostics ?? [], ...apiCredentialResolution.status === "configured_unavailable" ? [apiCredentialResolution.diagnostic] : []];
|
|
190
|
+
const credentialDiagnostics = [...account.credentialDiagnostics ?? [], ...apiCredentialResolution.status === "configured_unavailable" && apiCredentialResolution.diagnostic ? [apiCredentialResolution.diagnostic] : []];
|
|
186
191
|
return {
|
|
187
192
|
...account,
|
|
193
|
+
configured: isNextcloudTalkAccountConfigured(account),
|
|
194
|
+
mode: "webhook",
|
|
188
195
|
apiCredentialStatus: apiCredentialResolution.status,
|
|
189
196
|
...credentialDiagnostics.length > 0 ? { credentialDiagnostics } : {}
|
|
190
197
|
};
|
|
191
198
|
}
|
|
199
|
+
function isNextcloudTalkAccountConfigured(account) {
|
|
200
|
+
return Boolean(account.tokenStatus !== "missing" && account.baseUrl?.trim());
|
|
201
|
+
}
|
|
192
202
|
//#endregion
|
|
193
203
|
//#region extensions/nextcloud-talk/src/approval-auth.ts
|
|
194
204
|
function normalizeNextcloudTalkApproverId(value) {
|
|
@@ -210,6 +220,18 @@ async function releaseNextcloudTalkGuardedResponse(params) {
|
|
|
210
220
|
if (!params.response.bodyUsed) await params.response.body?.cancel().catch(() => void 0);
|
|
211
221
|
await params.release();
|
|
212
222
|
}
|
|
223
|
+
async function readNextcloudTalkErrorBody(response, ...credentials) {
|
|
224
|
+
try {
|
|
225
|
+
let body = await readProviderTextResponse(response, "Nextcloud Talk error", {
|
|
226
|
+
maxBytes: 8192,
|
|
227
|
+
chunkTimeoutMs: 1e4
|
|
228
|
+
});
|
|
229
|
+
for (const credential of credentials) body = body.replaceAll(credential, "***");
|
|
230
|
+
return redactToolPayloadText(body);
|
|
231
|
+
} catch {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
213
235
|
//#endregion
|
|
214
236
|
//#region extensions/nextcloud-talk/src/signature.ts
|
|
215
237
|
const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
|
|
@@ -222,7 +244,8 @@ const BACKEND_HEADER = "x-nextcloud-talk-backend";
|
|
|
222
244
|
function verifyNextcloudTalkSignature(params) {
|
|
223
245
|
const { signature, random, body, secret } = params;
|
|
224
246
|
if (!signature || !random || !secret) return false;
|
|
225
|
-
|
|
247
|
+
const expected = createHmac("sha256", secret).update(random + body).digest("hex");
|
|
248
|
+
return safeEqualSecret(signature, expected);
|
|
226
249
|
}
|
|
227
250
|
/**
|
|
228
251
|
* Extract webhook headers from an incoming request.
|
|
@@ -256,7 +279,6 @@ function generateNextcloudTalkSignature(params) {
|
|
|
256
279
|
//#endregion
|
|
257
280
|
//#region extensions/nextcloud-talk/src/bot-preflight.ts
|
|
258
281
|
const BOT_FEATURE_RESPONSE = 2;
|
|
259
|
-
const BOT_PREFLIGHT_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
|
260
282
|
function normalizeUrlForMatch(value) {
|
|
261
283
|
if (!value?.trim()) return "";
|
|
262
284
|
try {
|
|
@@ -321,7 +343,7 @@ async function probeNextcloudTalkBotResponseFeature(params) {
|
|
|
321
343
|
});
|
|
322
344
|
try {
|
|
323
345
|
if (!response.ok) {
|
|
324
|
-
const body = await
|
|
346
|
+
const body = await readNextcloudTalkErrorBody(response, auth, credentials.apiPassword);
|
|
325
347
|
return {
|
|
326
348
|
ok: false,
|
|
327
349
|
code: "api_error",
|
|
@@ -373,7 +395,16 @@ const nextcloudTalkConfigAdapter = createScopedChannelConfigAdapter({
|
|
|
373
395
|
sectionKey: "nextcloud-talk",
|
|
374
396
|
listAccountIds: listNextcloudTalkAccountIds,
|
|
375
397
|
resolveAccount: adaptScopedAccountAccessor(resolveNextcloudTalkAccount),
|
|
376
|
-
inspectAccount:
|
|
398
|
+
inspectAccount: (cfg, accountId) => {
|
|
399
|
+
const account = inspectNextcloudTalkAccount({
|
|
400
|
+
cfg,
|
|
401
|
+
accountId
|
|
402
|
+
});
|
|
403
|
+
return {
|
|
404
|
+
...account,
|
|
405
|
+
baseUrl: account.baseUrl ? "[set]" : "[missing]"
|
|
406
|
+
};
|
|
407
|
+
},
|
|
377
408
|
defaultAccountId: resolveDefaultNextcloudTalkAccountId,
|
|
378
409
|
clearBaseFields: [
|
|
379
410
|
"botSecret",
|
|
@@ -405,7 +436,7 @@ const NextcloudTalkRoomSchema = buildGroupEntrySchema({ allowFrom: z.array(z.str
|
|
|
405
436
|
const NextcloudTalkNetworkSchema = z.object({
|
|
406
437
|
/** Dangerous opt-in for self-hosted Nextcloud Talk on trusted private/internal hosts. */
|
|
407
438
|
dangerouslyAllowPrivateNetwork: z.boolean().optional() }).strict().optional();
|
|
408
|
-
const
|
|
439
|
+
const NextcloudTalkAccountSchemaBase = z.object({
|
|
409
440
|
name: z.string().optional(),
|
|
410
441
|
enabled: z.boolean().optional(),
|
|
411
442
|
configWrites: z.boolean().optional(),
|
|
@@ -429,7 +460,8 @@ const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
|
|
|
429
460
|
/** Network policy overrides for self-hosted Nextcloud Talk on trusted private/internal hosts. */
|
|
430
461
|
network: NextcloudTalkNetworkSchema,
|
|
431
462
|
...ReplyRuntimeConfigSchemaShape
|
|
432
|
-
}).strict()
|
|
463
|
+
}).strict();
|
|
464
|
+
const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(NextcloudTalkAccountSchemaBase, {
|
|
433
465
|
optionalAccount: true,
|
|
434
466
|
refine: (value, ctx) => {
|
|
435
467
|
requireChannelOpenAllowFrom({
|
|
@@ -445,7 +477,7 @@ const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
|
|
|
445
477
|
//#region extensions/nextcloud-talk/src/replay-migration-contract.ts
|
|
446
478
|
const NEXTCLOUD_TALK_PLUGIN_ID = "nextcloud-talk";
|
|
447
479
|
const NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX = "replay-dedupe";
|
|
448
|
-
const NEXTCLOUD_TALK_REPLAY_DEDUPE_TTL_MS =
|
|
480
|
+
const NEXTCLOUD_TALK_REPLAY_DEDUPE_TTL_MS = 864e5;
|
|
449
481
|
const NEXTCLOUD_TALK_REPLAY_DEDUPE_MAX_ENTRIES = 1e4;
|
|
450
482
|
//#endregion
|
|
451
483
|
//#region extensions/nextcloud-talk/src/doctor.ts
|
|
@@ -525,10 +557,12 @@ function normalizeNextcloudTalkAllowlist(values) {
|
|
|
525
557
|
return (values ?? []).map((value) => normalizeNextcloudTalkAllowEntry(String(value))).filter(Boolean);
|
|
526
558
|
}
|
|
527
559
|
function resolveNextcloudTalkAllowlistMatch(params) {
|
|
560
|
+
const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
|
|
561
|
+
const senderId = normalizeNextcloudTalkAllowEntry(params.senderId);
|
|
528
562
|
return resolveAllowlistMatchByCandidates({
|
|
529
|
-
allowList:
|
|
563
|
+
allowList: allowFrom,
|
|
530
564
|
candidates: [{
|
|
531
|
-
value:
|
|
565
|
+
value: senderId,
|
|
532
566
|
source: "id"
|
|
533
567
|
}]
|
|
534
568
|
});
|
|
@@ -596,17 +630,18 @@ function resolveNextcloudTalkGroupRequireMention(params) {
|
|
|
596
630
|
}
|
|
597
631
|
//#endregion
|
|
598
632
|
//#region extensions/nextcloud-talk/src/room-info.ts
|
|
599
|
-
const ROOM_CACHE_TTL_MS =
|
|
600
|
-
const ROOM_CACHE_ERROR_TTL_MS =
|
|
633
|
+
const ROOM_CACHE_TTL_MS = 3e5;
|
|
634
|
+
const ROOM_CACHE_ERROR_TTL_MS = 3e4;
|
|
601
635
|
const ROOM_CACHE_MAX_ENTRIES = 1e3;
|
|
602
636
|
const NEXTCLOUD_TALK_ROOM_INFO_TIMEOUT_MS = 3e4;
|
|
603
637
|
const roomCache = /* @__PURE__ */ new Map();
|
|
604
|
-
function
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
638
|
+
function cacheRoomInfo(key, kind, ttlMs) {
|
|
639
|
+
roomCache.set(key, {
|
|
640
|
+
kind,
|
|
641
|
+
expiresAt: Date.now() + ttlMs
|
|
642
|
+
});
|
|
609
643
|
pruneMapToMaxSize(roomCache, ROOM_CACHE_MAX_ENTRIES);
|
|
644
|
+
return kind;
|
|
610
645
|
}
|
|
611
646
|
function resolveRoomKindFromType(type) {
|
|
612
647
|
if (!type) return;
|
|
@@ -615,29 +650,28 @@ function resolveRoomKindFromType(type) {
|
|
|
615
650
|
}
|
|
616
651
|
async function resolveNextcloudTalkRoomKind(params) {
|
|
617
652
|
const { account, roomToken, runtime } = params;
|
|
618
|
-
const key =
|
|
619
|
-
accountId: account.accountId,
|
|
620
|
-
roomToken
|
|
621
|
-
});
|
|
653
|
+
const key = `${account.accountId}:${roomToken}`;
|
|
622
654
|
const cached = roomCache.get(key);
|
|
623
|
-
if (cached)
|
|
624
|
-
|
|
625
|
-
if (cached.kind && age < ROOM_CACHE_TTL_MS) return cached.kind;
|
|
626
|
-
if (cached.error && age < ROOM_CACHE_ERROR_TTL_MS) return;
|
|
627
|
-
}
|
|
655
|
+
if (cached && Date.now() < cached.expiresAt) return cached.kind;
|
|
656
|
+
roomCache.delete(key);
|
|
628
657
|
const apiCredentials = resolveNextcloudTalkApiCredentials({
|
|
629
658
|
apiUser: account.config.apiUser,
|
|
630
659
|
apiPassword: account.config.apiPassword,
|
|
631
660
|
apiPasswordFile: account.config.apiPasswordFile
|
|
632
661
|
});
|
|
633
|
-
if (!apiCredentials) return;
|
|
634
662
|
const baseUrl = account.baseUrl?.trim();
|
|
635
|
-
if (!baseUrl) return;
|
|
636
|
-
const
|
|
663
|
+
if (!apiCredentials || !baseUrl) return;
|
|
664
|
+
const fallback = (error) => {
|
|
665
|
+
runtime?.error?.(`nextcloud-talk: room lookup error: ${String(error)}`);
|
|
666
|
+
return cacheRoomInfo(key, void 0, ROOM_CACHE_ERROR_TTL_MS);
|
|
667
|
+
};
|
|
668
|
+
const parsedBaseUrl = URL.parse(baseUrl);
|
|
669
|
+
if (!parsedBaseUrl || !["http:", "https:"].includes(parsedBaseUrl.protocol)) return fallback(/* @__PURE__ */ new Error("Nextcloud Talk room lookup requires an HTTP(S) base URL"));
|
|
637
670
|
const auth = Buffer.from(`${apiCredentials.apiUser}:${apiCredentials.apiPassword}`, "utf-8").toString("base64");
|
|
671
|
+
let guarded;
|
|
638
672
|
try {
|
|
639
|
-
|
|
640
|
-
url
|
|
673
|
+
guarded = await fetchWithSsrFGuard({
|
|
674
|
+
url: `${baseUrl}/ocs/v2.php/apps/spreed/api/v4/room/${roomToken}`,
|
|
641
675
|
init: {
|
|
642
676
|
method: "GET",
|
|
643
677
|
headers: {
|
|
@@ -650,34 +684,30 @@ async function resolveNextcloudTalkRoomKind(params) {
|
|
|
650
684
|
policy: ssrfPolicyFromPrivateNetworkOptIn(account.config),
|
|
651
685
|
timeoutMs: params.timeoutMs ?? NEXTCLOUD_TALK_ROOM_INFO_TIMEOUT_MS
|
|
652
686
|
});
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (error instanceof SsrFBlockedError) return fallback(error);
|
|
689
|
+
throw error;
|
|
690
|
+
}
|
|
691
|
+
const { response, release } = guarded;
|
|
692
|
+
try {
|
|
693
|
+
if (!response.ok) {
|
|
694
|
+
const status = response.status;
|
|
695
|
+
if (status === 408 || status === 429 || status >= 500 && status <= 599) throw new Error(`Nextcloud Talk room lookup failed (${status})`);
|
|
696
|
+
runtime?.log?.(`nextcloud-talk: room lookup failed (${status}) token=${roomToken}`);
|
|
697
|
+
return cacheRoomInfo(key, void 0, ROOM_CACHE_ERROR_TTL_MS);
|
|
698
|
+
}
|
|
653
699
|
try {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
runtime?.log?.(`nextcloud-talk: room lookup failed (${response.status}) token=${roomToken}`);
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
const kind = resolveRoomKindFromType(parseStrictPositiveInteger((await readProviderJsonResponse(response, "Nextcloud Talk room info failed")).ocs?.data?.type));
|
|
663
|
-
cacheRoomInfo(key, {
|
|
664
|
-
fetchedAt: Date.now(),
|
|
665
|
-
kind
|
|
666
|
-
});
|
|
667
|
-
return kind;
|
|
668
|
-
} finally {
|
|
669
|
-
await releaseNextcloudTalkGuardedResponse({
|
|
670
|
-
response,
|
|
671
|
-
release
|
|
672
|
-
});
|
|
700
|
+
const payload = await readProviderJsonResponse(response, "Nextcloud Talk room info failed");
|
|
701
|
+
const kind = resolveRoomKindFromType(parseStrictPositiveInteger(payload.ocs?.data?.type));
|
|
702
|
+
return kind ? cacheRoomInfo(key, kind, ROOM_CACHE_TTL_MS) : void 0;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
return fallback(error);
|
|
673
705
|
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
706
|
+
} finally {
|
|
707
|
+
await releaseNextcloudTalkGuardedResponse({
|
|
708
|
+
response,
|
|
709
|
+
release
|
|
678
710
|
});
|
|
679
|
-
runtime?.error?.(`nextcloud-talk: room lookup error: ${String(err)}`);
|
|
680
|
-
return;
|
|
681
711
|
}
|
|
682
712
|
}
|
|
683
713
|
//#endregion
|
|
@@ -705,7 +735,6 @@ function looksLikeNextcloudTalkTargetId(raw) {
|
|
|
705
735
|
}
|
|
706
736
|
//#endregion
|
|
707
737
|
//#region extensions/nextcloud-talk/src/send.ts
|
|
708
|
-
const NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_BYTES = 8 * 1024;
|
|
709
738
|
const NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS = 200;
|
|
710
739
|
const NEXTCLOUD_TALK_SEND_TIMEOUT_MS = 3e4;
|
|
711
740
|
const NEXTCLOUD_TALK_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
@@ -726,20 +755,12 @@ function renderNextcloudTalkMarkdown(markdown) {
|
|
|
726
755
|
escapeText: (text) => text
|
|
727
756
|
}, NEXTCLOUD_TALK_FORMAT_PROFILE);
|
|
728
757
|
}
|
|
729
|
-
/** Collapses
|
|
758
|
+
/** Collapses and caps an already-redacted error body for display. */
|
|
730
759
|
function collapseErrorSnippet(text) {
|
|
731
760
|
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
732
761
|
if (collapsed.length > NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS) return `${truncateUtf16Safe(collapsed, NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS)}…`;
|
|
733
762
|
return collapsed;
|
|
734
763
|
}
|
|
735
|
-
/** Reads a bounded, collapsed error-body snippet without buffering hostile responses. */
|
|
736
|
-
async function readNextcloudTalkErrorSnippet(response) {
|
|
737
|
-
try {
|
|
738
|
-
return collapseErrorSnippet(await readResponseTextLimited(response, NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_BYTES));
|
|
739
|
-
} catch {
|
|
740
|
-
return "";
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
764
|
function resolveCredentials(explicit, account) {
|
|
744
765
|
const baseUrl = explicit.baseUrl?.trim() ?? account.baseUrl;
|
|
745
766
|
const secret = explicit.secret?.trim() ?? account.secret;
|
|
@@ -812,8 +833,9 @@ async function sendMessageNextcloudTalk(to, text, opts) {
|
|
|
812
833
|
body: message,
|
|
813
834
|
secret
|
|
814
835
|
});
|
|
836
|
+
const url = `${baseUrl}/ocs/v2.php/apps/spreed/api/v1/bot/${roomToken}/message`;
|
|
815
837
|
const { response, release } = await fetchWithSsrFGuard({
|
|
816
|
-
url
|
|
838
|
+
url,
|
|
817
839
|
init: {
|
|
818
840
|
method: "POST",
|
|
819
841
|
headers: {
|
|
@@ -830,7 +852,7 @@ async function sendMessageNextcloudTalk(to, text, opts) {
|
|
|
830
852
|
});
|
|
831
853
|
try {
|
|
832
854
|
if (!response.ok) {
|
|
833
|
-
const errorBody = await
|
|
855
|
+
const errorBody = collapseErrorSnippet(await readNextcloudTalkErrorBody(response, signature));
|
|
834
856
|
const status = response.status;
|
|
835
857
|
let errorMsg = `Nextcloud Talk send failed (${status})`;
|
|
836
858
|
if (status === 400) errorMsg = `Nextcloud Talk: bad request - ${errorBody || "invalid message format"}`;
|
|
@@ -874,8 +896,9 @@ async function sendReactionNextcloudTalk(roomToken, messageId, reaction, opts) {
|
|
|
874
896
|
body: reaction,
|
|
875
897
|
secret
|
|
876
898
|
});
|
|
899
|
+
const url = `${baseUrl}/ocs/v2.php/apps/spreed/api/v1/bot/${normalizedToken}/reaction/${messageId}`;
|
|
877
900
|
const { response, release } = await fetchWithSsrFGuard({
|
|
878
|
-
url
|
|
901
|
+
url,
|
|
879
902
|
init: {
|
|
880
903
|
method: "POST",
|
|
881
904
|
headers: {
|
|
@@ -892,7 +915,7 @@ async function sendReactionNextcloudTalk(roomToken, messageId, reaction, opts) {
|
|
|
892
915
|
});
|
|
893
916
|
try {
|
|
894
917
|
if (!response.ok) {
|
|
895
|
-
const errorBody = await
|
|
918
|
+
const errorBody = collapseErrorSnippet(await readNextcloudTalkErrorBody(response, signature));
|
|
896
919
|
throw new Error(`Nextcloud Talk reaction failed: ${response.status} ${errorBody}`.trim());
|
|
897
920
|
}
|
|
898
921
|
return { ok: true };
|
|
@@ -963,7 +986,15 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
963
986
|
accountId: account.accountId
|
|
964
987
|
});
|
|
965
988
|
const rawBody = message.text?.trim() ?? "";
|
|
966
|
-
if (!rawBody)
|
|
989
|
+
if (!rawBody) {
|
|
990
|
+
logInboundDrop({
|
|
991
|
+
log: (messageLocal) => runtime.log?.(messageLocal),
|
|
992
|
+
channel: CHANNEL_ID,
|
|
993
|
+
reason: `empty message body (mediaType=${message.mediaType})`,
|
|
994
|
+
target: message.senderId
|
|
995
|
+
});
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
967
998
|
const roomKind = await resolveNextcloudTalkRoomKind({
|
|
968
999
|
account,
|
|
969
1000
|
roomToken: message.roomToken,
|
|
@@ -984,7 +1015,10 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
984
1015
|
cfg: config,
|
|
985
1016
|
surface: CHANNEL_ID
|
|
986
1017
|
});
|
|
987
|
-
const
|
|
1018
|
+
const structuredBody = rawBody.startsWith("{") ? safeParseJson(rawBody) : void 0;
|
|
1019
|
+
const structuredText = isRecord(structuredBody) && Object.hasOwn(structuredBody, "parameters") ? normalizeOptionalString(structuredBody.message) : void 0;
|
|
1020
|
+
const commandBody = structuredText?.startsWith("/") ? structuredText : rawBody;
|
|
1021
|
+
const hasControlCommand = core.channel.text.hasControlCommand(commandBody, config);
|
|
988
1022
|
const shouldRequireMention = isGroup ? resolveNextcloudTalkGroupRequireMention({
|
|
989
1023
|
cfg: config,
|
|
990
1024
|
accountId: account.accountId,
|
|
@@ -1160,7 +1194,7 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
1160
1194
|
body,
|
|
1161
1195
|
bodyForAgent: rawBody,
|
|
1162
1196
|
rawBody,
|
|
1163
|
-
commandBody
|
|
1197
|
+
commandBody
|
|
1164
1198
|
},
|
|
1165
1199
|
access: {
|
|
1166
1200
|
commands: { authorized: commandAuthorized },
|
|
@@ -1225,7 +1259,7 @@ function parseRawObject(rawEvent) {
|
|
|
1225
1259
|
} catch (error) {
|
|
1226
1260
|
throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook contains invalid JSON.", { cause: error });
|
|
1227
1261
|
}
|
|
1228
|
-
if (!isRecord(parsed)) throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook must be a JSON object.");
|
|
1262
|
+
if (!isRecord$1(parsed)) throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook must be a JSON object.");
|
|
1229
1263
|
return parsed;
|
|
1230
1264
|
}
|
|
1231
1265
|
function requiredString(value, field) {
|
|
@@ -1235,10 +1269,10 @@ function requiredString(value, field) {
|
|
|
1235
1269
|
function inspectNextcloudTalkWebhookEnvelope(rawEvent) {
|
|
1236
1270
|
const envelope = parseRawObject(rawEvent);
|
|
1237
1271
|
if (envelope.type !== "Create") return null;
|
|
1238
|
-
const object = isRecord(envelope.object) ? envelope.object : null;
|
|
1272
|
+
const object = isRecord$1(envelope.object) ? envelope.object : null;
|
|
1239
1273
|
if (object?.type !== void 0 && object.type !== "Note") return null;
|
|
1240
1274
|
if (!object) throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook is missing object.");
|
|
1241
|
-
const target = isRecord(envelope.target) ? envelope.target : null;
|
|
1275
|
+
const target = isRecord$1(envelope.target) ? envelope.target : null;
|
|
1242
1276
|
return {
|
|
1243
1277
|
eventId: requiredString(object.id, "object.id"),
|
|
1244
1278
|
laneKey: `room:${requiredString(target?.id, "target.id")}`
|
|
@@ -1279,8 +1313,8 @@ async function migrateNextcloudTalkLegacyReplayState(params) {
|
|
|
1279
1313
|
}
|
|
1280
1314
|
//#endregion
|
|
1281
1315
|
//#region extensions/nextcloud-talk/src/monitor.ts
|
|
1282
|
-
const DEFAULT_WEBHOOK_MAX_BODY_BYTES =
|
|
1283
|
-
const PREAUTH_WEBHOOK_MAX_BODY_BYTES =
|
|
1316
|
+
const DEFAULT_WEBHOOK_MAX_BODY_BYTES = 1048576;
|
|
1317
|
+
const PREAUTH_WEBHOOK_MAX_BODY_BYTES = 65536;
|
|
1284
1318
|
const NEXTCLOUD_TALK_WEBHOOK_ACCEPTED_HEADER = "x-openclaw-delivery-accepted";
|
|
1285
1319
|
const NEXTCLOUD_TALK_WEBHOOK_ACCEPTED_VALUE = "durable";
|
|
1286
1320
|
const PREAUTH_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
|
|
@@ -1336,9 +1370,14 @@ function verifyWebhookSignature(params) {
|
|
|
1336
1370
|
function readNextcloudTalkWebhookBody(req, maxBodyBytes) {
|
|
1337
1371
|
return readRequestBodyWithLimit(req, {
|
|
1338
1372
|
maxBytes: Math.min(maxBodyBytes, PREAUTH_WEBHOOK_MAX_BODY_BYTES),
|
|
1339
|
-
timeoutMs: PREAUTH_WEBHOOK_BODY_TIMEOUT_MS
|
|
1373
|
+
timeoutMs: PREAUTH_WEBHOOK_BODY_TIMEOUT_MS,
|
|
1374
|
+
destroyOnLimit: false
|
|
1340
1375
|
});
|
|
1341
1376
|
}
|
|
1377
|
+
async function rejectWebhookRequest(req, res, status, error) {
|
|
1378
|
+
if (res.headersSent) return;
|
|
1379
|
+
await sendHttpRequestRejection(req, res, status, JSON.stringify({ error }), "application/json");
|
|
1380
|
+
}
|
|
1342
1381
|
function createNextcloudTalkWebhookServer(opts) {
|
|
1343
1382
|
const { port, host, path, secret, onWebhook, onError, abortSignal } = opts;
|
|
1344
1383
|
const maxBodyBytes = typeof opts.maxBodyBytes === "number" && Number.isFinite(opts.maxBodyBytes) && opts.maxBodyBytes > 0 ? Math.floor(opts.maxBodyBytes) : DEFAULT_WEBHOOK_MAX_BODY_BYTES;
|
|
@@ -1391,11 +1430,11 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1391
1430
|
writeJsonResponse(res, 200);
|
|
1392
1431
|
} catch (err) {
|
|
1393
1432
|
if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
|
|
1394
|
-
|
|
1433
|
+
await rejectWebhookRequest(req, res, 413, WEBHOOK_ERRORS.payloadTooLarge);
|
|
1395
1434
|
return;
|
|
1396
1435
|
}
|
|
1397
1436
|
if (isRequestBodyLimitError(err, "REQUEST_BODY_TIMEOUT")) {
|
|
1398
|
-
|
|
1437
|
+
await rejectWebhookRequest(req, res, 408, requestBodyErrorToText("REQUEST_BODY_TIMEOUT"));
|
|
1399
1438
|
return;
|
|
1400
1439
|
}
|
|
1401
1440
|
if (err instanceof NextcloudTalkWebhookPayloadError) {
|
|
@@ -1439,8 +1478,10 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1439
1478
|
});
|
|
1440
1479
|
});
|
|
1441
1480
|
};
|
|
1442
|
-
if (abortSignal)
|
|
1443
|
-
|
|
1481
|
+
if (abortSignal) {
|
|
1482
|
+
if (abortSignal.aborted) stop();
|
|
1483
|
+
else abortSignal.addEventListener("abort", () => void stop(), { once: true });
|
|
1484
|
+
}
|
|
1444
1485
|
return {
|
|
1445
1486
|
server,
|
|
1446
1487
|
start,
|
|
@@ -1450,6 +1491,12 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1450
1491
|
//#endregion
|
|
1451
1492
|
//#region extensions/nextcloud-talk/src/webhook-spool.ts
|
|
1452
1493
|
const NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS = 500;
|
|
1494
|
+
function describeIgnoredWebhookEvent(rawEvent) {
|
|
1495
|
+
const envelope = parseRawObject(rawEvent);
|
|
1496
|
+
const type = typeof envelope.type === "string" ? envelope.type : "unknown";
|
|
1497
|
+
const object = isRecord$1(envelope.object) ? envelope.object : null;
|
|
1498
|
+
return `type=${type} objectType=${typeof object?.type === "string" ? object.type : "unknown"}`;
|
|
1499
|
+
}
|
|
1453
1500
|
const NextcloudTalkWebhookPayloadSchema = z.object({
|
|
1454
1501
|
type: z.enum([
|
|
1455
1502
|
"Create",
|
|
@@ -1576,7 +1623,11 @@ function createNextcloudTalkWebhookSpool(options) {
|
|
|
1576
1623
|
if (stopping) return Promise.reject(/* @__PURE__ */ new Error("Nextcloud Talk ingress stopped"));
|
|
1577
1624
|
const receiveTask = (async () => {
|
|
1578
1625
|
await startAfterMigration;
|
|
1579
|
-
|
|
1626
|
+
if ((await monitor.admit(rawEvent)).kind === "ignored") {
|
|
1627
|
+
options.runtime.log?.(`nextcloud-talk: ignored non-message webhook event (${describeIgnoredWebhookEvent(rawEvent)})`);
|
|
1628
|
+
return "ignored";
|
|
1629
|
+
}
|
|
1630
|
+
return "accepted";
|
|
1580
1631
|
})();
|
|
1581
1632
|
inFlightReceives.add(receiveTask);
|
|
1582
1633
|
receiveTask.then(() => inFlightReceives.delete(receiveTask), () => inFlightReceives.delete(receiveTask));
|
|
@@ -1720,44 +1771,18 @@ const nextcloudTalkGatewayAdapter = {
|
|
|
1720
1771
|
});
|
|
1721
1772
|
},
|
|
1722
1773
|
logoutAccount: async ({ accountId, cfg }) => {
|
|
1723
|
-
const
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
delete nextSection.botSecret;
|
|
1730
|
-
cleared = true;
|
|
1731
|
-
changed = true;
|
|
1732
|
-
}
|
|
1733
|
-
const accountCleanup = clearAccountEntryFields({
|
|
1734
|
-
accounts: nextSection.accounts,
|
|
1735
|
-
accountId,
|
|
1736
|
-
fields: ["botSecret"]
|
|
1737
|
-
});
|
|
1738
|
-
if (accountCleanup.changed) {
|
|
1739
|
-
changed = true;
|
|
1740
|
-
if (accountCleanup.cleared) cleared = true;
|
|
1741
|
-
if (accountCleanup.nextAccounts) nextSection.accounts = accountCleanup.nextAccounts;
|
|
1742
|
-
else delete nextSection.accounts;
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
if (changed) if (nextSection && Object.keys(nextSection).length > 0) nextCfg.channels = {
|
|
1746
|
-
...nextCfg.channels,
|
|
1747
|
-
"nextcloud-talk": nextSection
|
|
1748
|
-
};
|
|
1749
|
-
else {
|
|
1750
|
-
const nextChannels = { ...nextCfg.channels };
|
|
1751
|
-
delete nextChannels["nextcloud-talk"];
|
|
1752
|
-
if (Object.keys(nextChannels).length > 0) nextCfg.channels = nextChannels;
|
|
1753
|
-
else delete nextCfg.channels;
|
|
1754
|
-
}
|
|
1774
|
+
const { nextConfig, changed, cleared } = clearAccountFieldsFromConfigSection({
|
|
1775
|
+
cfg,
|
|
1776
|
+
sectionKey: "nextcloud-talk",
|
|
1777
|
+
accountId,
|
|
1778
|
+
fields: ["botSecret"]
|
|
1779
|
+
});
|
|
1755
1780
|
const loggedOut = resolveNextcloudTalkAccount({
|
|
1756
|
-
cfg:
|
|
1781
|
+
cfg: nextConfig,
|
|
1757
1782
|
accountId
|
|
1758
1783
|
}).secretSource === "none";
|
|
1759
1784
|
if (changed) await getNextcloudTalkRuntime().config.replaceConfigFile({
|
|
1760
|
-
nextConfig
|
|
1785
|
+
nextConfig,
|
|
1761
1786
|
afterWrite: { mode: "auto" }
|
|
1762
1787
|
});
|
|
1763
1788
|
return {
|
|
@@ -1939,6 +1964,49 @@ const nextcloudTalkDmPolicy = createChannelDmPolicy({
|
|
|
1939
1964
|
applyPatch: ({ cfg, account, patch }) => setNextcloudTalkAccountConfig(cfg, account.accountId, patch),
|
|
1940
1965
|
promptAllowFrom: promptNextcloudTalkAllowFromForAccount
|
|
1941
1966
|
});
|
|
1967
|
+
const nextcloudTalkSetupAdapter = {
|
|
1968
|
+
singleAccountKeysToMove: ["rooms"],
|
|
1969
|
+
resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
|
|
1970
|
+
prepareAccountConfigInput: ({ input }) => {
|
|
1971
|
+
const setupInput = input;
|
|
1972
|
+
return {
|
|
1973
|
+
...setupInput,
|
|
1974
|
+
baseUrl: setupInput.baseUrl ?? readNonEmptyStringPreservingWhitespace(setupInput.url),
|
|
1975
|
+
secret: setupInput.secret ?? readNonEmptyStringPreservingWhitespace(setupInput.token) ?? readNonEmptyStringPreservingWhitespace(setupInput.password),
|
|
1976
|
+
secretFile: setupInput.secretFile ?? readNonEmptyStringPreservingWhitespace(setupInput.tokenFile)
|
|
1977
|
+
};
|
|
1978
|
+
},
|
|
1979
|
+
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
1980
|
+
cfg,
|
|
1981
|
+
channelKey: channel$1,
|
|
1982
|
+
accountId,
|
|
1983
|
+
name
|
|
1984
|
+
}),
|
|
1985
|
+
validateInput: createSetupInputPresenceValidator({
|
|
1986
|
+
defaultAccountOnlyEnvError: "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
|
|
1987
|
+
validate: ({ input }) => {
|
|
1988
|
+
const setupInput = input;
|
|
1989
|
+
if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
|
|
1990
|
+
const normalizedBaseUrl = normalizeNextcloudTalkBaseUrl(setupInput.baseUrl);
|
|
1991
|
+
if (!normalizedBaseUrl) return "Nextcloud Talk requires --base-url.";
|
|
1992
|
+
const baseUrlError = validateNextcloudTalkBaseUrl(normalizedBaseUrl);
|
|
1993
|
+
if (baseUrlError) return baseUrlError;
|
|
1994
|
+
return null;
|
|
1995
|
+
}
|
|
1996
|
+
}),
|
|
1997
|
+
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
1998
|
+
const setupInput = input;
|
|
1999
|
+
return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
|
|
2000
|
+
cfg,
|
|
2001
|
+
channelKey: channel$1,
|
|
2002
|
+
accountId,
|
|
2003
|
+
name: setupInput.name
|
|
2004
|
+
}), accountId, {
|
|
2005
|
+
baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
|
|
2006
|
+
...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
|
|
2007
|
+
}, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
1942
2010
|
const nextcloudTalkSetupContract = defineChannelSetupContract({
|
|
1943
2011
|
fields: {
|
|
1944
2012
|
baseUrl: {
|
|
@@ -2004,49 +2072,7 @@ const nextcloudTalkSetupContract = defineChannelSetupContract({
|
|
|
2004
2072
|
envVars: ["NEXTCLOUD_TALK_BOT_SECRET"]
|
|
2005
2073
|
}
|
|
2006
2074
|
},
|
|
2007
|
-
legacyAdapter:
|
|
2008
|
-
singleAccountKeysToMove: ["rooms"],
|
|
2009
|
-
resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
|
|
2010
|
-
prepareAccountConfigInput: ({ input }) => {
|
|
2011
|
-
const setupInput = input;
|
|
2012
|
-
return {
|
|
2013
|
-
...setupInput,
|
|
2014
|
-
baseUrl: setupInput.baseUrl ?? readNonEmptyStringPreservingWhitespace(setupInput.url),
|
|
2015
|
-
secret: setupInput.secret ?? readNonEmptyStringPreservingWhitespace(setupInput.token) ?? readNonEmptyStringPreservingWhitespace(setupInput.password),
|
|
2016
|
-
secretFile: setupInput.secretFile ?? readNonEmptyStringPreservingWhitespace(setupInput.tokenFile)
|
|
2017
|
-
};
|
|
2018
|
-
},
|
|
2019
|
-
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
2020
|
-
cfg,
|
|
2021
|
-
channelKey: channel$1,
|
|
2022
|
-
accountId,
|
|
2023
|
-
name
|
|
2024
|
-
}),
|
|
2025
|
-
validateInput: createSetupInputPresenceValidator({
|
|
2026
|
-
defaultAccountOnlyEnvError: "NEXTCLOUD_TALK_BOT_SECRET can only be used for the default account.",
|
|
2027
|
-
validate: ({ input }) => {
|
|
2028
|
-
const setupInput = input;
|
|
2029
|
-
if (!setupInput.useEnv && !setupInput.secret && !setupInput.secretFile) return "Nextcloud Talk requires bot secret or --secret-file (or --use-env).";
|
|
2030
|
-
const normalizedBaseUrl = normalizeNextcloudTalkBaseUrl(setupInput.baseUrl);
|
|
2031
|
-
if (!normalizedBaseUrl) return "Nextcloud Talk requires --base-url.";
|
|
2032
|
-
const baseUrlError = validateNextcloudTalkBaseUrl(normalizedBaseUrl);
|
|
2033
|
-
if (baseUrlError) return baseUrlError;
|
|
2034
|
-
return null;
|
|
2035
|
-
}
|
|
2036
|
-
}),
|
|
2037
|
-
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
2038
|
-
const setupInput = input;
|
|
2039
|
-
return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
|
|
2040
|
-
cfg,
|
|
2041
|
-
channelKey: channel$1,
|
|
2042
|
-
accountId,
|
|
2043
|
-
name: setupInput.name
|
|
2044
|
-
}), accountId, {
|
|
2045
|
-
baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
|
|
2046
|
-
...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
|
|
2047
|
-
}, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2075
|
+
legacyAdapter: nextcloudTalkSetupAdapter
|
|
2050
2076
|
});
|
|
2051
2077
|
//#endregion
|
|
2052
2078
|
//#region extensions/nextcloud-talk/src/setup-surface.ts
|
|
@@ -2231,10 +2257,10 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
|
|
|
2231
2257
|
configSchema: buildChannelConfigSchema(NextcloudTalkConfigSchema),
|
|
2232
2258
|
config: {
|
|
2233
2259
|
...nextcloudTalkConfigAdapter,
|
|
2234
|
-
isConfigured:
|
|
2260
|
+
isConfigured: isNextcloudTalkAccountConfigured,
|
|
2235
2261
|
describeAccount: (account) => describeWebhookAccountSnapshot({
|
|
2236
2262
|
account,
|
|
2237
|
-
configured:
|
|
2263
|
+
configured: isNextcloudTalkAccountConfigured(account),
|
|
2238
2264
|
extra: {
|
|
2239
2265
|
secretSource: account.secretSource,
|
|
2240
2266
|
tokenStatus: account.tokenStatus,
|
|
@@ -2290,7 +2316,7 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
|
|
|
2290
2316
|
accountId: account.accountId,
|
|
2291
2317
|
name: account.name,
|
|
2292
2318
|
enabled: account.enabled,
|
|
2293
|
-
configured:
|
|
2319
|
+
configured: isNextcloudTalkAccountConfigured(account),
|
|
2294
2320
|
extra: {
|
|
2295
2321
|
secretSource: account.secretSource,
|
|
2296
2322
|
tokenStatus: account.tokenStatus,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as nextcloudTalkPlugin } from "./channel-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-B3OgVClw.js";
|
|
2
2
|
export { nextcloudTalkPlugin };
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/nextcloud-talk",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.2",
|
|
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.9.
|
|
11
|
+
"openclaw": ">=2026.9.2"
|
|
12
12
|
},
|
|
13
13
|
"peerDependenciesMeta": {
|
|
14
14
|
"openclaw": {
|
|
@@ -129,10 +129,10 @@
|
|
|
129
129
|
"minHostVersion": ">=2026.4.10"
|
|
130
130
|
},
|
|
131
131
|
"compat": {
|
|
132
|
-
"pluginApi": ">=2026.9.
|
|
132
|
+
"pluginApi": ">=2026.9.2"
|
|
133
133
|
},
|
|
134
134
|
"build": {
|
|
135
|
-
"openclawVersion": "2026.9.
|
|
135
|
+
"openclawVersion": "2026.9.2"
|
|
136
136
|
},
|
|
137
137
|
"release": {
|
|
138
138
|
"publishToClawHub": true,
|