@openclaw/nextcloud-talk 2026.1.29 → 2026.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
-
4
3
  import { nextcloudTalkPlugin } from "./src/channel.js";
5
4
  import { setNextcloudTalkRuntime } from "./src/runtime.js";
6
5
 
@@ -1,8 +1,6 @@
1
1
  {
2
2
  "id": "nextcloud-talk",
3
- "channels": [
4
- "nextcloud-talk"
5
- ],
3
+ "channels": ["nextcloud-talk"],
6
4
  "configSchema": {
7
5
  "type": "object",
8
6
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@openclaw/nextcloud-talk",
3
- "version": "2026.1.29",
4
- "type": "module",
3
+ "version": "2026.2.1",
5
4
  "description": "OpenClaw Nextcloud Talk channel plugin",
5
+ "type": "module",
6
+ "devDependencies": {
7
+ "openclaw": "workspace:*"
8
+ },
6
9
  "openclaw": {
7
10
  "extensions": [
8
11
  "./index.ts"
package/src/accounts.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { readFileSync } from "node:fs";
2
-
3
2
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
4
-
5
3
  import type { CoreConfig, NextcloudTalkAccountConfig } from "./types.js";
6
4
 
7
5
  const TRUTHY_ENV = new Set(["true", "1", "yes", "on"]);
8
6
 
9
7
  function isTruthyEnvValue(value?: string): boolean {
10
- if (!value) return false;
8
+ if (!value) {
9
+ return false;
10
+ }
11
11
  return TRUTHY_ENV.has(value.trim().toLowerCase());
12
12
  }
13
13
 
@@ -29,10 +29,14 @@ export type ResolvedNextcloudTalkAccount = {
29
29
 
30
30
  function listConfiguredAccountIds(cfg: CoreConfig): string[] {
31
31
  const accounts = cfg.channels?.["nextcloud-talk"]?.accounts;
32
- if (!accounts || typeof accounts !== "object") return [];
32
+ if (!accounts || typeof accounts !== "object") {
33
+ return [];
34
+ }
33
35
  const ids = new Set<string>();
34
36
  for (const key of Object.keys(accounts)) {
35
- if (!key) continue;
37
+ if (!key) {
38
+ continue;
39
+ }
36
40
  ids.add(normalizeAccountId(key));
37
41
  }
38
42
  return [...ids];
@@ -41,13 +45,17 @@ function listConfiguredAccountIds(cfg: CoreConfig): string[] {
41
45
  export function listNextcloudTalkAccountIds(cfg: CoreConfig): string[] {
42
46
  const ids = listConfiguredAccountIds(cfg);
43
47
  debugAccounts("listNextcloudTalkAccountIds", ids);
44
- if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
45
- return ids.sort((a, b) => a.localeCompare(b));
48
+ if (ids.length === 0) {
49
+ return [DEFAULT_ACCOUNT_ID];
50
+ }
51
+ return ids.toSorted((a, b) => a.localeCompare(b));
46
52
  }
47
53
 
48
54
  export function resolveDefaultNextcloudTalkAccountId(cfg: CoreConfig): string {
49
55
  const ids = listNextcloudTalkAccountIds(cfg);
50
- if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
56
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
57
+ return DEFAULT_ACCOUNT_ID;
58
+ }
51
59
  return ids[0] ?? DEFAULT_ACCOUNT_ID;
52
60
  }
53
61
 
@@ -56,9 +64,13 @@ function resolveAccountConfig(
56
64
  accountId: string,
57
65
  ): NextcloudTalkAccountConfig | undefined {
58
66
  const accounts = cfg.channels?.["nextcloud-talk"]?.accounts;
59
- if (!accounts || typeof accounts !== "object") return undefined;
67
+ if (!accounts || typeof accounts !== "object") {
68
+ return undefined;
69
+ }
60
70
  const direct = accounts[accountId] as NextcloudTalkAccountConfig | undefined;
61
- if (direct) return direct;
71
+ if (direct) {
72
+ return direct;
73
+ }
62
74
  const normalized = normalizeAccountId(accountId);
63
75
  const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
64
76
  return matchKey ? (accounts[matchKey] as NextcloudTalkAccountConfig | undefined) : undefined;
@@ -88,7 +100,9 @@ function resolveNextcloudTalkSecret(
88
100
  if (merged.botSecretFile) {
89
101
  try {
90
102
  const fileSecret = readFileSync(merged.botSecretFile, "utf-8").trim();
91
- if (fileSecret) return { secret: fileSecret, source: "secretFile" };
103
+ if (fileSecret) {
104
+ return { secret: fileSecret, source: "secretFile" };
105
+ }
92
106
  } catch {
93
107
  // File not found or unreadable, fall through.
94
108
  }
@@ -135,19 +149,25 @@ export function resolveNextcloudTalkAccount(params: {
135
149
 
136
150
  const normalized = normalizeAccountId(params.accountId);
137
151
  const primary = resolve(normalized);
138
- if (hasExplicitAccountId) return primary;
139
- if (primary.secretSource !== "none") return primary;
152
+ if (hasExplicitAccountId) {
153
+ return primary;
154
+ }
155
+ if (primary.secretSource !== "none") {
156
+ return primary;
157
+ }
140
158
 
141
159
  const fallbackId = resolveDefaultNextcloudTalkAccountId(params.cfg);
142
- if (fallbackId === primary.accountId) return primary;
160
+ if (fallbackId === primary.accountId) {
161
+ return primary;
162
+ }
143
163
  const fallback = resolve(fallbackId);
144
- if (fallback.secretSource === "none") return primary;
164
+ if (fallback.secretSource === "none") {
165
+ return primary;
166
+ }
145
167
  return fallback;
146
168
  }
147
169
 
148
- export function listEnabledNextcloudTalkAccounts(
149
- cfg: CoreConfig,
150
- ): ResolvedNextcloudTalkAccount[] {
170
+ export function listEnabledNextcloudTalkAccounts(cfg: CoreConfig): ResolvedNextcloudTalkAccount[] {
151
171
  return listNextcloudTalkAccountIds(cfg)
152
172
  .map((accountId) => resolveNextcloudTalkAccount({ cfg, accountId }))
153
173
  .filter((account) => account.enabled);
package/src/channel.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  type OpenClawConfig,
11
11
  type ChannelSetupInput,
12
12
  } from "openclaw/plugin-sdk";
13
-
13
+ import type { CoreConfig } from "./types.js";
14
14
  import {
15
15
  listNextcloudTalkAccountIds,
16
16
  resolveDefaultNextcloudTalkAccountId,
@@ -19,12 +19,14 @@ import {
19
19
  } from "./accounts.js";
20
20
  import { NextcloudTalkConfigSchema } from "./config-schema.js";
21
21
  import { monitorNextcloudTalkProvider } from "./monitor.js";
22
- import { looksLikeNextcloudTalkTargetId, normalizeNextcloudTalkMessagingTarget } from "./normalize.js";
22
+ import {
23
+ looksLikeNextcloudTalkTargetId,
24
+ normalizeNextcloudTalkMessagingTarget,
25
+ } from "./normalize.js";
23
26
  import { nextcloudTalkOnboardingAdapter } from "./onboarding.js";
27
+ import { resolveNextcloudTalkGroupToolPolicy } from "./policy.js";
24
28
  import { getNextcloudTalkRuntime } from "./runtime.js";
25
29
  import { sendMessageNextcloudTalk } from "./send.js";
26
- import type { CoreConfig } from "./types.js";
27
- import { resolveNextcloudTalkGroupToolPolicy } from "./policy.js";
28
30
 
29
31
  const meta = {
30
32
  id: "nextcloud-talk",
@@ -97,9 +99,9 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
97
99
  baseUrl: account.baseUrl ? "[set]" : "[missing]",
98
100
  }),
99
101
  resolveAllowFrom: ({ cfg, accountId }) =>
100
- (resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []).map(
101
- (entry) => String(entry).toLowerCase(),
102
- ),
102
+ (
103
+ resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId }).config.allowFrom ?? []
104
+ ).map((entry) => String(entry).toLowerCase()),
103
105
  formatAllowFrom: ({ allowFrom }) =>
104
106
  allowFrom
105
107
  .map((entry) => String(entry).trim())
@@ -122,14 +124,15 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
122
124
  policyPath: `${basePath}dmPolicy`,
123
125
  allowFromPath: basePath,
124
126
  approveHint: formatPairingApproveHint("nextcloud-talk"),
125
- normalizeEntry: (raw) =>
126
- raw.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").toLowerCase(),
127
+ normalizeEntry: (raw) => raw.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").toLowerCase(),
127
128
  };
128
129
  },
129
130
  collectWarnings: ({ account, cfg }) => {
130
131
  const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
131
132
  const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
132
- if (groupPolicy !== "open") return [];
133
+ if (groupPolicy !== "open") {
134
+ return [];
135
+ }
133
136
  const roomAllowlistConfigured =
134
137
  account.config.rooms && Object.keys(account.config.rooms).length > 0;
135
138
  if (roomAllowlistConfigured) {
@@ -146,7 +149,9 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
146
149
  resolveRequireMention: ({ cfg, accountId, groupId }) => {
147
150
  const account = resolveNextcloudTalkAccount({ cfg: cfg as CoreConfig, accountId });
148
151
  const rooms = account.config.rooms;
149
- if (!rooms || !groupId) return true;
152
+ if (!rooms || !groupId) {
153
+ return true;
154
+ }
150
155
 
151
156
  const roomConfig = rooms[groupId];
152
157
  if (roomConfig?.requireMention !== undefined) {
@@ -173,7 +178,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
173
178
  resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
174
179
  applyAccountName: ({ cfg, accountId, name }) =>
175
180
  applyAccountNameToChannelSection({
176
- cfg: cfg as OpenClawConfig,
181
+ cfg: cfg,
177
182
  channelKey: "nextcloud-talk",
178
183
  accountId,
179
184
  name,
@@ -194,7 +199,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
194
199
  applyAccountConfig: ({ cfg, accountId, input }) => {
195
200
  const setupInput = input as NextcloudSetupInput;
196
201
  const namedConfig = applyAccountNameToChannelSection({
197
- cfg: cfg as OpenClawConfig,
202
+ cfg: cfg,
198
203
  channelKey: "nextcloud-talk",
199
204
  accountId,
200
205
  name: setupInput.name,
@@ -385,7 +390,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
385
390
  }
386
391
 
387
392
  const resolved = resolveNextcloudTalkAccount({
388
- cfg: (changed ? (nextCfg as CoreConfig) : (cfg as CoreConfig)),
393
+ cfg: changed ? (nextCfg as CoreConfig) : (cfg as CoreConfig),
389
394
  accountId,
390
395
  });
391
396
  const loggedOut = resolved.secretSource === "none";
package/src/format.ts CHANGED
@@ -51,25 +51,25 @@ export function formatNextcloudTalkInlineCode(code: string): string {
51
51
  * Useful for extracting plain text content.
52
52
  */
53
53
  export function stripNextcloudTalkFormatting(text: string): string {
54
- return (
55
- text
56
- .replace(/```[\s\S]*?```/g, "")
57
- .replace(/`[^`]+`/g, "")
58
- .replace(/\*\*([^*]+)\*\*/g, "$1")
59
- .replace(/\*([^*]+)\*/g, "$1")
60
- .replace(/_([^_]+)_/g, "$1")
61
- .replace(/~~([^~]+)~~/g, "$1")
62
- .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
63
- .replace(/\s+/g, " ")
64
- .trim()
65
- );
54
+ return text
55
+ .replace(/```[\s\S]*?```/g, "")
56
+ .replace(/`[^`]+`/g, "")
57
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
58
+ .replace(/\*([^*]+)\*/g, "$1")
59
+ .replace(/_([^_]+)_/g, "$1")
60
+ .replace(/~~([^~]+)~~/g, "$1")
61
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
62
+ .replace(/\s+/g, " ")
63
+ .trim();
66
64
  }
67
65
 
68
66
  /**
69
67
  * Truncate text to a maximum length, preserving word boundaries.
70
68
  */
71
69
  export function truncateNextcloudTalkText(text: string, maxLength: number, suffix = "..."): string {
72
- if (text.length <= maxLength) return text;
70
+ if (text.length <= maxLength) {
71
+ return text;
72
+ }
73
73
  const truncated = text.slice(0, maxLength - suffix.length);
74
74
  const lastSpace = truncated.lastIndexOf(" ");
75
75
  if (lastSpace > maxLength * 0.7) {
package/src/inbound.ts CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  type OpenClawConfig,
5
5
  type RuntimeEnv,
6
6
  } from "openclaw/plugin-sdk";
7
-
8
7
  import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
8
+ import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
9
9
  import {
10
10
  normalizeNextcloudTalkAllowlist,
11
11
  resolveNextcloudTalkAllowlistMatch,
@@ -15,9 +15,8 @@ import {
15
15
  resolveNextcloudTalkRoomMatch,
16
16
  } from "./policy.js";
17
17
  import { resolveNextcloudTalkRoomKind } from "./room-info.js";
18
- import { sendMessageNextcloudTalk } from "./send.js";
19
18
  import { getNextcloudTalkRuntime } from "./runtime.js";
20
- import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js";
19
+ import { sendMessageNextcloudTalk } from "./send.js";
21
20
 
22
21
  const CHANNEL_ID = "nextcloud-talk" as const;
23
22
 
@@ -35,7 +34,9 @@ async function deliverNextcloudTalkReply(params: {
35
34
  ? [payload.mediaUrl]
36
35
  : [];
37
36
 
38
- if (!text.trim() && mediaList.length === 0) return;
37
+ if (!text.trim() && mediaList.length === 0) {
38
+ return;
39
+ }
39
40
 
40
41
  const mediaBlock = mediaList.length
41
42
  ? mediaList.map((url) => `Attachment: ${url}`).join("\n")
@@ -64,15 +65,16 @@ export async function handleNextcloudTalkInbound(params: {
64
65
  const core = getNextcloudTalkRuntime();
65
66
 
66
67
  const rawBody = message.text?.trim() ?? "";
67
- if (!rawBody) return;
68
+ if (!rawBody) {
69
+ return;
70
+ }
68
71
 
69
72
  const roomKind = await resolveNextcloudTalkRoomKind({
70
73
  account,
71
74
  roomToken: message.roomToken,
72
75
  runtime,
73
76
  });
74
- const isGroup =
75
- roomKind === "direct" ? false : roomKind === "group" ? true : message.isGroupChat;
77
+ const isGroup = roomKind === "direct" ? false : roomKind === "group" ? true : message.isGroupChat;
76
78
  const senderId = message.senderId;
77
79
  const senderName = message.senderName;
78
80
  const roomToken = message.roomToken;
@@ -86,9 +88,7 @@ export async function handleNextcloudTalkInbound(params: {
86
88
 
87
89
  const configAllowFrom = normalizeNextcloudTalkAllowlist(account.config.allowFrom);
88
90
  const configGroupAllowFrom = normalizeNextcloudTalkAllowlist(account.config.groupAllowFrom);
89
- const storeAllowFrom = await core.channel.pairing
90
- .readAllowFromStore(CHANNEL_ID)
91
- .catch(() => []);
91
+ const storeAllowFrom = await core.channel.pairing.readAllowFromStore(CHANNEL_ID).catch(() => []);
92
92
  const storeAllowList = normalizeNextcloudTalkAllowlist(storeAllowFrom);
93
93
 
94
94
  const roomMatch = resolveNextcloudTalkRoomMatch({
@@ -123,16 +123,12 @@ export async function handleNextcloudTalkInbound(params: {
123
123
  senderId,
124
124
  senderName,
125
125
  }).allowed;
126
- const hasControlCommand = core.channel.text.hasControlCommand(
127
- rawBody,
128
- config as OpenClawConfig,
129
- );
126
+ const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config as OpenClawConfig);
130
127
  const commandGate = resolveControlCommandGate({
131
128
  useAccessGroups,
132
129
  authorizers: [
133
130
  {
134
- configured:
135
- (isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom).length > 0,
131
+ configured: (isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom).length > 0,
136
132
  allowed: senderAllowedForCommands,
137
133
  },
138
134
  ],
@@ -150,9 +146,7 @@ export async function handleNextcloudTalkInbound(params: {
150
146
  senderName,
151
147
  });
152
148
  if (!groupAllow.allowed) {
153
- runtime.log?.(
154
- `nextcloud-talk: drop group sender ${senderId} (policy=${groupPolicy})`,
155
- );
149
+ runtime.log?.(`nextcloud-talk: drop group sender ${senderId} (policy=${groupPolicy})`);
156
150
  return;
157
151
  }
158
152
  } else {
@@ -192,9 +186,7 @@ export async function handleNextcloudTalkInbound(params: {
192
186
  }
193
187
  }
194
188
  }
195
- runtime.log?.(
196
- `nextcloud-talk: drop DM sender ${senderId} (dmPolicy=${dmPolicy})`,
197
- );
189
+ runtime.log?.(`nextcloud-talk: drop DM sender ${senderId} (dmPolicy=${dmPolicy})`);
198
190
  return;
199
191
  }
200
192
  }
@@ -210,9 +202,7 @@ export async function handleNextcloudTalkInbound(params: {
210
202
  return;
211
203
  }
212
204
 
213
- const mentionRegexes = core.channel.mentions.buildMentionRegexes(
214
- config as OpenClawConfig,
215
- );
205
+ const mentionRegexes = core.channel.mentions.buildMentionRegexes(config as OpenClawConfig);
216
206
  const wasMentioned = mentionRegexes.length
217
207
  ? core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes)
218
208
  : false;
@@ -245,15 +235,11 @@ export async function handleNextcloudTalkInbound(params: {
245
235
  },
246
236
  });
247
237
 
248
- const fromLabel = isGroup
249
- ? `room:${roomName || roomToken}`
250
- : senderName || `user:${senderId}`;
238
+ const fromLabel = isGroup ? `room:${roomName || roomToken}` : senderName || `user:${senderId}`;
251
239
  const storePath = core.channel.session.resolveStorePath(config.session?.store, {
252
240
  agentId: route.agentId,
253
241
  });
254
- const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(
255
- config as OpenClawConfig,
256
- );
242
+ const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config as OpenClawConfig);
257
243
  const previousTimestamp = core.channel.session.readSessionUpdatedAt({
258
244
  storePath,
259
245
  sessionKey: route.sessionKey,
@@ -320,9 +306,7 @@ export async function handleNextcloudTalkInbound(params: {
320
306
  });
321
307
  },
322
308
  onError: (err, info) => {
323
- runtime.error?.(
324
- `nextcloud-talk ${info.kind} reply failed: ${String(err)}`,
325
- );
309
+ runtime.error?.(`nextcloud-talk ${info.kind} reply failed: ${String(err)}`);
326
310
  },
327
311
  },
328
312
  replyOptions: {
package/src/monitor.ts CHANGED
@@ -1,17 +1,15 @@
1
- import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
2
-
3
1
  import type { RuntimeEnv } from "openclaw/plugin-sdk";
4
-
5
- import { resolveNextcloudTalkAccount } from "./accounts.js";
6
- import { handleNextcloudTalkInbound } from "./inbound.js";
7
- import { getNextcloudTalkRuntime } from "./runtime.js";
8
- import { extractNextcloudTalkHeaders, verifyNextcloudTalkSignature } from "./signature.js";
2
+ import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
9
3
  import type {
10
4
  CoreConfig,
11
5
  NextcloudTalkInboundMessage,
12
6
  NextcloudTalkWebhookPayload,
13
7
  NextcloudTalkWebhookServerOptions,
14
8
  } from "./types.js";
9
+ import { resolveNextcloudTalkAccount } from "./accounts.js";
10
+ import { handleNextcloudTalkInbound } from "./inbound.js";
11
+ import { getNextcloudTalkRuntime } from "./runtime.js";
12
+ import { extractNextcloudTalkHeaders, verifyNextcloudTalkSignature } from "./signature.js";
15
13
 
16
14
  const DEFAULT_WEBHOOK_PORT = 8788;
17
15
  const DEFAULT_WEBHOOK_HOST = "0.0.0.0";
@@ -19,7 +17,9 @@ const DEFAULT_WEBHOOK_PATH = "/nextcloud-talk-webhook";
19
17
  const HEALTH_PATH = "/healthz";
20
18
 
21
19
  function formatError(err: unknown): string {
22
- if (err instanceof Error) return err.message;
20
+ if (err instanceof Error) {
21
+ return err.message;
22
+ }
23
23
  return typeof err === "string" ? err : JSON.stringify(err);
24
24
  }
25
25
 
package/src/normalize.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export function normalizeNextcloudTalkMessagingTarget(raw: string): string | undefined {
2
2
  const trimmed = raw.trim();
3
- if (!trimmed) return undefined;
3
+ if (!trimmed) {
4
+ return undefined;
5
+ }
4
6
 
5
7
  let normalized = trimmed;
6
8
 
@@ -16,16 +18,22 @@ export function normalizeNextcloudTalkMessagingTarget(raw: string): string | und
16
18
  normalized = normalized.slice("room:".length).trim();
17
19
  }
18
20
 
19
- if (!normalized) return undefined;
21
+ if (!normalized) {
22
+ return undefined;
23
+ }
20
24
 
21
25
  return `nextcloud-talk:${normalized}`.toLowerCase();
22
26
  }
23
27
 
24
28
  export function looksLikeNextcloudTalkTargetId(raw: string): boolean {
25
29
  const trimmed = raw.trim();
26
- if (!trimmed) return false;
30
+ if (!trimmed) {
31
+ return false;
32
+ }
27
33
 
28
- if (/^(nextcloud-talk|nc-talk|nc):/i.test(trimmed)) return true;
34
+ if (/^(nextcloud-talk|nc-talk|nc):/i.test(trimmed)) {
35
+ return true;
36
+ }
29
37
 
30
38
  return /^[a-z0-9]{8,}$/i.test(trimmed);
31
39
  }
package/src/onboarding.ts CHANGED
@@ -8,13 +8,12 @@ import {
8
8
  type ChannelOnboardingDmPolicy,
9
9
  type WizardPrompter,
10
10
  } from "openclaw/plugin-sdk";
11
-
11
+ import type { CoreConfig, DmPolicy } from "./types.js";
12
12
  import {
13
13
  listNextcloudTalkAccountIds,
14
14
  resolveDefaultNextcloudTalkAccountId,
15
15
  resolveNextcloudTalkAccount,
16
16
  } from "./accounts.js";
17
- import type { CoreConfig, DmPolicy } from "./types.js";
18
17
 
19
18
  const channel = "nextcloud-talk" as const;
20
19
 
@@ -221,7 +220,9 @@ export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
221
220
  message: "Enter Nextcloud instance URL (e.g., https://cloud.example.com)",
222
221
  validate: (value) => {
223
222
  const v = String(value ?? "").trim();
224
- if (!v) return "Required";
223
+ if (!v) {
224
+ return "Required";
225
+ }
225
226
  if (!v.startsWith("http://") && !v.startsWith("https://")) {
226
227
  return "URL must start with http:// or https://";
227
228
  }
@@ -309,7 +310,8 @@ export const nextcloudTalkOnboardingAdapter: ChannelOnboardingAdapter = {
309
310
  ...next.channels?.["nextcloud-talk"]?.accounts,
310
311
  [accountId]: {
311
312
  ...next.channels?.["nextcloud-talk"]?.accounts?.[accountId],
312
- enabled: next.channels?.["nextcloud-talk"]?.accounts?.[accountId]?.enabled ?? true,
313
+ enabled:
314
+ next.channels?.["nextcloud-talk"]?.accounts?.[accountId]?.enabled ?? true,
313
315
  baseUrl,
314
316
  ...(secret ? { botSecret: secret } : {}),
315
317
  },
package/src/policy.ts CHANGED
@@ -1,4 +1,9 @@
1
- import type { AllowlistMatch, ChannelGroupContext, GroupPolicy, GroupToolPolicyConfig } from "openclaw/plugin-sdk";
1
+ import type {
2
+ AllowlistMatch,
3
+ ChannelGroupContext,
4
+ GroupPolicy,
5
+ GroupToolPolicyConfig,
6
+ } from "openclaw/plugin-sdk";
2
7
  import {
3
8
  buildChannelKeyCandidates,
4
9
  normalizeChannelSlug,
@@ -6,11 +11,13 @@ import {
6
11
  resolveMentionGatingWithBypass,
7
12
  resolveNestedAllowlistDecision,
8
13
  } from "openclaw/plugin-sdk";
9
-
10
14
  import type { NextcloudTalkRoomConfig } from "./types.js";
11
15
 
12
16
  function normalizeAllowEntry(raw: string): string {
13
- return raw.trim().toLowerCase().replace(/^(nextcloud-talk|nc-talk|nc):/i, "");
17
+ return raw
18
+ .trim()
19
+ .toLowerCase()
20
+ .replace(/^(nextcloud-talk|nc-talk|nc):/i, "");
14
21
  }
15
22
 
16
23
  export function normalizeNextcloudTalkAllowlist(
@@ -25,7 +32,9 @@ export function resolveNextcloudTalkAllowlistMatch(params: {
25
32
  senderName?: string | null;
26
33
  }): AllowlistMatch<"wildcard" | "id" | "name"> {
27
34
  const allowFrom = normalizeNextcloudTalkAllowlist(params.allowFrom);
28
- if (allowFrom.length === 0) return { allowed: false };
35
+ if (allowFrom.length === 0) {
36
+ return { allowed: false };
37
+ }
29
38
  if (allowFrom.includes("*")) {
30
39
  return { allowed: true, matchKey: "*", matchSource: "wildcard" };
31
40
  }
@@ -89,9 +98,13 @@ export function resolveNextcloudTalkRoomMatch(params: {
89
98
  export function resolveNextcloudTalkGroupToolPolicy(
90
99
  params: ChannelGroupContext,
91
100
  ): GroupToolPolicyConfig | undefined {
92
- const cfg = params.cfg as { channels?: { "nextcloud-talk"?: { rooms?: Record<string, NextcloudTalkRoomConfig> } } };
101
+ const cfg = params.cfg as {
102
+ channels?: { "nextcloud-talk"?: { rooms?: Record<string, NextcloudTalkRoomConfig> } };
103
+ };
93
104
  const roomToken = params.groupId?.trim();
94
- if (!roomToken) return undefined;
105
+ if (!roomToken) {
106
+ return undefined;
107
+ }
95
108
  const roomName = params.groupChannel?.trim() || undefined;
96
109
  const match = resolveNextcloudTalkRoomMatch({
97
110
  rooms: cfg.channels?.["nextcloud-talk"]?.rooms,
package/src/room-info.ts CHANGED
@@ -1,7 +1,5 @@
1
- import { readFileSync } from "node:fs";
2
-
3
1
  import type { RuntimeEnv } from "openclaw/plugin-sdk";
4
-
2
+ import { readFileSync } from "node:fs";
5
3
  import type { ResolvedNextcloudTalkAccount } from "./accounts.js";
6
4
 
7
5
  const ROOM_CACHE_TTL_MS = 5 * 60 * 1000;
@@ -20,8 +18,12 @@ function readApiPassword(params: {
20
18
  apiPassword?: string;
21
19
  apiPasswordFile?: string;
22
20
  }): string | undefined {
23
- if (params.apiPassword?.trim()) return params.apiPassword.trim();
24
- if (!params.apiPasswordFile) return undefined;
21
+ if (params.apiPassword?.trim()) {
22
+ return params.apiPassword.trim();
23
+ }
24
+ if (!params.apiPasswordFile) {
25
+ return undefined;
26
+ }
25
27
  try {
26
28
  const value = readFileSync(params.apiPasswordFile, "utf-8").trim();
27
29
  return value || undefined;
@@ -31,7 +33,9 @@ function readApiPassword(params: {
31
33
  }
32
34
 
33
35
  function coerceRoomType(value: unknown): number | undefined {
34
- if (typeof value === "number" && Number.isFinite(value)) return value;
36
+ if (typeof value === "number" && Number.isFinite(value)) {
37
+ return value;
38
+ }
35
39
  if (typeof value === "string" && value.trim()) {
36
40
  const parsed = Number.parseInt(value, 10);
37
41
  return Number.isFinite(parsed) ? parsed : undefined;
@@ -40,8 +44,12 @@ function coerceRoomType(value: unknown): number | undefined {
40
44
  }
41
45
 
42
46
  function resolveRoomKindFromType(type: number | undefined): "direct" | "group" | undefined {
43
- if (!type) return undefined;
44
- if (type === 1 || type === 5 || type === 6) return "direct";
47
+ if (!type) {
48
+ return undefined;
49
+ }
50
+ if (type === 1 || type === 5 || type === 6) {
51
+ return "direct";
52
+ }
45
53
  return "group";
46
54
  }
47
55
 
@@ -55,8 +63,12 @@ export async function resolveNextcloudTalkRoomKind(params: {
55
63
  const cached = roomCache.get(key);
56
64
  if (cached) {
57
65
  const age = Date.now() - cached.fetchedAt;
58
- if (cached.kind && age < ROOM_CACHE_TTL_MS) return cached.kind;
59
- if (cached.error && age < ROOM_CACHE_ERROR_TTL_MS) return undefined;
66
+ if (cached.kind && age < ROOM_CACHE_TTL_MS) {
67
+ return cached.kind;
68
+ }
69
+ if (cached.error && age < ROOM_CACHE_ERROR_TTL_MS) {
70
+ return undefined;
71
+ }
60
72
  }
61
73
 
62
74
  const apiUser = account.config.apiUser?.trim();
@@ -64,10 +76,14 @@ export async function resolveNextcloudTalkRoomKind(params: {
64
76
  apiPassword: account.config.apiPassword,
65
77
  apiPasswordFile: account.config.apiPasswordFile,
66
78
  });
67
- if (!apiUser || !apiPassword) return undefined;
79
+ if (!apiUser || !apiPassword) {
80
+ return undefined;
81
+ }
68
82
 
69
83
  const baseUrl = account.baseUrl?.trim();
70
- if (!baseUrl) return undefined;
84
+ if (!baseUrl) {
85
+ return undefined;
86
+ }
71
87
 
72
88
  const url = `${baseUrl}/ocs/v2.php/apps/spreed/api/v4/room/${roomToken}`;
73
89
  const auth = Buffer.from(`${apiUser}:${apiPassword}`, "utf-8").toString("base64");
@@ -87,9 +103,7 @@ export async function resolveNextcloudTalkRoomKind(params: {
87
103
  fetchedAt: Date.now(),
88
104
  error: `status:${response.status}`,
89
105
  });
90
- runtime?.log?.(
91
- `nextcloud-talk: room lookup failed (${response.status}) token=${roomToken}`,
92
- );
106
+ runtime?.log?.(`nextcloud-talk: room lookup failed (${response.status}) token=${roomToken}`);
93
107
  return undefined;
94
108
  }
95
109
 
package/src/send.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import type { CoreConfig, NextcloudTalkSendResult } from "./types.js";
1
2
  import { resolveNextcloudTalkAccount } from "./accounts.js";
2
3
  import { getNextcloudTalkRuntime } from "./runtime.js";
3
4
  import { generateNextcloudTalkSignature } from "./signature.js";
4
- import type { CoreConfig, NextcloudTalkSendResult } from "./types.js";
5
5
 
6
6
  type NextcloudTalkSendOpts = {
7
7
  baseUrl?: string;
@@ -34,7 +34,9 @@ function resolveCredentials(
34
34
 
35
35
  function normalizeRoomToken(to: string): string {
36
36
  const trimmed = to.trim();
37
- if (!trimmed) throw new Error("Room token is required for Nextcloud Talk sends");
37
+ if (!trimmed) {
38
+ throw new Error("Room token is required for Nextcloud Talk sends");
39
+ }
38
40
 
39
41
  let normalized = trimmed;
40
42
  if (normalized.startsWith("nextcloud-talk:")) {
@@ -47,7 +49,9 @@ function normalizeRoomToken(to: string): string {
47
49
  normalized = normalized.slice("room:".length).trim();
48
50
  }
49
51
 
50
- if (!normalized) throw new Error("Room token is required for Nextcloud Talk sends");
52
+ if (!normalized) {
53
+ throw new Error("Room token is required for Nextcloud Talk sends");
54
+ }
51
55
  return normalized;
52
56
  }
53
57
 
package/src/signature.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { createHmac, randomBytes } from "node:crypto";
2
-
3
2
  import type { NextcloudTalkWebhookHeaders } from "./types.js";
4
3
 
5
4
  const SIGNATURE_HEADER = "x-nextcloud-talk-signature";
@@ -17,13 +16,17 @@ export function verifyNextcloudTalkSignature(params: {
17
16
  secret: string;
18
17
  }): boolean {
19
18
  const { signature, random, body, secret } = params;
20
- if (!signature || !random || !secret) return false;
19
+ if (!signature || !random || !secret) {
20
+ return false;
21
+ }
21
22
 
22
23
  const expected = createHmac("sha256", secret)
23
24
  .update(random + body)
24
25
  .digest("hex");
25
26
 
26
- if (signature.length !== expected.length) return false;
27
+ if (signature.length !== expected.length) {
28
+ return false;
29
+ }
27
30
  let result = 0;
28
31
  for (let i = 0; i < signature.length; i++) {
29
32
  result |= signature.charCodeAt(i) ^ expected.charCodeAt(i);
@@ -46,7 +49,9 @@ export function extractNextcloudTalkHeaders(
46
49
  const random = getHeader(RANDOM_HEADER);
47
50
  const backend = getHeader(BACKEND_HEADER);
48
51
 
49
- if (!signature || !random || !backend) return null;
52
+ if (!signature || !random || !backend) {
53
+ return null;
54
+ }
50
55
 
51
56
  return { signature, random, backend };
52
57
  }