@openclaw/nextcloud-talk 2026.9.1 → 2026.9.3
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 };
|
|
@@ -11,14 +11,14 @@ import { DEFAULT_ACCOUNT_ID, hasConfiguredAccountValue, normalizeAccountId, reso
|
|
|
11
11
|
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
|
12
12
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
13
13
|
import { buildSecretInputSchema, hasConfiguredSecretInput, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
14
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
|
+
import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
15
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
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
22
|
import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
|
|
23
23
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
24
24
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
@@ -37,13 +37,13 @@ import { migratePersistentDedupeLegacyJsonFile, resolvePersistentDedupePluginSta
|
|
|
37
37
|
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
38
38
|
import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
39
39
|
import { channelIngressRoutes, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
40
|
+
import { safeParseJson, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
40
41
|
import { buildChannelKeyCandidates, normalizeChannelSlug, resolveChannelEntryMatchWithFallback } from "openclaw/plugin-sdk/channel-targets";
|
|
41
42
|
import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
|
|
42
|
-
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
43
43
|
import { createServer } from "node:http";
|
|
44
44
|
import { WEBHOOK_RATE_LIMIT_DEFAULTS, createAuthRateLimiter, isRequestBodyLimitError, readRequestBodyWithLimit, requestBodyErrorToText, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
|
45
45
|
import { sendHttpRequestRejection } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
46
|
-
import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
46
|
+
import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
47
47
|
import { jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
|
|
48
48
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, buildOutboundBaseSessionKey, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
49
49
|
import { createChannelDmPolicy } from "openclaw/plugin-sdk/channel-dm-policy";
|
|
@@ -635,12 +635,13 @@ const ROOM_CACHE_ERROR_TTL_MS = 3e4;
|
|
|
635
635
|
const ROOM_CACHE_MAX_ENTRIES = 1e3;
|
|
636
636
|
const NEXTCLOUD_TALK_ROOM_INFO_TIMEOUT_MS = 3e4;
|
|
637
637
|
const roomCache = /* @__PURE__ */ new Map();
|
|
638
|
-
function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
638
|
+
function cacheRoomInfo(key, kind, ttlMs) {
|
|
639
|
+
roomCache.set(key, {
|
|
640
|
+
kind,
|
|
641
|
+
expiresAt: Date.now() + ttlMs
|
|
642
|
+
});
|
|
643
643
|
pruneMapToMaxSize(roomCache, ROOM_CACHE_MAX_ENTRIES);
|
|
644
|
+
return kind;
|
|
644
645
|
}
|
|
645
646
|
function resolveRoomKindFromType(type) {
|
|
646
647
|
if (!type) return;
|
|
@@ -649,29 +650,28 @@ function resolveRoomKindFromType(type) {
|
|
|
649
650
|
}
|
|
650
651
|
async function resolveNextcloudTalkRoomKind(params) {
|
|
651
652
|
const { account, roomToken, runtime } = params;
|
|
652
|
-
const key =
|
|
653
|
-
accountId: account.accountId,
|
|
654
|
-
roomToken
|
|
655
|
-
});
|
|
653
|
+
const key = `${account.accountId}:${roomToken}`;
|
|
656
654
|
const cached = roomCache.get(key);
|
|
657
|
-
if (cached)
|
|
658
|
-
|
|
659
|
-
if (cached.kind && age < ROOM_CACHE_TTL_MS) return cached.kind;
|
|
660
|
-
if (cached.error && age < ROOM_CACHE_ERROR_TTL_MS) return;
|
|
661
|
-
}
|
|
655
|
+
if (cached && Date.now() < cached.expiresAt) return cached.kind;
|
|
656
|
+
roomCache.delete(key);
|
|
662
657
|
const apiCredentials = resolveNextcloudTalkApiCredentials({
|
|
663
658
|
apiUser: account.config.apiUser,
|
|
664
659
|
apiPassword: account.config.apiPassword,
|
|
665
660
|
apiPasswordFile: account.config.apiPasswordFile
|
|
666
661
|
});
|
|
667
|
-
if (!apiCredentials) return;
|
|
668
662
|
const baseUrl = account.baseUrl?.trim();
|
|
669
|
-
if (!baseUrl) return;
|
|
670
|
-
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"));
|
|
671
670
|
const auth = Buffer.from(`${apiCredentials.apiUser}:${apiCredentials.apiPassword}`, "utf-8").toString("base64");
|
|
671
|
+
let guarded;
|
|
672
672
|
try {
|
|
673
|
-
|
|
674
|
-
url
|
|
673
|
+
guarded = await fetchWithSsrFGuard({
|
|
674
|
+
url: `${baseUrl}/ocs/v2.php/apps/spreed/api/v4/room/${roomToken}`,
|
|
675
675
|
init: {
|
|
676
676
|
method: "GET",
|
|
677
677
|
headers: {
|
|
@@ -684,35 +684,30 @@ async function resolveNextcloudTalkRoomKind(params) {
|
|
|
684
684
|
policy: ssrfPolicyFromPrivateNetworkOptIn(account.config),
|
|
685
685
|
timeoutMs: params.timeoutMs ?? NEXTCLOUD_TALK_ROOM_INFO_TIMEOUT_MS
|
|
686
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
|
+
}
|
|
687
699
|
try {
|
|
688
|
-
if (!response.ok) {
|
|
689
|
-
cacheRoomInfo(key, {
|
|
690
|
-
fetchedAt: Date.now(),
|
|
691
|
-
error: `status:${response.status}`
|
|
692
|
-
});
|
|
693
|
-
runtime?.log?.(`nextcloud-talk: room lookup failed (${response.status}) token=${roomToken}`);
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
700
|
const payload = await readProviderJsonResponse(response, "Nextcloud Talk room info failed");
|
|
697
701
|
const kind = resolveRoomKindFromType(parseStrictPositiveInteger(payload.ocs?.data?.type));
|
|
698
|
-
cacheRoomInfo(key,
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
});
|
|
702
|
-
return kind;
|
|
703
|
-
} finally {
|
|
704
|
-
await releaseNextcloudTalkGuardedResponse({
|
|
705
|
-
response,
|
|
706
|
-
release
|
|
707
|
-
});
|
|
702
|
+
return kind ? cacheRoomInfo(key, kind, ROOM_CACHE_TTL_MS) : void 0;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
return fallback(error);
|
|
708
705
|
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
706
|
+
} finally {
|
|
707
|
+
await releaseNextcloudTalkGuardedResponse({
|
|
708
|
+
response,
|
|
709
|
+
release
|
|
713
710
|
});
|
|
714
|
-
runtime?.error?.(`nextcloud-talk: room lookup error: ${String(err)}`);
|
|
715
|
-
return;
|
|
716
711
|
}
|
|
717
712
|
}
|
|
718
713
|
//#endregion
|
|
@@ -1020,7 +1015,10 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
1020
1015
|
cfg: config,
|
|
1021
1016
|
surface: CHANNEL_ID
|
|
1022
1017
|
});
|
|
1023
|
-
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);
|
|
1024
1022
|
const shouldRequireMention = isGroup ? resolveNextcloudTalkGroupRequireMention({
|
|
1025
1023
|
cfg: config,
|
|
1026
1024
|
accountId: account.accountId,
|
|
@@ -1196,7 +1194,7 @@ async function handleNextcloudTalkInbound(params) {
|
|
|
1196
1194
|
body,
|
|
1197
1195
|
bodyForAgent: rawBody,
|
|
1198
1196
|
rawBody,
|
|
1199
|
-
commandBody
|
|
1197
|
+
commandBody
|
|
1200
1198
|
},
|
|
1201
1199
|
access: {
|
|
1202
1200
|
commands: { authorized: commandAuthorized },
|
|
@@ -1261,7 +1259,7 @@ function parseRawObject(rawEvent) {
|
|
|
1261
1259
|
} catch (error) {
|
|
1262
1260
|
throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook contains invalid JSON.", { cause: error });
|
|
1263
1261
|
}
|
|
1264
|
-
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.");
|
|
1265
1263
|
return parsed;
|
|
1266
1264
|
}
|
|
1267
1265
|
function requiredString(value, field) {
|
|
@@ -1271,10 +1269,10 @@ function requiredString(value, field) {
|
|
|
1271
1269
|
function inspectNextcloudTalkWebhookEnvelope(rawEvent) {
|
|
1272
1270
|
const envelope = parseRawObject(rawEvent);
|
|
1273
1271
|
if (envelope.type !== "Create") return null;
|
|
1274
|
-
const object = isRecord(envelope.object) ? envelope.object : null;
|
|
1272
|
+
const object = isRecord$1(envelope.object) ? envelope.object : null;
|
|
1275
1273
|
if (object?.type !== void 0 && object.type !== "Note") return null;
|
|
1276
1274
|
if (!object) throw new NextcloudTalkWebhookPayloadError("Nextcloud Talk webhook is missing object.");
|
|
1277
|
-
const target = isRecord(envelope.target) ? envelope.target : null;
|
|
1275
|
+
const target = isRecord$1(envelope.target) ? envelope.target : null;
|
|
1278
1276
|
return {
|
|
1279
1277
|
eventId: requiredString(object.id, "object.id"),
|
|
1280
1278
|
laneKey: `room:${requiredString(target?.id, "target.id")}`
|
|
@@ -1496,7 +1494,7 @@ const NEXTCLOUD_TALK_INGRESS_POLL_INTERVAL_MS = 500;
|
|
|
1496
1494
|
function describeIgnoredWebhookEvent(rawEvent) {
|
|
1497
1495
|
const envelope = parseRawObject(rawEvent);
|
|
1498
1496
|
const type = typeof envelope.type === "string" ? envelope.type : "unknown";
|
|
1499
|
-
const object = isRecord(envelope.object) ? envelope.object : null;
|
|
1497
|
+
const object = isRecord$1(envelope.object) ? envelope.object : null;
|
|
1500
1498
|
return `type=${type} objectType=${typeof object?.type === "string" ? object.type : "unknown"}`;
|
|
1501
1499
|
}
|
|
1502
1500
|
const NextcloudTalkWebhookPayloadSchema = z.object({
|
|
@@ -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.3",
|
|
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.3"
|
|
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.3"
|
|
133
133
|
},
|
|
134
134
|
"build": {
|
|
135
|
-
"openclawVersion": "2026.9.
|
|
135
|
+
"openclawVersion": "2026.9.3"
|
|
136
136
|
},
|
|
137
137
|
"release": {
|
|
138
138
|
"publishToClawHub": true,
|