@openclaw/nextcloud-talk 2026.10.10 → 2026.11.5-beta.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/dist/.setup/api-BvrRSilb.mjs +2 -0
- package/dist/.setup/{channel-D9tHRtkv.mjs → channel-BA1-OoqO.mjs} +1695 -934
- package/dist/.setup/outbound-media-CpFe1Xgz.mjs +303 -0
- package/dist/.setup/reply-BEkv387l.mjs +64 -0
- package/dist/.setup/{runtime-api-DSz4xBAm.mjs → runtime-api-CGy_KpdV.mjs} +1 -2
- package/dist/.setup/{secret-contract-api-CKCDZ3m8.mjs → secret-contract-api-QMihLdr1.mjs} +1 -1
- package/dist/.setup/secret-contract-yjCLewtg.mjs +19 -0
- package/dist/.source-module-urls/3332e832383d54fca83a3cfe8961ecdecf361c1eec0972c99f00a876d9481d98.json +1 -1
- package/dist/api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/configured-state.js +2 -5
- package/dist/runtime-api.js +1 -1
- package/dist/secret-contract-api.js +1 -1
- package/dist/setup-entry.js +2 -2
- package/node_modules/saxes/README.md +323 -0
- package/node_modules/saxes/node_modules/xmlchars/LICENSE +18 -0
- package/node_modules/saxes/node_modules/xmlchars/README.md +33 -0
- package/node_modules/saxes/node_modules/xmlchars/package.json +51 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.0/ed4.d.ts +31 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.0/ed4.js +44 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.0/ed4.js.map +1 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.0/ed5.d.ts +51 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.0/ed5.js +105 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.0/ed5.js.map +1 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.1/ed2.d.ts +73 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.1/ed2.js +145 -0
- package/node_modules/saxes/node_modules/xmlchars/xml/1.1/ed2.js.map +1 -0
- package/node_modules/saxes/node_modules/xmlchars/xmlchars.d.ts +170 -0
- package/node_modules/saxes/node_modules/xmlchars/xmlchars.js +191 -0
- package/node_modules/saxes/node_modules/xmlchars/xmlchars.js.map +1 -0
- package/node_modules/saxes/node_modules/xmlchars/xmlns/1.0/ed3.d.ts +28 -0
- package/node_modules/saxes/node_modules/xmlchars/xmlns/1.0/ed3.js +65 -0
- package/node_modules/saxes/node_modules/xmlchars/xmlns/1.0/ed3.js.map +1 -0
- package/node_modules/saxes/package.json +71 -0
- package/node_modules/saxes/saxes.d.ts +635 -0
- package/node_modules/saxes/saxes.js +2053 -0
- package/node_modules/saxes/saxes.js.map +1 -0
- package/node_modules/zod/package.json +1 -1
- package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
- package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
- package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
- package/node_modules/zod/src/v4/core/compile.ts +9 -0
- package/node_modules/zod/src/v4/core/schemas.ts +35 -1
- package/node_modules/zod/src/v4/core/util.ts +13 -0
- package/node_modules/zod/src/v4/core/versions.ts +1 -1
- package/node_modules/zod/v4/core/compile.cjs +8 -0
- package/node_modules/zod/v4/core/compile.js +8 -0
- package/node_modules/zod/v4/core/schemas.cjs +30 -0
- package/node_modules/zod/v4/core/schemas.d.cts +3 -1
- package/node_modules/zod/v4/core/schemas.d.ts +3 -1
- package/node_modules/zod/v4/core/schemas.js +30 -0
- package/node_modules/zod/v4/core/util.cjs +15 -0
- package/node_modules/zod/v4/core/util.d.cts +2 -0
- package/node_modules/zod/v4/core/util.d.ts +2 -0
- package/node_modules/zod/v4/core/util.js +14 -0
- package/node_modules/zod/v4/core/versions.cjs +1 -1
- package/node_modules/zod/v4/core/versions.js +1 -1
- package/openclaw.plugin.json +23 -1
- package/package.json +8 -6
- package/dist/.setup/api-BgnUYkO3.mjs +0 -2
- package/dist/.setup/secret-contract-BHyPrmz3.mjs +0 -14
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { a as resolveNextcloudTalkAccount, i as releaseNextcloudTalkGuardedResponse, n as stripNextcloudTalkTargetPrefix, o as resolveNextcloudTalkApiCredentialsResult, r as parseWebDavFileId } from "./channel-BA1-OoqO.mjs";
|
|
2
|
+
import { l as getOptionalNextcloudTalkRuntime, r as fetchWithSsrFGuard } from "./runtime-api-CGy_KpdV.mjs";
|
|
3
|
+
import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/account-helpers";
|
|
4
|
+
import { createMessageReceiptFromOutboundResults, isRecentOutboundMessageIdentity, recordOutboundMessageIdentity } from "openclaw/plugin-sdk/channel-outbound";
|
|
5
|
+
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-chunking";
|
|
7
|
+
import { readProviderJsonResponse, readProviderTextResponse } from "openclaw/plugin-sdk/provider-http";
|
|
8
|
+
import { ssrfPolicyFromPrivateNetworkOptIn } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { sanitizeUntrustedFileName } from "openclaw/plugin-sdk/security-runtime";
|
|
11
|
+
import { truncateUtf8Prefix } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
12
|
+
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
13
|
+
import { createHmac, randomUUID } from "node:crypto";
|
|
14
|
+
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
|
15
|
+
// extensions/nextcloud-talk/src/outbound-media.ts
|
|
16
|
+
const OUTBOUND_FILE_PREFIX = /^openclaw-([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})-/u;
|
|
17
|
+
const FILE_ID_REQUEST = "<d:propfind xmlns:d=\"DAV:\" xmlns:oc=\"http://owncloud.org/ns\"><d:prop><oc:fileid/></d:prop></d:propfind>";
|
|
18
|
+
function createFileClient(account) {
|
|
19
|
+
const credentials = resolveNextcloudTalkApiCredentialsResult(account.config);
|
|
20
|
+
if (credentials.status !== "available") throw new Error("Nextcloud Talk attachments require available apiUser and apiPassword/apiPasswordFile credentials.");
|
|
21
|
+
const base = new URL(account.baseUrl);
|
|
22
|
+
if (!/^https?:$/u.test(base.protocol) || base.username || base.password || base.search || base.hash) throw new Error("Nextcloud Talk attachment baseUrl must be an HTTP(S) URL without credentials, query, or fragment.");
|
|
23
|
+
const authorization = `Basic ${Buffer.from(`${credentials.value.apiUser}:${credentials.value.apiPassword}`).toString("base64")}`;
|
|
24
|
+
const url = (pathname) => `${base.origin}${base.pathname.replace(/\/+$/u, "")}${pathname}`;
|
|
25
|
+
async function request(pathname, init, read, options = {}) {
|
|
26
|
+
options.signal?.throwIfAborted();
|
|
27
|
+
const headers = new Headers(init.headers);
|
|
28
|
+
headers.set("Authorization", authorization);
|
|
29
|
+
headers.set("OCS-APIRequest", "true");
|
|
30
|
+
if (!headers.has("Accept")) headers.set("Accept", "application/json");
|
|
31
|
+
const guarded = await fetchWithSsrFGuard({
|
|
32
|
+
url: url(pathname),
|
|
33
|
+
init: {
|
|
34
|
+
...init,
|
|
35
|
+
headers
|
|
36
|
+
},
|
|
37
|
+
maxRedirects: 0,
|
|
38
|
+
requireHttps: base.protocol === "https:",
|
|
39
|
+
policy: {
|
|
40
|
+
...ssrfPolicyFromPrivateNetworkOptIn(account.config.network?.dangerouslyAllowPrivateNetwork),
|
|
41
|
+
hostnameAllowlist: [base.hostname]
|
|
42
|
+
},
|
|
43
|
+
timeoutMs: 12e4,
|
|
44
|
+
signal: options.signal,
|
|
45
|
+
beforeRequest: options.beforeRequest,
|
|
46
|
+
auditContext: "nextcloud-talk-outbound-file"
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
return await read(guarded.response);
|
|
50
|
+
} finally {
|
|
51
|
+
await releaseNextcloudTalkGuardedResponse(guarded);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function json(pathname, init, options) {
|
|
55
|
+
return await request(pathname, init, async (response) => {
|
|
56
|
+
if (!response.ok) throw new Error(`Nextcloud Talk attachment API failed (${response.status}).`);
|
|
57
|
+
const payload = await readProviderJsonResponse(response, "Nextcloud Talk attachment API");
|
|
58
|
+
if (!isRecord(payload) || !isRecord(payload.ocs)) throw new Error("Nextcloud Talk attachment API returned an invalid response.");
|
|
59
|
+
return payload.ocs.data;
|
|
60
|
+
}, options);
|
|
61
|
+
}
|
|
62
|
+
async function user(options) {
|
|
63
|
+
const data = await json("/ocs/v1.php/cloud/user?format=json", {}, options);
|
|
64
|
+
if (!isRecord(data) || typeof data.id !== "string" || !data.id || data.id === "." || data.id === ".." || data.id.includes("/") || data.id.includes("\\") || Array.from(data.id).some((character) => {
|
|
65
|
+
const code = character.charCodeAt(0);
|
|
66
|
+
return code <= 31 || code === 127;
|
|
67
|
+
})) throw new Error("Nextcloud Talk API account has no valid canonical user ID.");
|
|
68
|
+
return data.id;
|
|
69
|
+
}
|
|
70
|
+
async function history(roomToken, messageId, options) {
|
|
71
|
+
const query = new URLSearchParams({
|
|
72
|
+
lookIntoFuture: "0",
|
|
73
|
+
limit: messageId ? "1" : "200",
|
|
74
|
+
setReadMarker: "0",
|
|
75
|
+
noStatusUpdate: "1",
|
|
76
|
+
markNotificationsAsRead: "0"
|
|
77
|
+
});
|
|
78
|
+
if (messageId) {
|
|
79
|
+
query.set("lastKnownMessageId", messageId);
|
|
80
|
+
query.set("includeLastKnown", "1");
|
|
81
|
+
}
|
|
82
|
+
const data = await json(`/ocs/v2.php/apps/spreed/api/v1/chat/${encodeURIComponent(roomToken)}?${query}`, {}, options);
|
|
83
|
+
return Array.isArray(data) ? data : [];
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
request,
|
|
87
|
+
json,
|
|
88
|
+
user,
|
|
89
|
+
history
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function fileReference(account, roomToken, uid, fileId) {
|
|
93
|
+
return createHmac("sha256", account.secret).update(JSON.stringify([
|
|
94
|
+
"nextcloud-talk-outbound-file",
|
|
95
|
+
account.accountId,
|
|
96
|
+
roomToken,
|
|
97
|
+
uid,
|
|
98
|
+
fileId
|
|
99
|
+
])).digest("hex");
|
|
100
|
+
}
|
|
101
|
+
function echoIdentity(accountId, roomToken, actor, fileId) {
|
|
102
|
+
return {
|
|
103
|
+
channel: "nextcloud-talk",
|
|
104
|
+
accountId,
|
|
105
|
+
conversationId: roomToken,
|
|
106
|
+
sourceId: `file:${actor.toLowerCase()}:${fileId}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** The bounded cache handles webhook-before-ack; exact history handles restart/backlog. */
|
|
110
|
+
async function isNextcloudTalkOutboundFileEcho(params) {
|
|
111
|
+
params.signal?.throwIfAborted();
|
|
112
|
+
const { account, message } = params;
|
|
113
|
+
const file = message.attachment;
|
|
114
|
+
if (!file?.fileId) return false;
|
|
115
|
+
if (isRecentOutboundMessageIdentity(echoIdentity(account.accountId, message.roomToken, message.senderId, file.fileId))) return true;
|
|
116
|
+
if (!OUTBOUND_FILE_PREFIX.test(file.name)) return false;
|
|
117
|
+
if (!account.secret || resolveNextcloudTalkApiCredentialsResult(account.config).status !== "available") return false;
|
|
118
|
+
try {
|
|
119
|
+
const client = createFileClient(account);
|
|
120
|
+
const uid = await client.user({ signal: params.signal });
|
|
121
|
+
if (message.senderId !== `users/${uid}`) return false;
|
|
122
|
+
const referenceId = fileReference(account, message.roomToken, uid, file.fileId);
|
|
123
|
+
const history = await client.history(message.roomToken, message.messageId, { signal: params.signal });
|
|
124
|
+
params.signal?.throwIfAborted();
|
|
125
|
+
return history.some((entry) => isRecord(entry) && String(entry.id) === message.messageId && entry.token === message.roomToken && entry.actorType === "users" && entry.actorId === uid && entry.referenceId === referenceId && isRecord(entry.messageParameters) && isRecord(entry.messageParameters.file) && String(entry.messageParameters.file.id) === file.fileId);
|
|
126
|
+
} catch {
|
|
127
|
+
params.signal?.throwIfAborted();
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function sendMediaNextcloudTalk(ctx) {
|
|
132
|
+
const account = resolveNextcloudTalkAccount({
|
|
133
|
+
cfg: ctx.cfg,
|
|
134
|
+
accountId: ctx.accountId
|
|
135
|
+
});
|
|
136
|
+
if (!account.enabled || !account.baseUrl || !account.secret) throw new Error("Nextcloud Talk account is disabled or not configured.");
|
|
137
|
+
const roomToken = stripNextcloudTalkTargetPrefix(ctx.to);
|
|
138
|
+
if (!roomToken) throw new Error("Room token is required for Nextcloud Talk attachments.");
|
|
139
|
+
if (ctx.replyToId && !/^[1-9]\d*$/u.test(ctx.replyToId)) throw new Error("Nextcloud Talk attachment replyTo must be a positive message ID.");
|
|
140
|
+
const client = createFileClient(account);
|
|
141
|
+
const assertCurrent = () => {
|
|
142
|
+
ctx.signal?.throwIfAborted();
|
|
143
|
+
ctx.assertDirectAdapterHandoff?.();
|
|
144
|
+
};
|
|
145
|
+
const options = {
|
|
146
|
+
signal: ctx.signal,
|
|
147
|
+
beforeRequest: assertCurrent
|
|
148
|
+
};
|
|
149
|
+
assertCurrent();
|
|
150
|
+
if (ctx.silent) {
|
|
151
|
+
const data = await client.json("/ocs/v2.php/cloud/capabilities?format=json", {}, options);
|
|
152
|
+
assertCurrent();
|
|
153
|
+
const spreed = isRecord(data) && isRecord(data.capabilities) ? data.capabilities.spreed : void 0;
|
|
154
|
+
if (!isRecord(spreed) || !Array.isArray(spreed.features) || !spreed.features.includes("media-caption")) throw new Error("Nextcloud Talk silent attachments require the server's media-caption capability. Upgrade Talk before retrying.");
|
|
155
|
+
}
|
|
156
|
+
const maxBytes = resolveChannelMediaMaxBytes({
|
|
157
|
+
cfg: ctx.cfg,
|
|
158
|
+
accountId: account.accountId,
|
|
159
|
+
resolveChannelLimitMb: () => account.config.mediaMaxMb
|
|
160
|
+
}) ?? 20971520;
|
|
161
|
+
const media = await loadOutboundMediaFromUrl(ctx.mediaUrl, {
|
|
162
|
+
maxBytes,
|
|
163
|
+
mediaAccess: ctx.mediaAccess,
|
|
164
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
165
|
+
mediaReadFile: ctx.mediaReadFile,
|
|
166
|
+
optimizeImages: false
|
|
167
|
+
});
|
|
168
|
+
assertCurrent();
|
|
169
|
+
const asVoice = ctx.audioAsVoice === true && (media.contentType === "audio/mpeg" || media.contentType === "audio/wav");
|
|
170
|
+
const uid = await client.user(options);
|
|
171
|
+
const uploadId = randomUUID();
|
|
172
|
+
const prefix = `openclaw-${uploadId}-`;
|
|
173
|
+
const filename = sanitizeUntrustedFileName(media.fileName ?? "attachment.bin", "attachment.bin");
|
|
174
|
+
const nameBudget = 250 - Buffer.byteLength(prefix);
|
|
175
|
+
const extension = truncateUtf8Prefix(path.extname(filename), nameBudget - 4);
|
|
176
|
+
const stem = truncateUtf8Prefix(filename.slice(0, filename.length - path.extname(filename).length), nameBudget - Buffer.byteLength(extension));
|
|
177
|
+
const uploadFolderPath = `/OpenClaw/openclaw-${uploadId}`;
|
|
178
|
+
const filePath = `${uploadFolderPath}/${prefix}${stem}${extension}`;
|
|
179
|
+
const davRoot = `/remote.php/dav/files/${encodeURIComponent(uid)}`;
|
|
180
|
+
const uploadFolder = `${davRoot}${uploadFolderPath}`;
|
|
181
|
+
const davPath = `${davRoot}${filePath.split("/").map(encodeURIComponent).join("/")}`;
|
|
182
|
+
let uploadFolderOwned = false;
|
|
183
|
+
let publicationAttempted = false;
|
|
184
|
+
try {
|
|
185
|
+
await client.request(`${davRoot}/OpenClaw`, { method: "MKCOL" }, async (response) => {
|
|
186
|
+
if (response.status !== 201 && response.status !== 405) throw new Error(`Nextcloud Talk attachment folder creation failed (${response.status}).`);
|
|
187
|
+
}, options);
|
|
188
|
+
await client.request(uploadFolder, { method: "MKCOL" }, async (response) => {
|
|
189
|
+
if (response.status !== 201) throw new Error(`Nextcloud Talk private upload folder creation failed (${response.status}).`);
|
|
190
|
+
uploadFolderOwned = true;
|
|
191
|
+
}, options);
|
|
192
|
+
await client.request(davPath, {
|
|
193
|
+
method: "PUT",
|
|
194
|
+
headers: {
|
|
195
|
+
"If-None-Match": "*",
|
|
196
|
+
"Content-Type": media.contentType ?? "application/octet-stream"
|
|
197
|
+
},
|
|
198
|
+
body: new Uint8Array(media.buffer)
|
|
199
|
+
}, async (response) => {
|
|
200
|
+
if (response.status !== 201) {
|
|
201
|
+
if (response.status === 412) uploadFolderOwned = false;
|
|
202
|
+
const recovery = response.status === 401 ? "Check apiUser and update its app password (apiPassword/apiPasswordFile) before retrying." : response.status === 507 ? "Free Files storage or increase the API user's quota before retrying." : "Check Nextcloud server logs and the API user's Files write access.";
|
|
203
|
+
throw new Error(`Nextcloud Talk attachment upload failed (${response.status}). ${recovery}`);
|
|
204
|
+
}
|
|
205
|
+
}, options);
|
|
206
|
+
const fileId = await client.request(davPath, {
|
|
207
|
+
method: "PROPFIND",
|
|
208
|
+
headers: {
|
|
209
|
+
Depth: "0",
|
|
210
|
+
"Content-Type": "application/xml"
|
|
211
|
+
},
|
|
212
|
+
body: FILE_ID_REQUEST
|
|
213
|
+
}, async (response) => {
|
|
214
|
+
if (response.status !== 207) throw new Error("Nextcloud Talk uploaded file identity unavailable.");
|
|
215
|
+
const id = parseWebDavFileId(await readProviderTextResponse(response, "Nextcloud Talk uploaded file identity", { maxBytes: 65536 }));
|
|
216
|
+
if (!id) throw new Error("Nextcloud Talk uploaded file identity invalid.");
|
|
217
|
+
return id;
|
|
218
|
+
}, options);
|
|
219
|
+
const referenceId = fileReference(account, roomToken, uid, fileId);
|
|
220
|
+
const caption = convertMarkdownTables(ctx.text.trim(), resolveMarkdownTableMode({
|
|
221
|
+
cfg: ctx.cfg,
|
|
222
|
+
channel: "nextcloud-talk",
|
|
223
|
+
accountId: account.accountId
|
|
224
|
+
}));
|
|
225
|
+
const body = new URLSearchParams({
|
|
226
|
+
path: filePath,
|
|
227
|
+
shareType: "10",
|
|
228
|
+
shareWith: roomToken,
|
|
229
|
+
permissions: "1",
|
|
230
|
+
referenceId,
|
|
231
|
+
talkMetaData: JSON.stringify({
|
|
232
|
+
caption,
|
|
233
|
+
...asVoice ? { messageType: "voice-message" } : {},
|
|
234
|
+
...ctx.silent ? { silent: true } : {},
|
|
235
|
+
...ctx.replyToId ? { replyTo: Number(ctx.replyToId) } : {}
|
|
236
|
+
})
|
|
237
|
+
});
|
|
238
|
+
await ctx.onPlatformSendDispatch?.();
|
|
239
|
+
assertCurrent();
|
|
240
|
+
recordOutboundMessageIdentity(echoIdentity(account.accountId, roomToken, `users/${uid}`, fileId));
|
|
241
|
+
let publicationError;
|
|
242
|
+
try {
|
|
243
|
+
await client.json("/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
246
|
+
body
|
|
247
|
+
}, {
|
|
248
|
+
...options,
|
|
249
|
+
beforeRequest: () => {
|
|
250
|
+
assertCurrent();
|
|
251
|
+
publicationAttempted = true;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (!publicationAttempted) throw error;
|
|
256
|
+
publicationError = error;
|
|
257
|
+
}
|
|
258
|
+
let history;
|
|
259
|
+
try {
|
|
260
|
+
history = await client.history(roomToken);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
throw new Error("Nextcloud Talk attachment delivery not confirmed; uploaded file retained. Do not blindly retry.", { cause: error });
|
|
263
|
+
}
|
|
264
|
+
const message = history.find((entry) => isRecord(entry) && entry.token === roomToken && entry.actorType === "users" && entry.actorId === uid && entry.referenceId === referenceId && /^[1-9]\d*$/u.test(String(entry.id)) && isRecord(entry.messageParameters) && isRecord(entry.messageParameters.file) && String(entry.messageParameters.file.id) === fileId);
|
|
265
|
+
if (!isRecord(message)) throw new Error("Nextcloud Talk attachment delivery not confirmed; uploaded file retained. Do not blindly retry.", { cause: publicationError });
|
|
266
|
+
const messageId = String(message.id);
|
|
267
|
+
const replyToId = isRecord(message.parent) && String(message.parent.id) === ctx.replyToId ? ctx.replyToId : void 0;
|
|
268
|
+
recordOutboundMessageIdentity({
|
|
269
|
+
channel: "nextcloud-talk",
|
|
270
|
+
accountId: account.accountId,
|
|
271
|
+
conversationId: roomToken,
|
|
272
|
+
messageId
|
|
273
|
+
});
|
|
274
|
+
getOptionalNextcloudTalkRuntime()?.channel.activity.record({
|
|
275
|
+
channel: "nextcloud-talk",
|
|
276
|
+
accountId: account.accountId,
|
|
277
|
+
direction: "outbound"
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
messageId,
|
|
281
|
+
roomToken,
|
|
282
|
+
receipt: createMessageReceiptFromOutboundResults({
|
|
283
|
+
results: [{
|
|
284
|
+
channel: "nextcloud-talk",
|
|
285
|
+
conversationId: roomToken,
|
|
286
|
+
messageId
|
|
287
|
+
}],
|
|
288
|
+
kind: asVoice && message.messageType === "voice-message" ? "voice" : "media",
|
|
289
|
+
...replyToId ? { replyToId } : {}
|
|
290
|
+
})
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (uploadFolderOwned && !publicationAttempted) try {
|
|
294
|
+
await client.request(uploadFolder, { method: "DELETE" }, async (response) => {
|
|
295
|
+
if (!response.ok && response.status !== 404) throw new Error("cleanup rejected", { cause: error });
|
|
296
|
+
});
|
|
297
|
+
} catch {
|
|
298
|
+
throw new Error("Nextcloud Talk attachment failed before sharing; unshared upload cleanup also failed.", { cause: error });
|
|
299
|
+
}
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export { isNextcloudTalkOutboundFileEcho, sendMediaNextcloudTalk };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { buildOutboundSessionContext, deliverInboundReplyWithMessageSendContext, sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
|
|
2
|
+
import { createAcceptedChannelDeliveryResult, createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
3
|
+
// extensions/nextcloud-talk/src/reply.ts
|
|
4
|
+
/** Keep final runtime handoff and all payload planning with the shared owners. */
|
|
5
|
+
async function deliverNextcloudTalkReply(params) {
|
|
6
|
+
const { cfg, accountId, agentId, ctxPayload, payload, info, to } = params;
|
|
7
|
+
const final = await deliverInboundReplyWithMessageSendContext({
|
|
8
|
+
cfg,
|
|
9
|
+
channel: "nextcloud-talk",
|
|
10
|
+
accountId,
|
|
11
|
+
agentId,
|
|
12
|
+
ctxPayload,
|
|
13
|
+
payload,
|
|
14
|
+
info,
|
|
15
|
+
to
|
|
16
|
+
});
|
|
17
|
+
if (final.status === "handled_visible" || final.status === "handled_no_send") return final.delivery;
|
|
18
|
+
if (final.status === "failed") throw final.error;
|
|
19
|
+
const send = await sendDurableMessageBatch({
|
|
20
|
+
cfg,
|
|
21
|
+
channel: "nextcloud-talk",
|
|
22
|
+
accountId,
|
|
23
|
+
to,
|
|
24
|
+
payloads: [payload],
|
|
25
|
+
replyToId: payload.replyToId,
|
|
26
|
+
session: buildOutboundSessionContext({
|
|
27
|
+
cfg,
|
|
28
|
+
agentId,
|
|
29
|
+
sessionKey: ctxPayload.SessionKey,
|
|
30
|
+
policySessionKey: ctxPayload.RuntimePolicySessionKey,
|
|
31
|
+
conversationType: ctxPayload.ChatType,
|
|
32
|
+
requesterAccountId: accountId ?? ctxPayload.AccountId,
|
|
33
|
+
requesterSenderId: ctxPayload.SenderId ?? ctxPayload.From,
|
|
34
|
+
requesterSenderName: ctxPayload.SenderName,
|
|
35
|
+
requesterSenderUsername: ctxPayload.SenderUsername,
|
|
36
|
+
requesterSenderE164: ctxPayload.SenderE164
|
|
37
|
+
}),
|
|
38
|
+
gatewayClientScopes: ctxPayload.GatewayClientScopes ?? [],
|
|
39
|
+
signal: params.signal,
|
|
40
|
+
onPlatformSendDispatch: info.onPlatformSendDispatch,
|
|
41
|
+
assertDirectAdapterHandoff: info.assertPlatformSendAuthorized,
|
|
42
|
+
skipQueue: true,
|
|
43
|
+
durability: "best_effort"
|
|
44
|
+
});
|
|
45
|
+
if (send.status === "failed") throw send.error;
|
|
46
|
+
const deliveryIntent = send.deliveryIntent ? {
|
|
47
|
+
id: send.deliveryIntent.id,
|
|
48
|
+
kind: "outbound_queue",
|
|
49
|
+
queuePolicy: send.deliveryIntent.queuePolicy
|
|
50
|
+
} : void 0;
|
|
51
|
+
if (send.status === "suppressed") return {
|
|
52
|
+
visibleReplySent: false,
|
|
53
|
+
receipt: send.receipt,
|
|
54
|
+
suppression: { reason: send.reason },
|
|
55
|
+
...deliveryIntent ? { deliveryIntent } : {}
|
|
56
|
+
};
|
|
57
|
+
const result = {
|
|
58
|
+
...createAcceptedChannelDeliveryResult({ results: [{ receipt: send.receipt }] }),
|
|
59
|
+
...deliveryIntent ? { deliveryIntent } : {}
|
|
60
|
+
};
|
|
61
|
+
if (send.status === "partial_failed") throw createChannelPartialDeliveryError(send.error, result);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
export { deliverNextcloudTalkReply };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
2
2
|
import { logInboundDrop } from "openclaw/plugin-sdk/channel-inbound";
|
|
3
3
|
import { GROUP_POLICY_BLOCKED_LABEL, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
4
|
-
import { deliverFormattedTextWithAttachments } from "openclaw/plugin-sdk/reply-payload";
|
|
5
4
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
6
5
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
7
6
|
// extensions/nextcloud-talk/src/runtime.ts
|
|
@@ -9,4 +8,4 @@ const { setRuntime: setNextcloudTalkRuntime, getRuntime: getNextcloudTalkRuntime
|
|
|
9
8
|
pluginId: "nextcloud-talk",
|
|
10
9
|
errorMessage: "Nextcloud Talk runtime not initialized"
|
|
11
10
|
});
|
|
12
|
-
export {
|
|
11
|
+
export { resolveAllowlistProviderRuntimeGroupPolicy as a, getNextcloudTalkRuntime as c, logInboundDrop as i, getOptionalNextcloudTalkRuntime as l, createChannelPairingController as n, resolveDefaultGroupPolicy as o, fetchWithSsrFGuard as r, warnMissingProviderGroupPolicyFallbackOnce as s, GROUP_POLICY_BLOCKED_LABEL as t, setNextcloudTalkRuntime as u };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { t as channelSecrets, n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-
|
|
1
|
+
export { t as channelSecrets, n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-yjCLewtg.mjs";
|
|
2
2
|
import "../secret-contract-api.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { hasConfiguredNextcloudAccount } from "../configured-state.js";
|
|
2
|
+
import { collectSimpleChannelFieldAssignments, createChannelSecretContract } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
|
|
3
|
+
// extensions/nextcloud-talk/src/secret-contract.ts
|
|
4
|
+
const channelSecrets = createChannelSecretContract({
|
|
5
|
+
channelKey: "nextcloud-talk",
|
|
6
|
+
account: ["apiPassword", "botSecret"],
|
|
7
|
+
channel: ["apiPassword", "botSecret"],
|
|
8
|
+
hasImplicitDefaultAccount: ({ context }) => hasConfiguredNextcloudAccount(context.sourceConfig.channels?.["nextcloud-talk"], context.env),
|
|
9
|
+
collect(params) {
|
|
10
|
+
for (const field of ["botSecret", "apiPassword"]) collectSimpleChannelFieldAssignments({
|
|
11
|
+
...params,
|
|
12
|
+
field,
|
|
13
|
+
topInactiveReason: `no enabled Nextcloud Talk surface inherits this top-level ${field}.`,
|
|
14
|
+
accountInactiveReason: "Nextcloud Talk account is disabled."
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const { secretTargetRegistryEntries, collectRuntimeConfigAssignments } = channelSecrets;
|
|
19
|
+
export { collectRuntimeConfigAssignments as n, secretTargetRegistryEntries as r, channelSecrets as t };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"entries":[{"output":"api.js","source":"extensions/nextcloud-talk/api.ts"},{"output":"channel-plugin-api.js","source":"extensions/nextcloud-talk/channel-plugin-api.ts"},{"output":"configured-state.js","source":"extensions/nextcloud-talk/configured-state.ts"},{"output":"doctor-contract-api.js","source":"extensions/nextcloud-talk/doctor-contract-api.ts"},{"output":"index.js","source":"extensions/nextcloud-talk/index.ts"},{"output":"runtime-api.js","source":"extensions/nextcloud-talk/runtime-api.ts"},{"output":"secret-contract-api.js","source":"extensions/nextcloud-talk/secret-contract-api.ts"},{"output":"setup-entry.js","source":"extensions/nextcloud-talk/setup-entry.ts"}],"chunks":[{"output":".setup/api-
|
|
1
|
+
{"version":3,"entries":[{"output":"api.js","source":"extensions/nextcloud-talk/api.ts"},{"output":"channel-plugin-api.js","source":"extensions/nextcloud-talk/channel-plugin-api.ts"},{"output":"configured-state.js","source":"extensions/nextcloud-talk/configured-state.ts"},{"output":"doctor-contract-api.js","source":"extensions/nextcloud-talk/doctor-contract-api.ts"},{"output":"index.js","source":"extensions/nextcloud-talk/index.ts"},{"output":"runtime-api.js","source":"extensions/nextcloud-talk/runtime-api.ts"},{"output":"secret-contract-api.js","source":"extensions/nextcloud-talk/secret-contract-api.ts"},{"output":"setup-entry.js","source":"extensions/nextcloud-talk/setup-entry.ts"}],"chunks":[{"output":".setup/api-BvrRSilb.mjs","sha256":"b25f39344c117b809520b3961b58f661adee93617654525cf980397c3567f61c","facadeSource":"extensions/nextcloud-talk/api.ts","sources":[]},{"output":".setup/channel-BA1-OoqO.mjs","sha256":"7332d213f8ca1e37bfa7a913f295bbef288630ddb9739c5f4032aa558c2e4057","facadeSource":null,"sources":["extensions/nextcloud-talk/src/accounts.ts","extensions/nextcloud-talk/src/api-credentials.ts","extensions/nextcloud-talk/src/bot-preflight.ts","extensions/nextcloud-talk/src/channel.ts","extensions/nextcloud-talk/src/config-schema.ts","extensions/nextcloud-talk/src/doctor.ts","extensions/nextcloud-talk/src/guarded-response.ts","extensions/nextcloud-talk/src/inbound-media.ts","extensions/nextcloud-talk/src/inbound.ts","extensions/nextcloud-talk/src/message-actions.ts","extensions/nextcloud-talk/src/monitor.ts","extensions/nextcloud-talk/src/normalize.ts","extensions/nextcloud-talk/src/policy.ts","extensions/nextcloud-talk/src/replay-migration-contract.ts","extensions/nextcloud-talk/src/room-info.ts","extensions/nextcloud-talk/src/send.ts","extensions/nextcloud-talk/src/setup-core.ts","extensions/nextcloud-talk/src/setup-surface.ts","extensions/nextcloud-talk/src/signature.ts","extensions/nextcloud-talk/src/voice-media.ts","extensions/nextcloud-talk/src/webdav.ts","extensions/nextcloud-talk/src/webhook-route.ts","extensions/nextcloud-talk/src/webhook-spool-state.ts","extensions/nextcloud-talk/src/webhook-spool.ts","packages/normalization-core/src/string-coerce.ts","src/gateway/gateway-http-route-contracts.ts","src/gateway/security-path.ts","src/gateway/server/plugins-http/path-context.ts","src/plugins/http-path-prefix.ts"]},{"output":".setup/doctor-contract-CkWQa5uZ.mjs","sha256":"b580887eb445ab10c67ee1a057982cd3be79d68e2c3440b3f2a46f2a8852c55b","facadeSource":null,"sources":["extensions/nextcloud-talk/src/doctor-contract.ts","packages/normalization-core/src/record-coerce.ts","src/config/channel-config-normalization.ts","src/plugin-sdk/legacy-webhook-listener-migration.ts"]},{"output":".setup/outbound-media-CpFe1Xgz.mjs","sha256":"bef81da85203f92dc6478aa3f00d11112e3f8aad10191dbf0a0678c07049ab61","facadeSource":"extensions/nextcloud-talk/src/outbound-media.ts","sources":["extensions/nextcloud-talk/src/outbound-media.ts"]},{"output":".setup/reply-BEkv387l.mjs","sha256":"e2c5093c05fcdbbe57a1693ea81de165c8280573ad3706f244dbd152dcd895ca","facadeSource":"extensions/nextcloud-talk/src/reply.ts","sources":["extensions/nextcloud-talk/src/reply.ts"]},{"output":".setup/runtime-api-CGy_KpdV.mjs","sha256":"344accb3739f831215489e33d0ca4dbaa5062fad8a0c98b7e356ee2bd0bb8228","facadeSource":null,"sources":["extensions/nextcloud-talk/src/runtime.ts"]},{"output":".setup/secret-contract-api-QMihLdr1.mjs","sha256":"0a2531895db3b16212c29ea87314c04021173a1bde49b58fbfd29fed181b4f15","facadeSource":"extensions/nextcloud-talk/secret-contract-api.ts","sources":[]},{"output":".setup/secret-contract-yjCLewtg.mjs","sha256":"183e7de729d242a63a7b607f3d353a5712f19eaea4ca24525cd96cc93bb63ffc","facadeSource":null,"sources":["extensions/nextcloud-talk/src/secret-contract.ts"]},{"output":"api.js","sha256":"e6a136ec30af9d685b3b1724ac335bc48534867c9c553e6a04f47d1ae22444d8","facadeSource":"extensions/nextcloud-talk/api.ts","sources":[]},{"output":"channel-plugin-api.js","sha256":"e6a136ec30af9d685b3b1724ac335bc48534867c9c553e6a04f47d1ae22444d8","facadeSource":"extensions/nextcloud-talk/channel-plugin-api.ts","sources":[]},{"output":"configured-state.js","sha256":"a45a34efe4339d78b7fcc92b8d09f8ad60d19c4a4e3a9754104c7f658be278d0","facadeSource":"extensions/nextcloud-talk/configured-state.ts","sources":["extensions/nextcloud-talk/configured-state.ts"]},{"output":"doctor-contract-api.js","sha256":"71237fb5c510be1302851b9fb7b264dd4e0d0095be206e50ffce4b5890ace54f","facadeSource":"extensions/nextcloud-talk/doctor-contract-api.ts","sources":[]},{"output":"index.js","sha256":"16bcc875626f7dd3bc4c1195210f856deaa9c3757abe5cea2a1d90cb2877cd22","facadeSource":"extensions/nextcloud-talk/index.ts","sources":["extensions/nextcloud-talk/index.ts"]},{"output":"runtime-api.js","sha256":"9306402515a553c1fe195fc8be8d6436a41abca338ae6cd4254434422e6f4cb3","facadeSource":"extensions/nextcloud-talk/runtime-api.ts","sources":[]},{"output":"secret-contract-api.js","sha256":"34c4d2c917c364f14b73bb8f51db22a8f24d047246358f543ab3aa0abfa78202","facadeSource":"extensions/nextcloud-talk/secret-contract-api.ts","sources":[]},{"output":"setup-entry.js","sha256":"7dba8b8fb26185b57bcabe7aab548d7e956deb37c46142110413ff4cab7cf80a","facadeSource":"extensions/nextcloud-talk/setup-entry.ts","sources":["extensions/nextcloud-talk/setup-entry.ts"]}],"unrenderedSources":["extensions/nextcloud-talk/api.ts","extensions/nextcloud-talk/channel-plugin-api.ts","extensions/nextcloud-talk/config-doctor-api.ts","extensions/nextcloud-talk/doctor-contract-api.ts","extensions/nextcloud-talk/runtime-api.ts","extensions/nextcloud-talk/secret-contract-api.ts"]}
|
package/dist/api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as nextcloudTalkPlugin } from "./.setup/channel-
|
|
1
|
+
export { t as nextcloudTalkPlugin } from "./.setup/channel-BA1-OoqO.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as nextcloudTalkPlugin } from "./.setup/channel-
|
|
1
|
+
export { t as nextcloudTalkPlugin } from "./.setup/channel-BA1-OoqO.mjs";
|
package/dist/configured-state.js
CHANGED
|
@@ -3,10 +3,7 @@ import { DEFAULT_ACCOUNT_ID, createAccountListHelpers, hasConfiguredAccountValue
|
|
|
3
3
|
const { listAccountIds: listNextcloudTalkAccountIds, resolveDefaultAccountId: resolveDefaultNextcloudTalkAccountId, resolveAccountConfig: mergeNextcloudTalkAccountConfig } = createAccountListHelpers("nextcloud-talk", {
|
|
4
4
|
normalizeAccountId,
|
|
5
5
|
omitKeys: ["defaultAccount"],
|
|
6
|
-
hasImplicitDefaultAccount: (cfg) =>
|
|
7
|
-
const channel = cfg.channels?.["nextcloud-talk"];
|
|
8
|
-
return Boolean(channel?.baseUrl?.trim() && (hasConfiguredAccountValue(channel.botSecret) || channel.botSecretFile?.trim() || process.env.NEXTCLOUD_TALK_BOT_SECRET?.trim()));
|
|
9
|
-
}
|
|
6
|
+
hasImplicitDefaultAccount: (cfg) => hasConfiguredNextcloudAccount(cfg.channels?.["nextcloud-talk"], process.env)
|
|
10
7
|
});
|
|
11
8
|
function hasConfiguredNextcloudAccount(account, env) {
|
|
12
9
|
return Boolean(account?.baseUrl?.trim() && (hasConfiguredAccountValue(account.botSecret) || hasConfiguredAccountValue(account.botSecretFile) || hasConfiguredAccountValue(env.NEXTCLOUD_TALK_BOT_SECRET)));
|
|
@@ -20,4 +17,4 @@ function hasConfiguredNextcloudTalkChannelState(params) {
|
|
|
20
17
|
}
|
|
21
18
|
return Object.entries(channel?.accounts ?? {}).some(([accountId, account]) => accountId !== DEFAULT_ACCOUNT_ID && account.enabled !== false && hasConfiguredNextcloudAccount(mergeNextcloudTalkAccountConfig(params.cfg, accountId), {}));
|
|
22
19
|
}
|
|
23
|
-
export { hasConfiguredNextcloudTalkChannelState, listNextcloudTalkAccountIds, mergeNextcloudTalkAccountConfig, resolveDefaultNextcloudTalkAccountId };
|
|
20
|
+
export { hasConfiguredNextcloudAccount, hasConfiguredNextcloudTalkChannelState, listNextcloudTalkAccountIds, mergeNextcloudTalkAccountConfig, resolveDefaultNextcloudTalkAccountId };
|
package/dist/runtime-api.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as GROUP_POLICY_BLOCKED_LABEL, n as createChannelPairingController, r as
|
|
1
|
+
export { t as GROUP_POLICY_BLOCKED_LABEL, n as createChannelPairingController, r as fetchWithSsrFGuard, i as logInboundDrop, a as resolveAllowlistProviderRuntimeGroupPolicy, o as resolveDefaultGroupPolicy, u as setNextcloudTalkRuntime, s as warnMissingProviderGroupPolicyFallbackOnce } from "./.setup/runtime-api-CGy_KpdV.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as channelSecrets, n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./.setup/secret-contract-
|
|
1
|
+
export { t as channelSecrets, n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./.setup/secret-contract-yjCLewtg.mjs";
|
package/dist/setup-entry.js
CHANGED
|
@@ -4,11 +4,11 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
var setup_entry_default = defineBundledChannelSetupEntry({
|
|
5
5
|
importMetaUrl: import.meta.url,
|
|
6
6
|
plugin: {
|
|
7
|
-
specifier: fileURLToPath(new URL(".setup/api-
|
|
7
|
+
specifier: fileURLToPath(new URL(".setup/api-BvrRSilb.mjs", import.meta.url).href),
|
|
8
8
|
exportName: "nextcloudTalkPlugin"
|
|
9
9
|
},
|
|
10
10
|
secrets: {
|
|
11
|
-
specifier: fileURLToPath(new URL(".setup/secret-contract-api-
|
|
11
|
+
specifier: fileURLToPath(new URL(".setup/secret-contract-api-QMihLdr1.mjs", import.meta.url).href),
|
|
12
12
|
exportName: "channelSecrets"
|
|
13
13
|
}
|
|
14
14
|
});
|