@openclaw/nextcloud-talk 2026.7.2-beta.3 → 2026.7.2-beta.5
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/README.md +1 -1
- package/dist/api.js +1 -1
- package/dist/{channel-De2OKnzd.js → channel-D6MTjGj3.js} +457 -272
- package/dist/channel-plugin-api.js +1 -1
- package/openclaw.plugin.json +0 -6
- package/package.json +89 -6
- package/npm-shrinkwrap.json +0 -32
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Official OpenClaw channel plugin for Nextcloud Talk conversations.
|
|
|
5
5
|
Install from OpenClaw:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
openclaw
|
|
8
|
+
openclaw plugins install @openclaw/nextcloud-talk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Configure the Nextcloud server and Talk credentials in OpenClaw, then enable the conversations where agents should receive and send messages.
|
package/dist/api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as nextcloudTalkPlugin } from "./channel-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-D6MTjGj3.js";
|
|
2
2
|
export { nextcloudTalkPlugin };
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { a as fetchWithSsrFGuard, c as resolveDefaultGroupPolicy, i as deliverFormattedTextWithAttachments, l as warnMissingProviderGroupPolicyFallbackOnce, o as logInboundDrop, r as createChannelPairingController, s as resolveAllowlistProviderRuntimeGroupPolicy, t as GROUP_POLICY_BLOCKED_LABEL, u as getNextcloudTalkRuntime } from "./runtime-api-BKsxTw4J.js";
|
|
2
2
|
import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-fNhK1wag.js";
|
|
3
3
|
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DGgvfVqm.js";
|
|
4
|
-
import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
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
7
|
import { createAllowlistProviderRouteAllowlistWarningCollector, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
8
8
|
import { buildWebhookChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
9
|
-
import { convertMarkdownTables, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
9
|
+
import { FormatCapabilityProfile, convertMarkdownTables, renderMarkdownWithMarkers, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
10
10
|
import { createChannelApprovalAuth } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
11
11
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
12
|
-
import { DEFAULT_ACCOUNT_ID,
|
|
12
|
+
import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, normalizeAccountId, resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
|
|
13
13
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
14
14
|
import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
15
15
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
16
16
|
import { parseStrictNonNegativeInteger, parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
17
17
|
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
18
18
|
import { buildChannelInboundEventContext, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
19
|
-
import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
19
|
+
import { bindIngressLifecycleToReplyOptions, createAccountStatusSink, createChannelIngressMonitor, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, resolveChannelStreamingBlockEnabled, runPassiveAccountLifecycle } from "openclaw/plugin-sdk/channel-outbound";
|
|
20
20
|
import { ssrfPolicyFromPrivateNetworkOptIn, ssrfPolicyFromPrivateNetworkOptIn as ssrfPolicyFromPrivateNetworkOptIn$1 } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
21
21
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
22
22
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
@@ -27,11 +27,11 @@ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2 } from "openclaw/plugin-sdk/
|
|
|
27
27
|
import { DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
|
|
28
28
|
import { formatAllowFromLowercase, resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
|
|
29
29
|
import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
30
|
-
import { requireChannelOpenAllowFrom, resolveLoggerBackedRuntime
|
|
30
|
+
import { requireChannelOpenAllowFrom, resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
|
|
31
31
|
import { z } from "zod";
|
|
32
32
|
import os from "node:os";
|
|
33
33
|
import path from "node:path";
|
|
34
|
-
import {
|
|
34
|
+
import { migratePersistentDedupeLegacyJsonFile, resolvePersistentDedupePluginStateNamespace } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
35
35
|
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
36
36
|
import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
37
37
|
import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback } from "openclaw/plugin-sdk/channel-targets";
|
|
@@ -41,6 +41,8 @@ import { createServer } from "node:http";
|
|
|
41
41
|
import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-ingress";
|
|
42
42
|
import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
|
|
43
43
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
44
|
+
import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
|
|
45
|
+
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
44
46
|
import { applyAccountNameToChannelSection, baseUrlTextInput, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
45
47
|
import { createSetupInputPresenceValidator, createSetupTranslator as createSetupTranslator$1, mergeAllowFromEntries, promptParsedAllowFromForAccount, resolveSetupAccountId } from "openclaw/plugin-sdk/setup-runtime";
|
|
46
48
|
import { formatDocsLink as formatDocsLink$1 } from "openclaw/plugin-sdk/setup-tools";
|
|
@@ -83,8 +85,9 @@ function isTruthyEnvValue(value) {
|
|
|
83
85
|
const debugAccounts = (...args) => {
|
|
84
86
|
if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_NEXTCLOUD_TALK_ACCOUNTS)) console.warn("[nextcloud-talk:accounts]", ...args);
|
|
85
87
|
};
|
|
86
|
-
const { listAccountIds: listNextcloudTalkAccountIdsInternal, resolveDefaultAccountId: resolveDefaultNextcloudTalkAccountId } = createAccountListHelpers("nextcloud-talk", {
|
|
88
|
+
const { listAccountIds: listNextcloudTalkAccountIdsInternal, resolveDefaultAccountId: resolveDefaultNextcloudTalkAccountId, resolveAccountConfig: mergeNextcloudTalkAccountConfig } = createAccountListHelpers("nextcloud-talk", {
|
|
87
89
|
normalizeAccountId,
|
|
90
|
+
omitKeys: ["defaultAccount"],
|
|
88
91
|
hasImplicitDefaultAccount: (cfg) => {
|
|
89
92
|
const channel = cfg.channels?.["nextcloud-talk"];
|
|
90
93
|
return Boolean(channel?.baseUrl?.trim() && (hasConfiguredAccountValue(channel.botSecret) || channel.botSecretFile?.trim() || process.env.NEXTCLOUD_TALK_BOT_SECRET?.trim()));
|
|
@@ -95,15 +98,6 @@ function listNextcloudTalkAccountIds(cfg) {
|
|
|
95
98
|
debugAccounts("listNextcloudTalkAccountIds", ids);
|
|
96
99
|
return ids;
|
|
97
100
|
}
|
|
98
|
-
function mergeNextcloudTalkAccountConfig(cfg, accountId) {
|
|
99
|
-
return resolveMergedAccountConfig({
|
|
100
|
-
channelConfig: cfg.channels?.["nextcloud-talk"],
|
|
101
|
-
accounts: cfg.channels?.["nextcloud-talk"]?.accounts,
|
|
102
|
-
accountId,
|
|
103
|
-
omitKeys: ["defaultAccount"],
|
|
104
|
-
normalizeAccountId
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
101
|
function resolveNextcloudTalkSecret(cfg, opts) {
|
|
108
102
|
const resolvedAccountId = opts.accountId ?? resolveDefaultNextcloudTalkAccountId(cfg);
|
|
109
103
|
const merged = mergeNextcloudTalkAccountConfig(cfg, resolvedAccountId);
|
|
@@ -201,6 +195,12 @@ const nextcloudTalkApprovalAuth = createChannelApprovalAuth({
|
|
|
201
195
|
normalizeApprover: normalizeNextcloudTalkApproverId
|
|
202
196
|
}).approvalAuth;
|
|
203
197
|
//#endregion
|
|
198
|
+
//#region extensions/nextcloud-talk/src/guarded-response.ts
|
|
199
|
+
async function releaseNextcloudTalkGuardedResponse(params) {
|
|
200
|
+
if (!params.response.bodyUsed) await params.response.body?.cancel().catch(() => void 0);
|
|
201
|
+
await params.release();
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
204
|
//#region extensions/nextcloud-talk/src/signature.ts
|
|
205
205
|
const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
|
|
206
206
|
const RANDOM_HEADER = "x-nextcloud-talk-random";
|
|
@@ -344,7 +344,10 @@ async function probeNextcloudTalkBotResponseFeature(params) {
|
|
|
344
344
|
message: `Nextcloud Talk bot "${bot.name ?? bot.id ?? "matching bot"}" has the response feature.`
|
|
345
345
|
};
|
|
346
346
|
} finally {
|
|
347
|
-
await
|
|
347
|
+
await releaseNextcloudTalkGuardedResponse({
|
|
348
|
+
response,
|
|
349
|
+
release
|
|
350
|
+
});
|
|
348
351
|
}
|
|
349
352
|
} catch (error) {
|
|
350
353
|
return {
|
|
@@ -426,44 +429,13 @@ const NextcloudTalkConfigSchema = buildMultiAccountChannelSchema(z.object({
|
|
|
426
429
|
}
|
|
427
430
|
});
|
|
428
431
|
//#endregion
|
|
429
|
-
//#region extensions/nextcloud-talk/src/replay-
|
|
432
|
+
//#region extensions/nextcloud-talk/src/replay-migration-contract.ts
|
|
430
433
|
const NEXTCLOUD_TALK_PLUGIN_ID = "nextcloud-talk";
|
|
431
434
|
const NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX = "replay-dedupe";
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
const DEFAULT_STATE_MAX_ENTRIES = 1e4;
|
|
435
|
-
function buildNextcloudTalkReplayKey(params) {
|
|
436
|
-
const roomToken = params.roomToken.trim();
|
|
437
|
-
const messageId = params.messageId.trim();
|
|
438
|
-
if (!roomToken || !messageId) return null;
|
|
439
|
-
return `${roomToken}:${messageId}`;
|
|
440
|
-
}
|
|
441
|
-
function createNextcloudTalkReplayGuard(options) {
|
|
442
|
-
const stateDir = options.stateDir?.trim();
|
|
443
|
-
const baseOptions = {
|
|
444
|
-
ttlMs: options.ttlMs ?? DEFAULT_REPLAY_TTL_MS,
|
|
445
|
-
memoryMaxSize: options.memoryMaxSize ?? DEFAULT_MEMORY_MAX_SIZE
|
|
446
|
-
};
|
|
447
|
-
return createChannelReplayGuard({
|
|
448
|
-
dedupe: stateDir ? {
|
|
449
|
-
...baseOptions,
|
|
450
|
-
pluginId: NEXTCLOUD_TALK_PLUGIN_ID,
|
|
451
|
-
namespacePrefix: NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX,
|
|
452
|
-
stateMaxEntries: options.stateMaxEntries ?? options.fileMaxEntries ?? DEFAULT_STATE_MAX_ENTRIES,
|
|
453
|
-
env: {
|
|
454
|
-
...process.env,
|
|
455
|
-
OPENCLAW_STATE_DIR: stateDir
|
|
456
|
-
},
|
|
457
|
-
onDiskError: options.onDiskError
|
|
458
|
-
} : baseOptions,
|
|
459
|
-
buildReplayKey: buildNextcloudTalkReplayKey,
|
|
460
|
-
namespace: (event) => event.accountId
|
|
461
|
-
});
|
|
462
|
-
}
|
|
435
|
+
const NEXTCLOUD_TALK_REPLAY_DEDUPE_TTL_MS = 1440 * 60 * 1e3;
|
|
436
|
+
const NEXTCLOUD_TALK_REPLAY_DEDUPE_MAX_ENTRIES = 1e4;
|
|
463
437
|
//#endregion
|
|
464
438
|
//#region extensions/nextcloud-talk/src/doctor.ts
|
|
465
|
-
const REPLAY_DEDUPE_TTL_MS = 1440 * 60 * 1e3;
|
|
466
|
-
const REPLAY_DEDUPE_MAX_ENTRIES = 1e4;
|
|
467
439
|
function sanitizeLegacyReplaySegment(value) {
|
|
468
440
|
const trimmed = value.trim();
|
|
469
441
|
if (!trimmed) return "default";
|
|
@@ -498,11 +470,11 @@ async function repairNextcloudTalkReplayDedupeState(params) {
|
|
|
498
470
|
const result = await migratePersistentDedupeLegacyJsonFile({
|
|
499
471
|
filePath: legacyPath,
|
|
500
472
|
namespace: accountId,
|
|
501
|
-
ttlMs:
|
|
473
|
+
ttlMs: NEXTCLOUD_TALK_REPLAY_DEDUPE_TTL_MS,
|
|
502
474
|
memoryMaxSize: 0,
|
|
503
475
|
pluginId: NEXTCLOUD_TALK_PLUGIN_ID,
|
|
504
476
|
namespacePrefix: NEXTCLOUD_TALK_REPLAY_DEDUPE_NAMESPACE_PREFIX,
|
|
505
|
-
stateMaxEntries:
|
|
477
|
+
stateMaxEntries: NEXTCLOUD_TALK_REPLAY_DEDUPE_MAX_ENTRIES,
|
|
506
478
|
env
|
|
507
479
|
});
|
|
508
480
|
changes.push(`Migrated Nextcloud Talk replay dedupe cache for account "${accountId}" to SQLite (${result.imported} imported, ${result.skippedExpired} expired, ${result.skippedExisting} already current).`);
|
|
@@ -685,7 +657,10 @@ async function resolveNextcloudTalkRoomKind(params) {
|
|
|
685
657
|
});
|
|
686
658
|
return kind;
|
|
687
659
|
} finally {
|
|
688
|
-
await
|
|
660
|
+
await releaseNextcloudTalkGuardedResponse({
|
|
661
|
+
response,
|
|
662
|
+
release
|
|
663
|
+
});
|
|
689
664
|
}
|
|
690
665
|
} catch (err) {
|
|
691
666
|
cacheRoomInfo(key, {
|
|
@@ -724,6 +699,24 @@ function looksLikeNextcloudTalkTargetId(raw) {
|
|
|
724
699
|
const NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_BYTES = 8 * 1024;
|
|
725
700
|
const NEXTCLOUD_TALK_ERROR_SNIPPET_MAX_CHARS = 200;
|
|
726
701
|
const NEXTCLOUD_TALK_SEND_TIMEOUT_MS = 3e4;
|
|
702
|
+
const NEXTCLOUD_TALK_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
703
|
+
mechanism: "markdown",
|
|
704
|
+
chunk: {
|
|
705
|
+
limit: 4e3,
|
|
706
|
+
unit: "chars",
|
|
707
|
+
hardCap: 32e3
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
function renderNextcloudTalkMarkdown(markdown) {
|
|
711
|
+
return renderMarkdownWithMarkers({
|
|
712
|
+
text: markdown,
|
|
713
|
+
styles: [],
|
|
714
|
+
links: []
|
|
715
|
+
}, {
|
|
716
|
+
styleMarkers: {},
|
|
717
|
+
escapeText: (text) => text
|
|
718
|
+
}, NEXTCLOUD_TALK_FORMAT_PROFILE);
|
|
719
|
+
}
|
|
727
720
|
/** Collapses whitespace and caps an error-body prefix to a short, log-safe snippet. */
|
|
728
721
|
function collapseErrorSnippet(text) {
|
|
729
722
|
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
@@ -802,7 +795,7 @@ async function sendMessageNextcloudTalk(to, text, opts) {
|
|
|
802
795
|
channel: "nextcloud-talk",
|
|
803
796
|
accountId: account.accountId
|
|
804
797
|
});
|
|
805
|
-
const message = convertMarkdownTables(text.trim(), tableMode);
|
|
798
|
+
const message = convertMarkdownTables(renderNextcloudTalkMarkdown(text.trim()), tableMode);
|
|
806
799
|
const body = { message };
|
|
807
800
|
if (opts.replyTo) body.replyTo = opts.replyTo;
|
|
808
801
|
const bodyStr = JSON.stringify(body);
|
|
@@ -858,7 +851,10 @@ async function sendMessageNextcloudTalk(to, text, opts) {
|
|
|
858
851
|
timestamp
|
|
859
852
|
};
|
|
860
853
|
} finally {
|
|
861
|
-
await
|
|
854
|
+
await releaseNextcloudTalkGuardedResponse({
|
|
855
|
+
response,
|
|
856
|
+
release
|
|
857
|
+
});
|
|
862
858
|
}
|
|
863
859
|
}
|
|
864
860
|
async function sendReactionNextcloudTalk(roomToken, messageId, reaction, opts) {
|
|
@@ -892,7 +888,10 @@ async function sendReactionNextcloudTalk(roomToken, messageId, reaction, opts) {
|
|
|
892
888
|
}
|
|
893
889
|
return { ok: true };
|
|
894
890
|
} finally {
|
|
895
|
-
await
|
|
891
|
+
await releaseNextcloudTalkGuardedResponse({
|
|
892
|
+
response,
|
|
893
|
+
release
|
|
894
|
+
});
|
|
896
895
|
}
|
|
897
896
|
}
|
|
898
897
|
//#endregion
|
|
@@ -1188,6 +1187,7 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
1188
1187
|
},
|
|
1189
1188
|
replyPipeline: {},
|
|
1190
1189
|
replyOptions: {
|
|
1190
|
+
...params.turnAdoptionLifecycle ? bindIngressLifecycleToReplyOptions(params.turnAdoptionLifecycle) : {},
|
|
1191
1191
|
skillFilter: roomConfig?.skills,
|
|
1192
1192
|
disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : void 0
|
|
1193
1193
|
},
|
|
@@ -1196,6 +1196,74 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
1196
1196
|
} }
|
|
1197
1197
|
});
|
|
1198
1198
|
}
|
|
1199
|
+
var NextcloudTalkWebhookPayloadError = class extends Error {
|
|
1200
|
+
constructor(message, options) {
|
|
1201
|
+
super(message, options);
|
|
1202
|
+
this.name = "NextcloudTalkWebhookPayloadError";
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
function isRecord(value) {
|
|
1206
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1207
|
+
}
|
|
1208
|
+
function parseRawObject(rawEvent) {
|
|
1209
|
+
let parsed;
|
|
1210
|
+
try {
|
|
1211
|
+
parsed = JSON.parse(rawEvent);
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook contains invalid JSON.", { cause: error });
|
|
1214
|
+
}
|
|
1215
|
+
if (!isRecord(parsed)) throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook must be a JSON object.");
|
|
1216
|
+
return parsed;
|
|
1217
|
+
}
|
|
1218
|
+
function requiredString(value, field) {
|
|
1219
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1220
|
+
throw new NextcloudTalkWebhookPayloadError(`Nextcloud Talk webhook is missing ${field}.`);
|
|
1221
|
+
}
|
|
1222
|
+
function inspectNextcloudTalkWebhookEnvelope(rawEvent) {
|
|
1223
|
+
const envelope = parseRawObject(rawEvent);
|
|
1224
|
+
if (envelope.type !== "Create") return null;
|
|
1225
|
+
const object = isRecord(envelope.object) ? envelope.object : null;
|
|
1226
|
+
if (object?.type !== void 0 && object.type !== "Note") return null;
|
|
1227
|
+
if (!object) throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook is missing object.");
|
|
1228
|
+
const target = isRecord(envelope.target) ? envelope.target : null;
|
|
1229
|
+
return {
|
|
1230
|
+
eventId: requiredString(object.id, "object.id"),
|
|
1231
|
+
laneKey: `room:${requiredString(target?.id, "target.id")}`
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function parseLegacyReplayKey(key) {
|
|
1235
|
+
const separator = key.lastIndexOf(":");
|
|
1236
|
+
const roomId = key.slice(0, separator).trim();
|
|
1237
|
+
const messageId = key.slice(separator + 1).trim();
|
|
1238
|
+
return separator > 0 && roomId && messageId ? {
|
|
1239
|
+
messageId,
|
|
1240
|
+
roomId
|
|
1241
|
+
} : null;
|
|
1242
|
+
}
|
|
1243
|
+
/** Convert the shipped replay guard's live window into durable completion tombstones. */
|
|
1244
|
+
async function migrateNextcloudTalkLegacyReplayState(params) {
|
|
1245
|
+
const entries = await params.store.entries();
|
|
1246
|
+
let migrated = 0;
|
|
1247
|
+
for (const entry of entries) {
|
|
1248
|
+
const identity = parseLegacyReplayKey(entry.value.key);
|
|
1249
|
+
if (!identity || !Number.isFinite(entry.value.seenAt)) continue;
|
|
1250
|
+
const marker = {
|
|
1251
|
+
version: 1,
|
|
1252
|
+
receivedAt: entry.value.seenAt,
|
|
1253
|
+
rawEvent: ""
|
|
1254
|
+
};
|
|
1255
|
+
const result = await params.queue.enqueue(identity.messageId, marker, {
|
|
1256
|
+
receivedAt: entry.value.seenAt,
|
|
1257
|
+
laneKey: `room:${identity.roomId}`
|
|
1258
|
+
});
|
|
1259
|
+
if (result.kind === "accepted" || result.kind === "pending" && result.record.payload.rawEvent === "") {
|
|
1260
|
+
if (!await params.queue.complete(identity.messageId, { completedAt: entry.value.seenAt })) throw new Error(`Failed to migrate Nextcloud Talk replay key ${entry.value.key}.`);
|
|
1261
|
+
}
|
|
1262
|
+
migrated += 1;
|
|
1263
|
+
}
|
|
1264
|
+
await params.store.clear();
|
|
1265
|
+
return migrated;
|
|
1266
|
+
}
|
|
1199
1267
|
//#endregion
|
|
1200
1268
|
//#region extensions/nextcloud-talk/src/monitor.ts
|
|
1201
1269
|
const DEFAULT_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
|
@@ -1203,34 +1271,6 @@ const PREAUTH_WEBHOOK_MAX_BODY_BYTES = 64 * 1024;
|
|
|
1203
1271
|
const PREAUTH_WEBHOOK_BODY_TIMEOUT_MS = 5e3;
|
|
1204
1272
|
const HEALTH_PATH = "/healthz";
|
|
1205
1273
|
const WEBHOOK_AUTH_RATE_LIMIT_SCOPE = "nextcloud-talk-webhook-auth";
|
|
1206
|
-
const NextcloudTalkWebhookPayloadSchema = z.object({
|
|
1207
|
-
type: z.enum([
|
|
1208
|
-
"Create",
|
|
1209
|
-
"Update",
|
|
1210
|
-
"Delete"
|
|
1211
|
-
]),
|
|
1212
|
-
actor: z.object({
|
|
1213
|
-
type: z.literal("Person"),
|
|
1214
|
-
id: z.string().min(1),
|
|
1215
|
-
name: z.string()
|
|
1216
|
-
}),
|
|
1217
|
-
object: z.object({
|
|
1218
|
-
type: z.literal("Note"),
|
|
1219
|
-
id: z.string().min(1),
|
|
1220
|
-
name: z.string(),
|
|
1221
|
-
content: z.string(),
|
|
1222
|
-
mediaType: z.string()
|
|
1223
|
-
}),
|
|
1224
|
-
target: z.object({
|
|
1225
|
-
type: z.literal("Collection"),
|
|
1226
|
-
id: z.string().min(1),
|
|
1227
|
-
name: z.string()
|
|
1228
|
-
})
|
|
1229
|
-
});
|
|
1230
|
-
const NextcloudTalkWebhookEnvelopeSchema = z.object({
|
|
1231
|
-
type: z.string().min(1),
|
|
1232
|
-
object: z.object({ type: z.string().min(1).optional() }).passthrough().optional()
|
|
1233
|
-
});
|
|
1234
1274
|
const WEBHOOK_ERRORS = {
|
|
1235
1275
|
missingSignatureHeaders: "Missing signature headers",
|
|
1236
1276
|
invalidBackend: "Invalid backend",
|
|
@@ -1239,20 +1279,10 @@ const WEBHOOK_ERRORS = {
|
|
|
1239
1279
|
payloadTooLarge: "Payload too large",
|
|
1240
1280
|
internalServerError: "Internal server error"
|
|
1241
1281
|
};
|
|
1242
|
-
async function processNextcloudTalkReplayGuardedMessage(params) {
|
|
1243
|
-
return (await params.replayGuard.processGuarded({
|
|
1244
|
-
accountId: params.accountId,
|
|
1245
|
-
roomToken: params.message.roomToken,
|
|
1246
|
-
messageId: params.message.messageId
|
|
1247
|
-
}, params.handleMessage, { onError: "commit" })).kind === "processed" ? "processed" : "duplicate";
|
|
1248
|
-
}
|
|
1249
1282
|
function formatError(err) {
|
|
1250
1283
|
if (err instanceof Error) return err.message;
|
|
1251
1284
|
return typeof err === "string" ? err : JSON.stringify(err);
|
|
1252
1285
|
}
|
|
1253
|
-
function parseWebhookPayload(body) {
|
|
1254
|
-
return safeParseJsonWithSchema(NextcloudTalkWebhookPayloadSchema, body);
|
|
1255
|
-
}
|
|
1256
1286
|
function writeJsonResponse(res, status, body) {
|
|
1257
1287
|
if (body) {
|
|
1258
1288
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
@@ -1292,37 +1322,6 @@ function verifyWebhookSignature(params) {
|
|
|
1292
1322
|
params.authRateLimiter.reset(params.clientIp, WEBHOOK_AUTH_RATE_LIMIT_SCOPE);
|
|
1293
1323
|
return true;
|
|
1294
1324
|
}
|
|
1295
|
-
function decodeWebhookCreateMessage(params) {
|
|
1296
|
-
const envelope = safeParseJsonWithSchema(NextcloudTalkWebhookEnvelopeSchema, params.body);
|
|
1297
|
-
if (!envelope) {
|
|
1298
|
-
writeWebhookError(params.res, 400, WEBHOOK_ERRORS.invalidPayloadFormat);
|
|
1299
|
-
return { kind: "invalid" };
|
|
1300
|
-
}
|
|
1301
|
-
if (envelope.type !== "Create") return { kind: "ignore" };
|
|
1302
|
-
if (envelope.object?.type && envelope.object.type !== "Note") return { kind: "ignore" };
|
|
1303
|
-
const payload = parseWebhookPayload(params.body);
|
|
1304
|
-
if (!payload) {
|
|
1305
|
-
writeWebhookError(params.res, 400, WEBHOOK_ERRORS.invalidPayloadFormat);
|
|
1306
|
-
return { kind: "invalid" };
|
|
1307
|
-
}
|
|
1308
|
-
return {
|
|
1309
|
-
kind: "message",
|
|
1310
|
-
message: payloadToInboundMessage(payload)
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
function payloadToInboundMessage(payload) {
|
|
1314
|
-
return {
|
|
1315
|
-
messageId: payload.object.id,
|
|
1316
|
-
roomToken: payload.target.id,
|
|
1317
|
-
roomName: payload.target.name,
|
|
1318
|
-
senderId: payload.actor.id,
|
|
1319
|
-
senderName: payload.actor.name ?? "",
|
|
1320
|
-
text: payload.object.content || payload.object.name || "",
|
|
1321
|
-
mediaType: payload.object.mediaType || "text/plain",
|
|
1322
|
-
timestamp: Date.now(),
|
|
1323
|
-
isGroupChat: true
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
1325
|
function readNextcloudTalkWebhookBody(req, maxBodyBytes) {
|
|
1327
1326
|
return readRequestBodyWithLimit(req, {
|
|
1328
1327
|
maxBytes: Math.min(maxBodyBytes, PREAUTH_WEBHOOK_MAX_BODY_BYTES),
|
|
@@ -1330,12 +1329,10 @@ function readNextcloudTalkWebhookBody(req, maxBodyBytes) {
|
|
|
1330
1329
|
});
|
|
1331
1330
|
}
|
|
1332
1331
|
function createNextcloudTalkWebhookServer(opts) {
|
|
1333
|
-
const { port, host, path, secret,
|
|
1332
|
+
const { port, host, path, secret, onWebhook, onError, abortSignal } = opts;
|
|
1334
1333
|
const maxBodyBytes = typeof opts.maxBodyBytes === "number" && Number.isFinite(opts.maxBodyBytes) && opts.maxBodyBytes > 0 ? Math.floor(opts.maxBodyBytes) : DEFAULT_WEBHOOK_MAX_BODY_BYTES;
|
|
1335
1334
|
const readBody = opts.readBody ?? readNextcloudTalkWebhookBody;
|
|
1336
1335
|
const isBackendAllowed = opts.isBackendAllowed;
|
|
1337
|
-
const shouldProcessMessage = opts.shouldProcessMessage;
|
|
1338
|
-
const processMessage = opts.processMessage;
|
|
1339
1336
|
const authRateLimitMaxRequests = typeof opts.authRateLimit?.maxRequests === "number" ? opts.authRateLimit.maxRequests : WEBHOOK_RATE_LIMIT_DEFAULTS.maxRequests;
|
|
1340
1337
|
const authRateLimitWindowMs = typeof opts.authRateLimit?.windowMs === "number" ? opts.authRateLimit.windowMs : WEBHOOK_RATE_LIMIT_DEFAULTS.windowMs;
|
|
1341
1338
|
const webhookAuthRateLimiter = createAuthRateLimiter({
|
|
@@ -1379,37 +1376,8 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1379
1376
|
clientIp,
|
|
1380
1377
|
authRateLimiter: webhookAuthRateLimiter
|
|
1381
1378
|
})) return;
|
|
1382
|
-
|
|
1383
|
-
body,
|
|
1384
|
-
res
|
|
1385
|
-
});
|
|
1386
|
-
if (decoded.kind === "invalid") return;
|
|
1387
|
-
if (decoded.kind === "ignore") {
|
|
1388
|
-
writeJsonResponse(res, 200);
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
const message = decoded.message;
|
|
1392
|
-
if (processMessage) {
|
|
1393
|
-
writeJsonResponse(res, 200);
|
|
1394
|
-
try {
|
|
1395
|
-
await processMessage(message);
|
|
1396
|
-
} catch (err) {
|
|
1397
|
-
onError?.(err instanceof Error ? err : new Error(formatError(err)));
|
|
1398
|
-
}
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
if (shouldProcessMessage) {
|
|
1402
|
-
if (!await shouldProcessMessage(message)) {
|
|
1403
|
-
writeJsonResponse(res, 200);
|
|
1404
|
-
return;
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1379
|
+
await onWebhook(body);
|
|
1407
1380
|
writeJsonResponse(res, 200);
|
|
1408
|
-
try {
|
|
1409
|
-
await onMessage(message);
|
|
1410
|
-
} catch (err) {
|
|
1411
|
-
onError?.(err instanceof Error ? err : new Error(formatError(err)));
|
|
1412
|
-
}
|
|
1413
1381
|
} catch (err) {
|
|
1414
1382
|
if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
|
|
1415
1383
|
writeWebhookError(res, 413, WEBHOOK_ERRORS.payloadTooLarge);
|
|
@@ -1419,27 +1387,48 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1419
1387
|
writeWebhookError(res, 408, requestBodyErrorToText("REQUEST_BODY_TIMEOUT"));
|
|
1420
1388
|
return;
|
|
1421
1389
|
}
|
|
1390
|
+
if (err instanceof NextcloudTalkWebhookPayloadError) {
|
|
1391
|
+
writeWebhookError(res, 400, WEBHOOK_ERRORS.invalidPayloadFormat);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1422
1394
|
const error = err instanceof Error ? err : new Error(formatError(err));
|
|
1423
1395
|
onError?.(error);
|
|
1424
1396
|
writeWebhookError(res, 500, WEBHOOK_ERRORS.internalServerError);
|
|
1425
1397
|
}
|
|
1426
1398
|
})();
|
|
1427
1399
|
});
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1400
|
+
let stopRequested = false;
|
|
1401
|
+
let closePromise;
|
|
1402
|
+
const closeIfListening = () => {
|
|
1403
|
+
if (closePromise) return closePromise;
|
|
1404
|
+
if (!server.listening) return Promise.resolve();
|
|
1405
|
+
closePromise = new Promise((resolve) => {
|
|
1406
|
+
server.close(() => resolve());
|
|
1407
|
+
}).finally(() => {
|
|
1408
|
+
closePromise = void 0;
|
|
1431
1409
|
});
|
|
1410
|
+
return closePromise;
|
|
1432
1411
|
};
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1412
|
+
const stop = async () => {
|
|
1413
|
+
stopRequested = true;
|
|
1414
|
+
await closeIfListening();
|
|
1415
|
+
};
|
|
1416
|
+
const start = () => {
|
|
1417
|
+
if (stopRequested) return Promise.resolve();
|
|
1418
|
+
return new Promise((resolve, reject) => {
|
|
1419
|
+
const onListenError = (error) => reject(error);
|
|
1420
|
+
server.once("error", onListenError);
|
|
1421
|
+
server.listen(port, host, () => {
|
|
1422
|
+
server.off("error", onListenError);
|
|
1423
|
+
(async () => {
|
|
1424
|
+
if (stopRequested) await closeIfListening();
|
|
1425
|
+
resolve();
|
|
1426
|
+
})().catch(reject);
|
|
1427
|
+
});
|
|
1428
|
+
});
|
|
1440
1429
|
};
|
|
1441
1430
|
if (abortSignal) if (abortSignal.aborted) stop();
|
|
1442
|
-
else abortSignal.addEventListener("abort", stop, { once: true });
|
|
1431
|
+
else abortSignal.addEventListener("abort", () => void stop(), { once: true });
|
|
1443
1432
|
return {
|
|
1444
1433
|
server,
|
|
1445
1434
|
start,
|
|
@@ -1447,6 +1436,164 @@ function createNextcloudTalkWebhookServer(opts) {
|
|
|
1447
1436
|
};
|
|
1448
1437
|
}
|
|
1449
1438
|
//#endregion
|
|
1439
|
+
//#region extensions/nextcloud-talk/src/webhook-spool.ts
|
|
1440
|
+
const NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS = 500;
|
|
1441
|
+
const NEXTCLOUD_TALK_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
|
|
1442
|
+
const NEXTCLOUD_TALK_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
1443
|
+
const NEXTCLOUD_TALK_INGRESS_COMPLETED_MAX_ENTRIES = 1e4;
|
|
1444
|
+
const NEXTCLOUD_TALK_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
1445
|
+
const NEXTCLOUD_TALK_INGRESS_FAILED_MAX_ENTRIES = 1e4;
|
|
1446
|
+
const NextcloudTalkWebhookPayloadSchema = z.object({
|
|
1447
|
+
type: z.enum([
|
|
1448
|
+
"Create",
|
|
1449
|
+
"Update",
|
|
1450
|
+
"Delete"
|
|
1451
|
+
]),
|
|
1452
|
+
actor: z.object({
|
|
1453
|
+
type: z.literal("Person"),
|
|
1454
|
+
id: z.string().min(1),
|
|
1455
|
+
name: z.string()
|
|
1456
|
+
}),
|
|
1457
|
+
object: z.object({
|
|
1458
|
+
type: z.literal("Note"),
|
|
1459
|
+
id: z.string().min(1),
|
|
1460
|
+
name: z.string(),
|
|
1461
|
+
content: z.string(),
|
|
1462
|
+
mediaType: z.string()
|
|
1463
|
+
}),
|
|
1464
|
+
target: z.object({
|
|
1465
|
+
type: z.literal("Collection"),
|
|
1466
|
+
id: z.string().min(1),
|
|
1467
|
+
name: z.string()
|
|
1468
|
+
})
|
|
1469
|
+
});
|
|
1470
|
+
function parseClaimedMessage(payload, claimedId, claimedLaneKey) {
|
|
1471
|
+
if (payload.version !== 1) throw new NextcloudTalkWebhookPayloadError(`Nextcloud Talk ingress row ${claimedId} has an unsupported version.`);
|
|
1472
|
+
const result = NextcloudTalkWebhookPayloadSchema.safeParse(parseRawObject(payload.rawEvent));
|
|
1473
|
+
if (!result.success || result.data.type !== "Create" || result.data.object.id !== claimedId) throw new NextcloudTalkWebhookPayloadError(`Nextcloud Talk ingress row ${claimedId} has invalid message identity.`);
|
|
1474
|
+
const webhook = result.data;
|
|
1475
|
+
const roomId = requiredString(webhook.target.id, "target.id");
|
|
1476
|
+
if (claimedLaneKey !== `room:${roomId}`) throw new NextcloudTalkWebhookPayloadError(`Nextcloud Talk ingress row ${claimedId} changed room identity.`);
|
|
1477
|
+
return {
|
|
1478
|
+
messageId: webhook.object.id,
|
|
1479
|
+
roomToken: roomId,
|
|
1480
|
+
roomName: webhook.target.name,
|
|
1481
|
+
senderId: webhook.actor.id,
|
|
1482
|
+
senderName: webhook.actor.name,
|
|
1483
|
+
text: webhook.object.content || webhook.object.name,
|
|
1484
|
+
mediaType: webhook.object.mediaType || "text/plain",
|
|
1485
|
+
timestamp: payload.receivedAt,
|
|
1486
|
+
isGroupChat: true
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
function resolveNonRetryableFailure(error) {
|
|
1490
|
+
if (error instanceof NextcloudTalkWebhookPayloadError) return {
|
|
1491
|
+
reason: "invalid-event",
|
|
1492
|
+
message: error.message
|
|
1493
|
+
};
|
|
1494
|
+
const message = formatErrorMessage(error);
|
|
1495
|
+
if (message.includes("Nextcloud Talk: bot send was rejected") || message.includes("Nextcloud Talk: forbidden")) return {
|
|
1496
|
+
reason: "nextcloud-talk-auth",
|
|
1497
|
+
message
|
|
1498
|
+
};
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
function createNextcloudTalkWebhookSpool(options) {
|
|
1502
|
+
let queue = options.queue;
|
|
1503
|
+
const getQueue = () => {
|
|
1504
|
+
queue ??= getNextcloudTalkRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
|
|
1505
|
+
return queue;
|
|
1506
|
+
};
|
|
1507
|
+
const legacyReplayStore = options.legacyReplayStore === null ? null : options.legacyReplayStore ?? getNextcloudTalkRuntime().state.openKeyedStore({
|
|
1508
|
+
namespace: resolvePersistentDedupePluginStateNamespace({
|
|
1509
|
+
namespace: options.accountId,
|
|
1510
|
+
namespacePrefix: "replay-dedupe"
|
|
1511
|
+
}),
|
|
1512
|
+
maxEntries: 1e4,
|
|
1513
|
+
defaultTtlMs: 864e5
|
|
1514
|
+
});
|
|
1515
|
+
const legacyMigration = legacyReplayStore ? migrateNextcloudTalkLegacyReplayState({
|
|
1516
|
+
queue: getQueue(),
|
|
1517
|
+
store: legacyReplayStore
|
|
1518
|
+
}) : Promise.resolve();
|
|
1519
|
+
const monitor = createChannelIngressMonitor({
|
|
1520
|
+
queue: getQueue,
|
|
1521
|
+
inspect: (rawEvent) => inspectNextcloudTalkWebhookEnvelope(rawEvent),
|
|
1522
|
+
payload: {
|
|
1523
|
+
version: 1,
|
|
1524
|
+
serialize: (rawEvent, { receivedAt }) => ({
|
|
1525
|
+
receivedAt,
|
|
1526
|
+
rawEvent
|
|
1527
|
+
}),
|
|
1528
|
+
deserialize: (body) => body.rawEvent,
|
|
1529
|
+
encode: ({ body }) => ({
|
|
1530
|
+
version: 1,
|
|
1531
|
+
...body
|
|
1532
|
+
}),
|
|
1533
|
+
decode: (payload) => ({
|
|
1534
|
+
version: payload.version,
|
|
1535
|
+
body: {
|
|
1536
|
+
receivedAt: payload.receivedAt,
|
|
1537
|
+
rawEvent: payload.rawEvent
|
|
1538
|
+
}
|
|
1539
|
+
}),
|
|
1540
|
+
createClaimError: (kind, claim) => new NextcloudTalkWebhookPayloadError(kind === "invalid-version" ? `Nextcloud Talk ingress row ${claim.id} has an unsupported version.` : `Nextcloud Talk ingress row ${claim.id} has invalid message identity.`)
|
|
1541
|
+
},
|
|
1542
|
+
deliver: async (_rawEvent, lifecycle, claim) => {
|
|
1543
|
+
const message = parseClaimedMessage(claim.payload, claim.id, claim.laneKey);
|
|
1544
|
+
await options.deliver(message, lifecycle);
|
|
1545
|
+
},
|
|
1546
|
+
pollIntervalMs: options.pollIntervalMs ?? NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS,
|
|
1547
|
+
waitForDeliveryIdleBeforeRepump: true,
|
|
1548
|
+
retention: {
|
|
1549
|
+
pruneIntervalMs: NEXTCLOUD_TALK_INGRESS_PRUNE_INTERVAL_MS,
|
|
1550
|
+
completedTtlMs: NEXTCLOUD_TALK_INGRESS_COMPLETED_TTL_MS,
|
|
1551
|
+
completedMaxEntries: NEXTCLOUD_TALK_INGRESS_COMPLETED_MAX_ENTRIES,
|
|
1552
|
+
failedTtlMs: NEXTCLOUD_TALK_INGRESS_FAILED_TTL_MS,
|
|
1553
|
+
failedMaxEntries: NEXTCLOUD_TALK_INGRESS_FAILED_MAX_ENTRIES
|
|
1554
|
+
},
|
|
1555
|
+
drain: {
|
|
1556
|
+
resolveNonRetryableFailure,
|
|
1557
|
+
...options.adoptionStallTimeoutMs === void 0 ? {} : { adoptionStallTimeoutMs: options.adoptionStallTimeoutMs },
|
|
1558
|
+
onLog: (message) => options.runtime.log?.(`nextcloud-talk ${message}`)
|
|
1559
|
+
},
|
|
1560
|
+
...options.abortSignal ? { abortSignal: options.abortSignal } : {},
|
|
1561
|
+
createStoppedError: () => /* @__PURE__ */ new Error("Nextcloud Talk ingress stopped"),
|
|
1562
|
+
onError: (error) => options.runtime.error?.(`nextcloud-talk ingress drain failed: ${formatErrorMessage(error)}`)
|
|
1563
|
+
});
|
|
1564
|
+
let stopping = false;
|
|
1565
|
+
const inFlightReceives = /* @__PURE__ */ new Set();
|
|
1566
|
+
const startAfterMigration = legacyMigration.then(() => {
|
|
1567
|
+
if (!stopping) monitor.start();
|
|
1568
|
+
});
|
|
1569
|
+
return {
|
|
1570
|
+
ready: async () => await startAfterMigration,
|
|
1571
|
+
receive: (rawEvent) => {
|
|
1572
|
+
if (stopping) return Promise.reject(/* @__PURE__ */ new Error("Nextcloud Talk ingress stopped"));
|
|
1573
|
+
const receiveTask = (async () => {
|
|
1574
|
+
await startAfterMigration;
|
|
1575
|
+
return (await monitor.admit(rawEvent)).kind === "ignored" ? "ignored" : "accepted";
|
|
1576
|
+
})();
|
|
1577
|
+
inFlightReceives.add(receiveTask);
|
|
1578
|
+
receiveTask.then(() => inFlightReceives.delete(receiveTask), () => inFlightReceives.delete(receiveTask));
|
|
1579
|
+
return receiveTask;
|
|
1580
|
+
},
|
|
1581
|
+
stop: async () => {
|
|
1582
|
+
stopping = true;
|
|
1583
|
+
const pendingReceives = [...inFlightReceives];
|
|
1584
|
+
const pauseTask = monitor.pause();
|
|
1585
|
+
await startAfterMigration.catch(() => void 0);
|
|
1586
|
+
await Promise.allSettled(pendingReceives);
|
|
1587
|
+
await monitor.stop();
|
|
1588
|
+
await pauseTask;
|
|
1589
|
+
},
|
|
1590
|
+
waitForIdle: async () => {
|
|
1591
|
+
await startAfterMigration.catch(() => void 0);
|
|
1592
|
+
await monitor.waitForIdle();
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
//#endregion
|
|
1450
1597
|
//#region extensions/nextcloud-talk/src/monitor-runtime.ts
|
|
1451
1598
|
const DEFAULT_WEBHOOK_PORT = 8788;
|
|
1452
1599
|
const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
|
|
@@ -1475,13 +1622,29 @@ async function monitorNextcloudTalkProvider(opts) {
|
|
|
1475
1622
|
accountId: account.accountId
|
|
1476
1623
|
});
|
|
1477
1624
|
const expectedBackendOrigin = normalizeOrigin(account.baseUrl);
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1625
|
+
const spool = (opts.createSpool ?? createNextcloudTalkWebhookSpool)({
|
|
1626
|
+
accountId: account.accountId,
|
|
1627
|
+
runtime,
|
|
1628
|
+
abortSignal: opts.abortSignal,
|
|
1629
|
+
deliver: async (message, lifecycle) => {
|
|
1630
|
+
core.channel.activity.record({
|
|
1631
|
+
channel: "nextcloud-talk",
|
|
1632
|
+
accountId: account.accountId,
|
|
1633
|
+
direction: "inbound",
|
|
1634
|
+
at: message.timestamp
|
|
1635
|
+
});
|
|
1636
|
+
if (opts.onMessage) await opts.onMessage(message, lifecycle);
|
|
1637
|
+
else await handleNextcloudTalkInbound({
|
|
1638
|
+
message,
|
|
1639
|
+
account,
|
|
1640
|
+
config: cfg,
|
|
1641
|
+
runtime,
|
|
1642
|
+
statusSink: opts.statusSink,
|
|
1643
|
+
turnAdoptionLifecycle: lifecycle
|
|
1644
|
+
});
|
|
1482
1645
|
}
|
|
1483
1646
|
});
|
|
1484
|
-
const
|
|
1647
|
+
const server = (opts.createServer ?? createNextcloudTalkWebhookServer)({
|
|
1485
1648
|
port,
|
|
1486
1649
|
host,
|
|
1487
1650
|
path,
|
|
@@ -1490,39 +1653,34 @@ async function monitorNextcloudTalkProvider(opts) {
|
|
|
1490
1653
|
if (!expectedBackendOrigin) return true;
|
|
1491
1654
|
return normalizeOrigin(backend) === expectedBackendOrigin;
|
|
1492
1655
|
},
|
|
1493
|
-
|
|
1494
|
-
if (await processNextcloudTalkReplayGuardedMessage({
|
|
1495
|
-
replayGuard,
|
|
1496
|
-
accountId: account.accountId,
|
|
1497
|
-
message,
|
|
1498
|
-
handleMessage: async () => {
|
|
1499
|
-
core.channel.activity.record({
|
|
1500
|
-
channel: "nextcloud-talk",
|
|
1501
|
-
accountId: account.accountId,
|
|
1502
|
-
direction: "inbound",
|
|
1503
|
-
at: message.timestamp
|
|
1504
|
-
});
|
|
1505
|
-
if (opts.onMessage) await opts.onMessage(message);
|
|
1506
|
-
else await handleNextcloudTalkInbound({
|
|
1507
|
-
message,
|
|
1508
|
-
account,
|
|
1509
|
-
config: cfg,
|
|
1510
|
-
runtime,
|
|
1511
|
-
statusSink: opts.statusSink
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
}) === "duplicate") logger.warn(`[nextcloud-talk:${account.accountId}] replayed webhook ignored room=${message.roomToken} messageId=${message.messageId}`);
|
|
1515
|
-
},
|
|
1516
|
-
onMessage: async () => {},
|
|
1656
|
+
onWebhook: spool.receive,
|
|
1517
1657
|
onError: (error) => {
|
|
1518
1658
|
logger.error(`[nextcloud-talk:${account.accountId}] webhook error: ${error.message}`);
|
|
1519
1659
|
},
|
|
1520
1660
|
abortSignal: opts.abortSignal
|
|
1521
1661
|
});
|
|
1522
|
-
|
|
1523
|
-
|
|
1662
|
+
let stopPromise;
|
|
1663
|
+
const stop = () => {
|
|
1664
|
+
stopPromise ??= (async () => {
|
|
1665
|
+
await server.stop();
|
|
1666
|
+
await spool.stop();
|
|
1667
|
+
})();
|
|
1668
|
+
return stopPromise;
|
|
1669
|
+
};
|
|
1670
|
+
if (opts.abortSignal && !opts.abortSignal.aborted) opts.abortSignal.addEventListener("abort", () => void stop(), { once: true });
|
|
1524
1671
|
if (opts.abortSignal?.aborted) {
|
|
1525
|
-
stop();
|
|
1672
|
+
await stop();
|
|
1673
|
+
return { stop };
|
|
1674
|
+
}
|
|
1675
|
+
try {
|
|
1676
|
+
await spool.ready();
|
|
1677
|
+
await server.start();
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
await stop();
|
|
1680
|
+
throw error;
|
|
1681
|
+
}
|
|
1682
|
+
if (opts.abortSignal?.aborted) {
|
|
1683
|
+
await stop();
|
|
1526
1684
|
return { stop };
|
|
1527
1685
|
}
|
|
1528
1686
|
const publicUrl = account.config.webhookPublicUrl ?? `http://${host === "0.0.0.0" ? "localhost" : host}:${port}${path}`;
|
|
@@ -1540,7 +1698,7 @@ const nextcloudTalkGatewayAdapter = {
|
|
|
1540
1698
|
accountId: ctx.accountId,
|
|
1541
1699
|
setStatus: ctx.setStatus
|
|
1542
1700
|
});
|
|
1543
|
-
await
|
|
1701
|
+
await runPassiveAccountLifecycle({
|
|
1544
1702
|
abortSignal: ctx.abortSignal,
|
|
1545
1703
|
start: async () => await monitorNextcloudTalkProvider({
|
|
1546
1704
|
accountId: account.accountId,
|
|
@@ -1548,7 +1706,10 @@ const nextcloudTalkGatewayAdapter = {
|
|
|
1548
1706
|
runtime: ctx.runtime,
|
|
1549
1707
|
abortSignal: ctx.abortSignal,
|
|
1550
1708
|
statusSink
|
|
1551
|
-
})
|
|
1709
|
+
}),
|
|
1710
|
+
stop: async (monitor) => {
|
|
1711
|
+
await monitor.stop();
|
|
1712
|
+
}
|
|
1552
1713
|
});
|
|
1553
1714
|
},
|
|
1554
1715
|
logoutAccount: async ({ accountId, cfg }) => {
|
|
@@ -1702,8 +1863,8 @@ function resolveNextcloudTalkOutboundSessionRoute(params) {
|
|
|
1702
1863
|
//#region extensions/nextcloud-talk/src/setup-core.ts
|
|
1703
1864
|
const t$1 = createSetupTranslator$1();
|
|
1704
1865
|
const channel$1 = "nextcloud-talk";
|
|
1705
|
-
function
|
|
1706
|
-
return
|
|
1866
|
+
function readOptionalString(value) {
|
|
1867
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1707
1868
|
}
|
|
1708
1869
|
function normalizeNextcloudTalkBaseUrl(value) {
|
|
1709
1870
|
return value?.trim().replace(/\/+$/, "") ?? "";
|
|
@@ -1712,46 +1873,15 @@ function validateNextcloudTalkBaseUrl(value) {
|
|
|
1712
1873
|
if (!value) return "Required";
|
|
1713
1874
|
if (!value.startsWith("http://") && !value.startsWith("https://")) return "URL must start with http:// or https://";
|
|
1714
1875
|
}
|
|
1715
|
-
function setNextcloudTalkAccountConfig(cfg, accountId, updates) {
|
|
1876
|
+
function setNextcloudTalkAccountConfig(cfg, accountId, updates, clearFields) {
|
|
1716
1877
|
return patchScopedAccountConfig({
|
|
1717
1878
|
cfg,
|
|
1718
1879
|
channelKey: channel$1,
|
|
1719
1880
|
accountId,
|
|
1720
|
-
patch: updates
|
|
1881
|
+
patch: updates,
|
|
1882
|
+
...clearFields ? { clearFields } : {}
|
|
1721
1883
|
});
|
|
1722
1884
|
}
|
|
1723
|
-
function clearNextcloudTalkAccountFields(cfg, accountId, fields) {
|
|
1724
|
-
const section = cfg.channels?.["nextcloud-talk"];
|
|
1725
|
-
if (!section) return cfg;
|
|
1726
|
-
if (accountId === DEFAULT_ACCOUNT_ID$1) {
|
|
1727
|
-
const nextSection = { ...section };
|
|
1728
|
-
for (const field of fields) delete nextSection[field];
|
|
1729
|
-
return {
|
|
1730
|
-
...cfg,
|
|
1731
|
-
channels: {
|
|
1732
|
-
...cfg.channels,
|
|
1733
|
-
"nextcloud-talk": nextSection
|
|
1734
|
-
}
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
const currentAccount = section.accounts?.[accountId];
|
|
1738
|
-
if (!currentAccount) return cfg;
|
|
1739
|
-
const nextAccount = { ...currentAccount };
|
|
1740
|
-
for (const field of fields) delete nextAccount[field];
|
|
1741
|
-
return {
|
|
1742
|
-
...cfg,
|
|
1743
|
-
channels: {
|
|
1744
|
-
...cfg.channels,
|
|
1745
|
-
"nextcloud-talk": {
|
|
1746
|
-
...section,
|
|
1747
|
-
accounts: {
|
|
1748
|
-
...section.accounts,
|
|
1749
|
-
[accountId]: nextAccount
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
};
|
|
1754
|
-
}
|
|
1755
1885
|
async function promptNextcloudTalkAllowFrom(params) {
|
|
1756
1886
|
return await promptParsedAllowFromForAccount({
|
|
1757
1887
|
cfg: params.cfg,
|
|
@@ -1790,37 +1920,28 @@ async function promptNextcloudTalkAllowFromForAccount(params) {
|
|
|
1790
1920
|
accountId
|
|
1791
1921
|
});
|
|
1792
1922
|
}
|
|
1793
|
-
const nextcloudTalkDmPolicy = {
|
|
1923
|
+
const nextcloudTalkDmPolicy = createChannelDmPolicy({
|
|
1794
1924
|
label: "Nextcloud Talk",
|
|
1795
1925
|
channel: channel$1,
|
|
1796
|
-
|
|
1797
|
-
allowFromKey: "channels.nextcloud-talk.allowFrom",
|
|
1798
|
-
resolveConfigKeys: (cfg, accountId) => (accountId ?? resolveDefaultNextcloudTalkAccountId(cfg)) !== DEFAULT_ACCOUNT_ID$1 ? {
|
|
1799
|
-
policyKey: `channels.nextcloud-talk.accounts.${accountId ?? resolveDefaultNextcloudTalkAccountId(cfg)}.dmPolicy`,
|
|
1800
|
-
allowFromKey: `channels.nextcloud-talk.accounts.${accountId ?? resolveDefaultNextcloudTalkAccountId(cfg)}.allowFrom`
|
|
1801
|
-
} : {
|
|
1802
|
-
policyKey: "channels.nextcloud-talk.dmPolicy",
|
|
1803
|
-
allowFromKey: "channels.nextcloud-talk.allowFrom"
|
|
1804
|
-
},
|
|
1805
|
-
getCurrent: (cfg, accountId) => resolveNextcloudTalkAccount({
|
|
1926
|
+
resolveAccount: (cfg, accountId) => resolveNextcloudTalkAccount({
|
|
1806
1927
|
cfg,
|
|
1807
1928
|
accountId: accountId ?? resolveDefaultNextcloudTalkAccountId(cfg)
|
|
1808
|
-
})
|
|
1809
|
-
|
|
1810
|
-
const resolvedAccountId = accountId ?? resolveDefaultNextcloudTalkAccountId(cfg);
|
|
1811
|
-
const resolved = resolveNextcloudTalkAccount({
|
|
1812
|
-
cfg,
|
|
1813
|
-
accountId: resolvedAccountId
|
|
1814
|
-
});
|
|
1815
|
-
return setNextcloudTalkAccountConfig(cfg, resolvedAccountId, {
|
|
1816
|
-
dmPolicy: policy,
|
|
1817
|
-
...policy === "open" ? { allowFrom: addWildcardAllowFrom(resolved.config.allowFrom) } : {}
|
|
1818
|
-
});
|
|
1819
|
-
},
|
|
1929
|
+
}),
|
|
1930
|
+
applyPatch: ({ cfg, account, patch }) => setNextcloudTalkAccountConfig(cfg, account.accountId, patch),
|
|
1820
1931
|
promptAllowFrom: promptNextcloudTalkAllowFromForAccount
|
|
1821
|
-
};
|
|
1932
|
+
});
|
|
1822
1933
|
const nextcloudTalkSetupAdapter = {
|
|
1934
|
+
singleAccountKeysToMove: ["rooms"],
|
|
1823
1935
|
resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
|
|
1936
|
+
prepareAccountConfigInput: ({ input }) => {
|
|
1937
|
+
const setupInput = input;
|
|
1938
|
+
return {
|
|
1939
|
+
...setupInput,
|
|
1940
|
+
baseUrl: setupInput.baseUrl ?? readOptionalString(setupInput.url),
|
|
1941
|
+
secret: setupInput.secret ?? readOptionalString(setupInput.token) ?? readOptionalString(setupInput.password),
|
|
1942
|
+
secretFile: setupInput.secretFile ?? readOptionalString(setupInput.tokenFile)
|
|
1943
|
+
};
|
|
1944
|
+
},
|
|
1824
1945
|
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
1825
1946
|
cfg,
|
|
1826
1947
|
channelKey: channel$1,
|
|
@@ -1838,18 +1959,83 @@ const nextcloudTalkSetupAdapter = {
|
|
|
1838
1959
|
}),
|
|
1839
1960
|
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
1840
1961
|
const setupInput = input;
|
|
1841
|
-
|
|
1962
|
+
return setNextcloudTalkAccountConfig(applyAccountNameToChannelSection({
|
|
1842
1963
|
cfg,
|
|
1843
1964
|
channelKey: channel$1,
|
|
1844
1965
|
accountId,
|
|
1845
1966
|
name: setupInput.name
|
|
1846
|
-
})
|
|
1847
|
-
return setNextcloudTalkAccountConfig(setupInput.useEnv ? clearNextcloudTalkAccountFields(namedConfig, accountId, ["botSecret", "botSecretFile"]) : namedConfig, accountId, {
|
|
1967
|
+
}), accountId, {
|
|
1848
1968
|
baseUrl: normalizeNextcloudTalkBaseUrl(setupInput.baseUrl),
|
|
1849
1969
|
...setupInput.useEnv ? {} : setupInput.secretFile ? { botSecretFile: setupInput.secretFile } : setupInput.secret ? { botSecret: setupInput.secret } : {}
|
|
1850
|
-
});
|
|
1970
|
+
}, setupInput.useEnv ? ["botSecret", "botSecretFile"] : void 0);
|
|
1851
1971
|
}
|
|
1852
1972
|
};
|
|
1973
|
+
const nextcloudTalkSetupContract = defineChannelSetupContract({
|
|
1974
|
+
fields: {
|
|
1975
|
+
baseUrl: {
|
|
1976
|
+
kind: "string",
|
|
1977
|
+
cli: {
|
|
1978
|
+
flags: "--base-url <url>",
|
|
1979
|
+
description: "Nextcloud base URL"
|
|
1980
|
+
}
|
|
1981
|
+
},
|
|
1982
|
+
url: {
|
|
1983
|
+
kind: "string",
|
|
1984
|
+
cli: {
|
|
1985
|
+
flags: "--url <url>",
|
|
1986
|
+
description: "Legacy Nextcloud base URL alias"
|
|
1987
|
+
}
|
|
1988
|
+
},
|
|
1989
|
+
secret: {
|
|
1990
|
+
kind: "string",
|
|
1991
|
+
sensitive: true,
|
|
1992
|
+
cli: {
|
|
1993
|
+
flags: "--secret <secret>",
|
|
1994
|
+
description: "Nextcloud Talk bot secret"
|
|
1995
|
+
}
|
|
1996
|
+
},
|
|
1997
|
+
token: {
|
|
1998
|
+
kind: "string",
|
|
1999
|
+
sensitive: true,
|
|
2000
|
+
cli: {
|
|
2001
|
+
flags: "--token <secret>",
|
|
2002
|
+
description: "Legacy Nextcloud bot secret alias"
|
|
2003
|
+
}
|
|
2004
|
+
},
|
|
2005
|
+
password: {
|
|
2006
|
+
kind: "string",
|
|
2007
|
+
sensitive: true,
|
|
2008
|
+
cli: {
|
|
2009
|
+
flags: "--password <secret>",
|
|
2010
|
+
description: "Legacy Nextcloud bot secret alias"
|
|
2011
|
+
}
|
|
2012
|
+
},
|
|
2013
|
+
secretFile: {
|
|
2014
|
+
kind: "string",
|
|
2015
|
+
sensitive: true,
|
|
2016
|
+
cli: {
|
|
2017
|
+
flags: "--secret-file <path>",
|
|
2018
|
+
description: "Nextcloud Talk bot secret file"
|
|
2019
|
+
}
|
|
2020
|
+
},
|
|
2021
|
+
tokenFile: {
|
|
2022
|
+
kind: "string",
|
|
2023
|
+
sensitive: true,
|
|
2024
|
+
cli: {
|
|
2025
|
+
flags: "--token-file <path>",
|
|
2026
|
+
description: "Legacy Nextcloud bot secret file alias"
|
|
2027
|
+
}
|
|
2028
|
+
},
|
|
2029
|
+
useEnv: {
|
|
2030
|
+
kind: "boolean",
|
|
2031
|
+
cli: {
|
|
2032
|
+
flags: "--use-env",
|
|
2033
|
+
description: "Use Nextcloud Talk environment credentials"
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
},
|
|
2037
|
+
legacyAdapter: nextcloudTalkSetupAdapter
|
|
2038
|
+
});
|
|
1853
2039
|
//#endregion
|
|
1854
2040
|
//#region extensions/nextcloud-talk/src/setup-surface.ts
|
|
1855
2041
|
const t = createSetupTranslator();
|
|
@@ -1926,9 +2112,7 @@ const nextcloudTalkSetupWizard = {
|
|
|
1926
2112
|
hasConfiguredValue: (account) => Boolean(hasConfiguredSecretInput(account.config.botSecret) || account.config.botSecretFile),
|
|
1927
2113
|
resolvedValue: (account) => account.secret || void 0,
|
|
1928
2114
|
envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID$1 ? normalizeOptionalString(process.env.NEXTCLOUD_TALK_BOT_SECRET) : void 0,
|
|
1929
|
-
patchAccount: ({ cfg, accountId, patch, clearFields }) =>
|
|
1930
|
-
return setNextcloudTalkAccountConfig(clearNextcloudTalkAccountFields(cfg, accountId, clearFields), accountId, patch);
|
|
1931
|
-
},
|
|
2115
|
+
patchAccount: ({ cfg, accountId, patch, clearFields }) => setNextcloudTalkAccountConfig(cfg, accountId, patch, clearFields),
|
|
1932
2116
|
useEnv: {
|
|
1933
2117
|
clearFields: ["botSecret", "botSecretFile"],
|
|
1934
2118
|
patch: (account) => ({ baseUrl: account.baseUrl })
|
|
@@ -1951,7 +2135,7 @@ const nextcloudTalkSetupWizard = {
|
|
|
1951
2135
|
accountConfigured: (account) => Boolean(account.config.apiUser?.trim() && (hasConfiguredSecretInput(account.config.apiPassword) || account.config.apiPasswordFile)),
|
|
1952
2136
|
hasConfiguredValue: (account) => Boolean(hasConfiguredSecretInput(account.config.apiPassword) || account.config.apiPasswordFile),
|
|
1953
2137
|
shouldPrompt: ({ credentialValues }) => credentialValues[CONFIGURE_API_FLAG] === "1",
|
|
1954
|
-
patchAccount: ({ cfg, accountId, patch, clearFields }) => setNextcloudTalkAccountConfig(
|
|
2138
|
+
patchAccount: ({ cfg, accountId, patch, clearFields }) => setNextcloudTalkAccountConfig(cfg, accountId, patch, clearFields),
|
|
1955
2139
|
set: { clearFields: ["apiPassword", "apiPasswordFile"] }
|
|
1956
2140
|
})],
|
|
1957
2141
|
textInputs: [baseUrlTextInput({
|
|
@@ -2065,6 +2249,7 @@ const nextcloudTalkPlugin = createChatChannelPlugin({
|
|
|
2065
2249
|
collectRuntimeConfigAssignments
|
|
2066
2250
|
},
|
|
2067
2251
|
setup: nextcloudTalkSetupAdapter,
|
|
2252
|
+
setupContract: nextcloudTalkSetupContract,
|
|
2068
2253
|
status: createComputedAccountStatusAdapter({
|
|
2069
2254
|
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID$2),
|
|
2070
2255
|
buildChannelSummary: ({ snapshot }) => buildWebhookChannelStatusSummary(snapshot, { secretSource: snapshot.secretSource ?? "none" }),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as nextcloudTalkPlugin } from "./channel-
|
|
1
|
+
import { t as nextcloudTalkPlugin } from "./channel-D6MTjGj3.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.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.5",
|
|
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.7.2-beta.
|
|
11
|
+
"openclaw": ">=2026.7.2-beta.5"
|
|
12
12
|
},
|
|
13
13
|
"peerDependenciesMeta": {
|
|
14
14
|
"openclaw": {
|
|
@@ -20,8 +20,19 @@
|
|
|
20
20
|
"./index.ts"
|
|
21
21
|
],
|
|
22
22
|
"setupEntry": "./dist/setup-entry.js",
|
|
23
|
+
"setupFeatures": {
|
|
24
|
+
"configPromotion": true
|
|
25
|
+
},
|
|
23
26
|
"channel": {
|
|
24
27
|
"id": "nextcloud-talk",
|
|
28
|
+
"configuredState": {
|
|
29
|
+
"env": {
|
|
30
|
+
"anyOf": [
|
|
31
|
+
"NEXTCLOUD_TALK_BOT_SECRET",
|
|
32
|
+
"NEXTCLOUD_TALK_API_PASSWORD"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
25
36
|
"label": "Nextcloud Talk",
|
|
26
37
|
"selectionLabel": "Nextcloud Talk (self-hosted)",
|
|
27
38
|
"docsPath": "/channels/nextcloud-talk",
|
|
@@ -32,7 +43,80 @@
|
|
|
32
43
|
"nc"
|
|
33
44
|
],
|
|
34
45
|
"order": 65,
|
|
35
|
-
"quickstartAllowFrom": true
|
|
46
|
+
"quickstartAllowFrom": true,
|
|
47
|
+
"setup": {
|
|
48
|
+
"fields": [
|
|
49
|
+
{
|
|
50
|
+
"key": "baseUrl",
|
|
51
|
+
"kind": "string",
|
|
52
|
+
"cli": {
|
|
53
|
+
"flags": "--base-url <url>",
|
|
54
|
+
"description": "Nextcloud base URL"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"key": "url",
|
|
59
|
+
"kind": "string",
|
|
60
|
+
"cli": {
|
|
61
|
+
"flags": "--url <url>",
|
|
62
|
+
"description": "Legacy Nextcloud base URL alias"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"key": "secret",
|
|
67
|
+
"kind": "string",
|
|
68
|
+
"sensitive": true,
|
|
69
|
+
"cli": {
|
|
70
|
+
"flags": "--secret <secret>",
|
|
71
|
+
"description": "Nextcloud Talk bot secret"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"key": "token",
|
|
76
|
+
"kind": "string",
|
|
77
|
+
"sensitive": true,
|
|
78
|
+
"cli": {
|
|
79
|
+
"flags": "--token <secret>",
|
|
80
|
+
"description": "Legacy Nextcloud bot secret alias"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"key": "password",
|
|
85
|
+
"kind": "string",
|
|
86
|
+
"sensitive": true,
|
|
87
|
+
"cli": {
|
|
88
|
+
"flags": "--password <secret>",
|
|
89
|
+
"description": "Legacy Nextcloud bot secret alias"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"key": "secretFile",
|
|
94
|
+
"kind": "string",
|
|
95
|
+
"sensitive": true,
|
|
96
|
+
"cli": {
|
|
97
|
+
"flags": "--secret-file <path>",
|
|
98
|
+
"description": "Nextcloud Talk bot secret file"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"key": "tokenFile",
|
|
103
|
+
"kind": "string",
|
|
104
|
+
"sensitive": true,
|
|
105
|
+
"cli": {
|
|
106
|
+
"flags": "--token-file <path>",
|
|
107
|
+
"description": "Legacy Nextcloud bot secret file alias"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"key": "useEnv",
|
|
112
|
+
"kind": "boolean",
|
|
113
|
+
"cli": {
|
|
114
|
+
"flags": "--use-env",
|
|
115
|
+
"description": "Use Nextcloud Talk environment credentials"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
36
120
|
},
|
|
37
121
|
"install": {
|
|
38
122
|
"npmSpec": "@openclaw/nextcloud-talk",
|
|
@@ -40,10 +124,10 @@
|
|
|
40
124
|
"minHostVersion": ">=2026.4.10"
|
|
41
125
|
},
|
|
42
126
|
"compat": {
|
|
43
|
-
"pluginApi": ">=2026.7.2-beta.
|
|
127
|
+
"pluginApi": ">=2026.7.2-beta.5"
|
|
44
128
|
},
|
|
45
129
|
"build": {
|
|
46
|
-
"openclawVersion": "2026.7.2-beta.
|
|
130
|
+
"openclawVersion": "2026.7.2-beta.5"
|
|
47
131
|
},
|
|
48
132
|
"release": {
|
|
49
133
|
"publishToClawHub": true,
|
|
@@ -60,7 +144,6 @@
|
|
|
60
144
|
"files": [
|
|
61
145
|
"dist/**",
|
|
62
146
|
"openclaw.plugin.json",
|
|
63
|
-
"npm-shrinkwrap.json",
|
|
64
147
|
"README.md"
|
|
65
148
|
],
|
|
66
149
|
"bundledDependencies": [
|
package/npm-shrinkwrap.json
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@openclaw/nextcloud-talk",
|
|
3
|
-
"version": "2026.7.2-beta.3",
|
|
4
|
-
"lockfileVersion": 3,
|
|
5
|
-
"requires": true,
|
|
6
|
-
"packages": {
|
|
7
|
-
"": {
|
|
8
|
-
"name": "@openclaw/nextcloud-talk",
|
|
9
|
-
"version": "2026.7.2-beta.3",
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"zod": "4.4.3"
|
|
12
|
-
},
|
|
13
|
-
"peerDependencies": {
|
|
14
|
-
"openclaw": ">=2026.7.2-beta.3"
|
|
15
|
-
},
|
|
16
|
-
"peerDependenciesMeta": {
|
|
17
|
-
"openclaw": {
|
|
18
|
-
"optional": true
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"node_modules/zod": {
|
|
23
|
-
"version": "4.4.3",
|
|
24
|
-
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
|
|
25
|
-
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"funding": {
|
|
28
|
-
"url": "https://github.com/sponsors/colinhacks"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|