clawgram 2.6.1 → 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 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.",
@@ -1346,6 +1369,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
1346
1369
  : captionText.replaceAll("\\n", "\n");
1347
1370
  const uploadReplyToId = (0, param_readers_1.readStringOrNumberParam)(params, "replyToId") ?? (0, param_readers_1.readStringOrNumberParam)(params, "replyTo");
1348
1371
  const uploadThreadId = (0, param_readers_1.readStringOrNumberParam)(params, "threadId");
1372
+ const asVoice = (0, helpers_1.readVoiceNoteFlag)(params);
1349
1373
  actionLog.info("clawgram handleAction upload-file", {
1350
1374
  accountId: uploadAccountId,
1351
1375
  dryRun: dryRun === true,
@@ -1353,6 +1377,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
1353
1377
  hasCaption: Boolean(caption),
1354
1378
  replyToId: uploadReplyToId ?? null,
1355
1379
  threadId: uploadThreadId ?? null,
1380
+ asVoice,
1356
1381
  });
1357
1382
  if (dryRun === true) {
1358
1383
  return (0, core_1.jsonResult)({
@@ -1372,6 +1397,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
1372
1397
  caption: caption || undefined,
1373
1398
  replyToMessageId: (0, helpers_1.resolveReplyToMessageIdForTarget)(rawUploadTo, uploadReplyToId),
1374
1399
  messageThreadId: parseOptionalThreadId(uploadThreadId),
1400
+ asVoice,
1375
1401
  });
1376
1402
  actionLog.info("clawgram handleAction upload-file completed", {
1377
1403
  accountId: uploadAccountId,
@@ -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") {
@@ -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.6.1",
5
+ "version": "2.7.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawgram",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
4
  "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {