@mulmobridge/telegram 1.0.1 → 1.1.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/README.md +5 -3
- package/dist/api.d.ts +3 -3
- package/dist/api.js +1 -1
- package/dist/index.js +10 -6
- package/dist/router.js +34 -34
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,15 +28,17 @@ yarn telegram
|
|
|
28
28
|
|---|---|---|---|
|
|
29
29
|
| `TELEGRAM_BOT_TOKEN` | Yes | BotFather token | - |
|
|
30
30
|
| `TELEGRAM_ALLOWED_CHAT_IDS` | Yes | Comma-separated integer chat IDs | empty (deny all) |
|
|
31
|
-
| `MULMOCLAUDE_API_URL` | No | Server URL |
|
|
31
|
+
| `MULMOCLAUDE_API_URL` | No | Server URL | auto (`.server-port`; waits if nothing is published) |
|
|
32
32
|
| `MULMOCLAUDE_AUTH_TOKEN` | No | Bearer token override | reads from file |
|
|
33
33
|
| `TELEGRAM_POLL_TIMEOUT_SEC` | No | Long-poll timeout | `25` |
|
|
34
34
|
|
|
35
35
|
### Auth token persistence across server restarts
|
|
36
36
|
|
|
37
|
-
The MulmoClaude server regenerates a fresh bearer token on every startup and writes it to
|
|
37
|
+
The MulmoClaude server regenerates a fresh bearer token on every startup and writes it to `<workspace>/.session-token` (`$MULMOCLAUDE_WORKSPACE_PATH`, or `~/mulmoclaude` when unset), alongside the port it bound in `.server-port`.
|
|
38
38
|
|
|
39
|
-
**
|
|
39
|
+
**The bridge follows a restart on its own.** When the connection fails it re-reads both files, and if the server came back as a different generation — new token, new port, or both — it rebuilds its socket against it (#3078). You do not have to restart the bridge.
|
|
40
|
+
|
|
41
|
+
Pinning the token is still useful when the bridge runs **on a different machine** from the server, where it cannot read the workspace at all: set `MULMOCLAUDE_AUTH_TOKEN` to the same long random value on both sides. The server then uses it verbatim instead of regenerating.
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
44
|
# Server (one-time setup — same value across restarts)
|
package/dist/api.d.ts
CHANGED
|
@@ -46,9 +46,9 @@ export interface TelegramApiOptions {
|
|
|
46
46
|
fetchImpl?: typeof fetch;
|
|
47
47
|
}
|
|
48
48
|
export interface GetUpdatesOptions {
|
|
49
|
-
offset?: number;
|
|
50
|
-
timeoutSec?: number;
|
|
51
|
-
signal?: AbortSignal;
|
|
49
|
+
offset?: number | undefined;
|
|
50
|
+
timeoutSec?: number | undefined;
|
|
51
|
+
signal?: AbortSignal | undefined;
|
|
52
52
|
}
|
|
53
53
|
export interface TelegramApi {
|
|
54
54
|
getUpdates(opts?: GetUpdatesOptions): Promise<TelegramUpdate[]>;
|
package/dist/api.js
CHANGED
|
@@ -13,7 +13,7 @@ export function createTelegramApi(opts) {
|
|
|
13
13
|
params.set("timeout", String(gu.timeoutSec));
|
|
14
14
|
}
|
|
15
15
|
const url = `${base}/getUpdates?${params.toString()}`;
|
|
16
|
-
const res = await fetchImpl(url, { signal: gu.signal });
|
|
16
|
+
const res = await fetchImpl(url, gu.signal !== undefined ? { signal: gu.signal } : {});
|
|
17
17
|
if (!res.ok) {
|
|
18
18
|
throw new Error(`getUpdates failed: ${res.status} ${await safeText(res)}`);
|
|
19
19
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// MULMOCLAUDE_AUTH_TOKEN — optional override
|
|
13
13
|
// TELEGRAM_POLL_TIMEOUT_SEC — optional, default 25
|
|
14
14
|
import "dotenv/config";
|
|
15
|
-
import { createBridgeClient } from "@mulmobridge/client";
|
|
15
|
+
import { createBridgeClient, installProcessGuards } from "@mulmobridge/client";
|
|
16
16
|
import { createTelegramApi } from "./api.js";
|
|
17
17
|
import { parseAllowlist } from "./allowlist.js";
|
|
18
18
|
import { createMessageRouter } from "./router.js";
|
|
@@ -88,11 +88,15 @@ async function main() {
|
|
|
88
88
|
router.handlePush(ev).catch((err) => console.error(`[telegram] handlePush failed: ${String(err)}`));
|
|
89
89
|
});
|
|
90
90
|
const abortController = new AbortController();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
// Installed here rather than at module level because the shutdown work needs
|
|
92
|
+
// the controller and the socket. `main().catch` still covers the awaited
|
|
93
|
+
// startup path above, so nothing is unguarded in between.
|
|
94
|
+
installProcessGuards({
|
|
95
|
+
name: TRANSPORT_ID,
|
|
96
|
+
onShutdown: () => {
|
|
97
|
+
abortController.abort();
|
|
98
|
+
client.close();
|
|
99
|
+
},
|
|
96
100
|
});
|
|
97
101
|
await pollLoop(api, router, {
|
|
98
102
|
pollTimeoutSec,
|
package/dist/router.js
CHANGED
|
@@ -74,42 +74,42 @@ export function createMessageRouter(deps) {
|
|
|
74
74
|
// exclusive (photo OR document), but forwarded messages or future
|
|
75
75
|
// API changes could carry both. We collect all available attachments
|
|
76
76
|
// rather than picking one.
|
|
77
|
-
async function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
attachments.push({ mimeType: parsed.mimeType, data: parsed.data });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
catch (err) {
|
|
90
|
-
log.error(`[telegram] photo download failed: ${String(err)}`);
|
|
91
|
-
anyFailed = true;
|
|
92
|
-
}
|
|
77
|
+
async function downloadPhoto(msg) {
|
|
78
|
+
// Telegram lists photo sizes smallest-first, so the last entry is
|
|
79
|
+
// the highest resolution available.
|
|
80
|
+
const largest = Array.isArray(msg.photo) ? msg.photo[msg.photo.length - 1] : undefined;
|
|
81
|
+
if (!largest)
|
|
82
|
+
return null;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = parseDataUrl(await api.downloadFile(largest.file_id, "image/jpeg"));
|
|
85
|
+
return parsed ? { attachment: { mimeType: parsed.mimeType, data: parsed.data } } : null;
|
|
93
86
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
anyFailed = true;
|
|
111
|
-
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
log.error(`[telegram] photo download failed: ${String(err)}`);
|
|
89
|
+
return { failed: true };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function downloadDocument(msg) {
|
|
93
|
+
const doc = msg.document;
|
|
94
|
+
if (!doc)
|
|
95
|
+
return null;
|
|
96
|
+
const fallbackMime = doc.mime_type ?? "application/octet-stream";
|
|
97
|
+
try {
|
|
98
|
+
const parsed = parseDataUrl(await api.downloadFile(doc.file_id, fallbackMime));
|
|
99
|
+
if (!parsed)
|
|
100
|
+
return null;
|
|
101
|
+
const filename = doc.file_name !== undefined ? { filename: doc.file_name } : {};
|
|
102
|
+
return { attachment: { mimeType: parsed.mimeType, data: parsed.data, ...filename } };
|
|
112
103
|
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
log.error(`[telegram] document download failed: ${String(err)}`);
|
|
106
|
+
return { failed: true };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function tryDownloadAttachments(msg) {
|
|
110
|
+
const outcomes = [await downloadPhoto(msg), await downloadDocument(msg)];
|
|
111
|
+
const attachments = outcomes.flatMap((outcome) => (outcome && "attachment" in outcome ? [outcome.attachment] : []));
|
|
112
|
+
const anyFailed = outcomes.some((outcome) => outcome !== null && "failed" in outcome);
|
|
113
113
|
if (attachments.length === 0 && anyFailed) {
|
|
114
114
|
return { attachments: [], failed: true };
|
|
115
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mulmobridge/telegram",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Telegram bridge for MulmoBridge — connect a Telegram bot to MulmoClaude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"author": "Receptron Team",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@mulmobridge/client": "^1.0
|
|
33
|
+
"@mulmobridge/client": "^1.2.0",
|
|
34
34
|
"@mulmobridge/protocol": "^1.0.1",
|
|
35
35
|
"dotenv": "^17.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@types/node": "^26.
|
|
38
|
+
"@types/node": "^26.4.1",
|
|
39
39
|
"typescript": "^6.0.3"
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/receptron/mulmoclaude/tree/main/packages/bridges/telegram#readme",
|