@openclaw/nextcloud-talk 2026.9.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-D2RVrR4p.js";
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 resolveRoomCacheKey(params) {
639
- return `${params.accountId}:${params.roomToken}`;
640
- }
641
- function cacheRoomInfo(key, value) {
642
- roomCache.set(key, value);
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 = resolveRoomCacheKey({
653
- accountId: account.accountId,
654
- roomToken
655
- });
653
+ const key = `${account.accountId}:${roomToken}`;
656
654
  const cached = roomCache.get(key);
657
- if (cached) {
658
- const age = Date.now() - cached.fetchedAt;
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 url = `${baseUrl}/ocs/v2.php/apps/spreed/api/v4/room/${roomToken}`;
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
- const { response, release } = await fetchWithSsrFGuard({
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
- fetchedAt: Date.now(),
700
- kind
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
- } catch (err) {
710
- cacheRoomInfo(key, {
711
- fetchedAt: Date.now(),
712
- error: formatErrorMessage(err)
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 hasControlCommand = core.channel.text.hasControlCommand(rawBody, config);
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: rawBody
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-D2RVrR4p.js";
1
+ import { t as nextcloudTalkPlugin } from "./channel-B3OgVClw.js";
2
2
  export { nextcloudTalkPlugin };
@@ -5,7 +5,6 @@
5
5
  },
6
6
  "name": "Nextcloud Talk",
7
7
  "description": "OpenClaw Nextcloud Talk channel plugin for conversations.",
8
- "icon": "https://cdn.simpleicons.org/nextcloud",
9
8
  "activation": {
10
9
  "onStartup": false
11
10
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.9.1",
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.1"
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.1"
132
+ "pluginApi": ">=2026.9.2"
133
133
  },
134
134
  "build": {
135
- "openclawVersion": "2026.9.1"
135
+ "openclawVersion": "2026.9.2"
136
136
  },
137
137
  "release": {
138
138
  "publishToClawHub": true,