clawgram 2.6.0 → 2.7.0
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/channel.js +46 -17
- package/dist/gramjs-client.js +18 -0
- package/dist/helpers.js +12 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -12,6 +12,36 @@ const node_fs_1 = require("node:fs");
|
|
|
12
12
|
* a different conversation from a spoken line or a screenshot, and the
|
|
13
13
|
* transfer is not free. */
|
|
14
14
|
const INBOUND_MEDIA_MAX_BYTES = 25 * 1024 * 1024;
|
|
15
|
+
/**
|
|
16
|
+
* What this channel promises the Gateway.
|
|
17
|
+
*
|
|
18
|
+
* Annotated with core's own `ChannelCapabilities` on purpose: the shape is read
|
|
19
|
+
* by core (`resolveChannelTtsVoiceDelivery` reaches straight into
|
|
20
|
+
* `capabilities.tts.voice`), so a typo here would not fail — it would silently
|
|
21
|
+
* fall back to a default. With the annotation the compiler checks the promise
|
|
22
|
+
* against the version of OpenClaw we build against.
|
|
23
|
+
*/
|
|
24
|
+
const CHANNEL_CAPABILITIES = {
|
|
25
|
+
chatTypes: ["direct", "group"],
|
|
26
|
+
reactions: true,
|
|
27
|
+
threads: true,
|
|
28
|
+
media: true,
|
|
29
|
+
nativeCommands: false,
|
|
30
|
+
blockStreaming: false,
|
|
31
|
+
// Without this key core resolves the default "audio-file" and delivers
|
|
32
|
+
// synthesized speech as a document: a grey file card you must download
|
|
33
|
+
// before you know what it is. Advertising "voice-note" makes core mark such
|
|
34
|
+
// sends with `asVoice`, which the upload path honours.
|
|
35
|
+
//
|
|
36
|
+
// `transcodesAudio` is deliberately absent: we ship no ffmpeg and add no
|
|
37
|
+
// dependencies, so core must hand us Ogg/Opus — the only container Telegram
|
|
38
|
+
// renders as a voice bubble.
|
|
39
|
+
tts: {
|
|
40
|
+
voice: {
|
|
41
|
+
synthesisTarget: "voice-note",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
15
45
|
const media_1 = require("./media");
|
|
16
46
|
const channel_runtime_1 = require("openclaw/plugin-sdk/channel-runtime");
|
|
17
47
|
const param_readers_1 = require("openclaw/plugin-sdk/param-readers");
|
|
@@ -201,14 +231,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
201
231
|
blurb: "Connect your personal Telegram account to OpenClaw via MTProto. Your AI assistant responds as you.",
|
|
202
232
|
aliases: ["tguserbot"],
|
|
203
233
|
},
|
|
204
|
-
capabilities:
|
|
205
|
-
chatTypes: ["direct", "group"],
|
|
206
|
-
reactions: true,
|
|
207
|
-
threads: true,
|
|
208
|
-
media: true,
|
|
209
|
-
nativeCommands: false,
|
|
210
|
-
blockStreaming: false,
|
|
211
|
-
},
|
|
234
|
+
capabilities: CHANNEL_CAPABILITIES,
|
|
212
235
|
agentPrompt: {
|
|
213
236
|
messageToolHints: () => [
|
|
214
237
|
"Use clawgram to send Telegram replies from the connected personal account.",
|
|
@@ -1316,22 +1339,25 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1316
1339
|
removed: reactionParams.remove,
|
|
1317
1340
|
});
|
|
1318
1341
|
}
|
|
1342
|
+
// Core normalizes whichever of these it filled in to a local path (see
|
|
1343
|
+
// `mediaSourceParams` above); `mediaUrl` stays a URL, which GramJS
|
|
1344
|
+
// accepts as well.
|
|
1345
|
+
const attachedFile = (0, param_readers_1.readStringParam)(params, "filePath")
|
|
1346
|
+
?? (0, param_readers_1.readStringParam)(params, "path")
|
|
1347
|
+
?? (0, param_readers_1.readStringParam)(params, "media")
|
|
1348
|
+
?? (0, param_readers_1.readStringParam)(params, "mediaUrl");
|
|
1319
1349
|
// Core dispatches `upload-file`; `sendAttachment` is its legacy alias
|
|
1320
|
-
// and arrives from older callers
|
|
1321
|
-
|
|
1350
|
+
// and arrives from older callers. A plain `send` carrying a file lands
|
|
1351
|
+
// here too — `openclaw message send --media` does exactly that, and
|
|
1352
|
+
// routing it to the text path dropped the file without a word.
|
|
1353
|
+
if (action === "upload-file" || action === "sendAttachment" || (action === "send" && attachedFile)) {
|
|
1322
1354
|
const rawUploadTo = (0, helpers_1.resolveActionTarget)(params, toolContext);
|
|
1323
1355
|
const uploadTo = (0, helpers_1.normalizeOutboundTarget)(rawUploadTo);
|
|
1324
1356
|
const uploadAccountId = resolveRuntimeAccountId(cfg, accountId);
|
|
1325
1357
|
if (!uploadAccountId) {
|
|
1326
1358
|
throw new Error("clawgram: no configured account found");
|
|
1327
1359
|
}
|
|
1328
|
-
|
|
1329
|
-
// (see `mediaSourceParams` above); `mediaUrl` stays a URL, which
|
|
1330
|
-
// GramJS accepts as well.
|
|
1331
|
-
const file = (0, param_readers_1.readStringParam)(params, "filePath")
|
|
1332
|
-
?? (0, param_readers_1.readStringParam)(params, "path")
|
|
1333
|
-
?? (0, param_readers_1.readStringParam)(params, "media")
|
|
1334
|
-
?? (0, param_readers_1.readStringParam)(params, "mediaUrl");
|
|
1360
|
+
const file = attachedFile;
|
|
1335
1361
|
if (!file) {
|
|
1336
1362
|
throw new Error("clawgram: upload-file requires filePath, path, media, or mediaUrl");
|
|
1337
1363
|
}
|
|
@@ -1343,6 +1369,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1343
1369
|
: captionText.replaceAll("\\n", "\n");
|
|
1344
1370
|
const uploadReplyToId = (0, param_readers_1.readStringOrNumberParam)(params, "replyToId") ?? (0, param_readers_1.readStringOrNumberParam)(params, "replyTo");
|
|
1345
1371
|
const uploadThreadId = (0, param_readers_1.readStringOrNumberParam)(params, "threadId");
|
|
1372
|
+
const asVoice = (0, helpers_1.readVoiceNoteFlag)(params);
|
|
1346
1373
|
actionLog.info("clawgram handleAction upload-file", {
|
|
1347
1374
|
accountId: uploadAccountId,
|
|
1348
1375
|
dryRun: dryRun === true,
|
|
@@ -1350,6 +1377,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1350
1377
|
hasCaption: Boolean(caption),
|
|
1351
1378
|
replyToId: uploadReplyToId ?? null,
|
|
1352
1379
|
threadId: uploadThreadId ?? null,
|
|
1380
|
+
asVoice,
|
|
1353
1381
|
});
|
|
1354
1382
|
if (dryRun === true) {
|
|
1355
1383
|
return (0, core_1.jsonResult)({
|
|
@@ -1369,6 +1397,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1369
1397
|
caption: caption || undefined,
|
|
1370
1398
|
replyToMessageId: (0, helpers_1.resolveReplyToMessageIdForTarget)(rawUploadTo, uploadReplyToId),
|
|
1371
1399
|
messageThreadId: parseOptionalThreadId(uploadThreadId),
|
|
1400
|
+
asVoice,
|
|
1372
1401
|
});
|
|
1373
1402
|
actionLog.info("clawgram handleAction upload-file completed", {
|
|
1374
1403
|
accountId: uploadAccountId,
|
package/dist/gramjs-client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GramJsClientManager = void 0;
|
|
4
|
+
exports.buildVoiceNoteParams = buildVoiceNoteParams;
|
|
4
5
|
const telegram_1 = require("telegram");
|
|
5
6
|
const sessions_1 = require("telegram/sessions");
|
|
6
7
|
const helpers_1 = require("./helpers");
|
|
@@ -79,6 +80,22 @@ function parseTargetWithThread(rawTarget) {
|
|
|
79
80
|
chatId: raw,
|
|
80
81
|
};
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Voice-message option for `sendFile`.
|
|
85
|
+
*
|
|
86
|
+
* GramJS branches on `voiceNote` and builds `DocumentAttributeAudio` with
|
|
87
|
+
* `voice: true` itself, so the attribute must not be assembled by hand.
|
|
88
|
+
* The key is omitted rather than set to `false`: this codebase already paid
|
|
89
|
+
* for the lesson that a key's mere presence can flip a branch (see the MTProxy
|
|
90
|
+
* note in `proxy-config.ts`).
|
|
91
|
+
*
|
|
92
|
+
* Telegram renders a voice bubble only for Ogg/Opus; core is responsible for
|
|
93
|
+
* handing us that container, which is why the channel does not advertise
|
|
94
|
+
* `transcodesAudio`.
|
|
95
|
+
*/
|
|
96
|
+
function buildVoiceNoteParams(asVoice) {
|
|
97
|
+
return asVoice === true ? { voiceNote: true } : {};
|
|
98
|
+
}
|
|
82
99
|
function buildForumReplyParams(messageThreadId, replyToMessageId) {
|
|
83
100
|
const normalizedThreadId = typeof messageThreadId === "number" && Number.isFinite(messageThreadId)
|
|
84
101
|
? Math.trunc(messageThreadId)
|
|
@@ -502,6 +519,7 @@ class GramJsClientManager {
|
|
|
502
519
|
file: args.file,
|
|
503
520
|
caption: args.caption,
|
|
504
521
|
...replyParams,
|
|
522
|
+
...buildVoiceNoteParams(args.asVoice),
|
|
505
523
|
});
|
|
506
524
|
}
|
|
507
525
|
}
|
package/dist/helpers.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.readLatestAssistantFallbackFromTranscript = readLatestAssistantFallbackF
|
|
|
15
15
|
exports.resolveActionTarget = resolveActionTarget;
|
|
16
16
|
exports.resolveReplyToMessageIdForTarget = resolveReplyToMessageIdForTarget;
|
|
17
17
|
exports.readMessageText = readMessageText;
|
|
18
|
+
exports.readVoiceNoteFlag = readVoiceNoteFlag;
|
|
18
19
|
exports.resolveAllowFrom = resolveAllowFrom;
|
|
19
20
|
exports.resolveGroupPolicy = resolveGroupPolicy;
|
|
20
21
|
exports.resolveGroups = resolveGroups;
|
|
@@ -193,6 +194,17 @@ function resolveReplyToMessageIdForTarget(rawTarget, replyToId) {
|
|
|
193
194
|
}
|
|
194
195
|
return undefined;
|
|
195
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Whether an outbound file should become a Telegram voice message.
|
|
199
|
+
*
|
|
200
|
+
* Core emits `asVoice` and, on some paths, the older `audioAsVoice` — both
|
|
201
|
+
* carry the same meaning, so both are read. Only a real `true` counts: a
|
|
202
|
+
* string "true" or a stray truthy value must not silently turn a document
|
|
203
|
+
* into a voice bubble.
|
|
204
|
+
*/
|
|
205
|
+
function readVoiceNoteFlag(params) {
|
|
206
|
+
return params?.asVoice === true || params?.audioAsVoice === true;
|
|
207
|
+
}
|
|
196
208
|
function readMessageText(params) {
|
|
197
209
|
const message = (0, core_1.readStringParam)(params, "message", { allowEmpty: true });
|
|
198
210
|
if (typeof message === "string") {
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "clawgram",
|
|
3
3
|
"name": "Clawgram",
|
|
4
4
|
"description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.7.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED