@mulmobridge/telegram 0.1.1 → 0.1.3
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 +37 -7
- package/dist/api.d.ts +16 -2
- package/dist/api.js +22 -2
- package/dist/index.js +6 -6
- package/dist/router.d.ts +2 -0
- package/dist/router.js +138 -32
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -46,13 +46,43 @@ The bridge enforces a **chat-ID allowlist**. Only messages from listed chat IDs
|
|
|
46
46
|
|
|
47
47
|
## Ecosystem
|
|
48
48
|
|
|
49
|
-
Part of the `@mulmobridge/*` package family
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
49
|
+
Part of the [`@mulmobridge/*`](https://www.npmjs.com/~mulmobridge) package family.
|
|
50
|
+
|
|
51
|
+
**Shared libraries:**
|
|
52
|
+
|
|
53
|
+
- [`@mulmobridge/client`](https://www.npmjs.com/package/@mulmobridge/client) — socket.io client library used by every bridge below
|
|
54
|
+
- [`@mulmobridge/protocol`](https://www.npmjs.com/package/@mulmobridge/protocol) — wire types and constants
|
|
55
|
+
- [`@mulmobridge/chat-service`](https://www.npmjs.com/package/@mulmobridge/chat-service) — server-side relay + session store
|
|
56
|
+
- [`@mulmobridge/relay`](https://www.npmjs.com/package/@mulmobridge/relay) — Cloudflare Workers webhook proxy
|
|
57
|
+
- [`@mulmobridge/mock-server`](https://www.npmjs.com/package/@mulmobridge/mock-server) — mock server for local bridge development
|
|
58
|
+
|
|
59
|
+
**Bridges** (one npm package per platform):
|
|
60
|
+
|
|
61
|
+
- [`@mulmobridge/bluesky`](https://www.npmjs.com/package/@mulmobridge/bluesky) — Bluesky DMs over atproto
|
|
62
|
+
- [`@mulmobridge/chatwork`](https://www.npmjs.com/package/@mulmobridge/chatwork) — Chatwork (Japanese business chat)
|
|
63
|
+
- [`@mulmobridge/cli`](https://www.npmjs.com/package/@mulmobridge/cli) — interactive terminal bridge
|
|
64
|
+
- [`@mulmobridge/discord`](https://www.npmjs.com/package/@mulmobridge/discord) — Discord bot via Gateway
|
|
65
|
+
- [`@mulmobridge/email`](https://www.npmjs.com/package/@mulmobridge/email) — IMAP poll + SMTP reply, threading preserved
|
|
66
|
+
- [`@mulmobridge/google-chat`](https://www.npmjs.com/package/@mulmobridge/google-chat) — Google Chat via MulmoBridge relay
|
|
67
|
+
- [`@mulmobridge/irc`](https://www.npmjs.com/package/@mulmobridge/irc) — IRC (Libera, Freenode, custom)
|
|
68
|
+
- [`@mulmobridge/line`](https://www.npmjs.com/package/@mulmobridge/line) — LINE Messaging API via MulmoBridge relay
|
|
69
|
+
- [`@mulmobridge/line-works`](https://www.npmjs.com/package/@mulmobridge/line-works) — LINE Works (enterprise LINE)
|
|
70
|
+
- [`@mulmobridge/mastodon`](https://www.npmjs.com/package/@mulmobridge/mastodon) — Mastodon DMs + mentions
|
|
71
|
+
- [`@mulmobridge/matrix`](https://www.npmjs.com/package/@mulmobridge/matrix) — Matrix / Element
|
|
72
|
+
- [`@mulmobridge/mattermost`](https://www.npmjs.com/package/@mulmobridge/mattermost) — Mattermost
|
|
73
|
+
- [`@mulmobridge/messenger`](https://www.npmjs.com/package/@mulmobridge/messenger) — Facebook Messenger via MulmoBridge relay
|
|
74
|
+
- [`@mulmobridge/nostr`](https://www.npmjs.com/package/@mulmobridge/nostr) — Nostr NIP-04 encrypted DMs
|
|
75
|
+
- [`@mulmobridge/rocketchat`](https://www.npmjs.com/package/@mulmobridge/rocketchat) — Rocket.Chat
|
|
76
|
+
- [`@mulmobridge/signal`](https://www.npmjs.com/package/@mulmobridge/signal) — Signal via signal-cli-rest-api
|
|
77
|
+
- [`@mulmobridge/slack`](https://www.npmjs.com/package/@mulmobridge/slack) — Slack Socket Mode
|
|
78
|
+
- [`@mulmobridge/teams`](https://www.npmjs.com/package/@mulmobridge/teams) — Microsoft Teams via Bot Framework
|
|
79
|
+
- [`@mulmobridge/telegram`](https://www.npmjs.com/package/@mulmobridge/telegram) — Telegram bot ← **this package**
|
|
80
|
+
- [`@mulmobridge/twilio-sms`](https://www.npmjs.com/package/@mulmobridge/twilio-sms) — SMS via Twilio Programmable Messaging
|
|
81
|
+
- [`@mulmobridge/viber`](https://www.npmjs.com/package/@mulmobridge/viber) — Viber Public Account bots
|
|
82
|
+
- [`@mulmobridge/webhook`](https://www.npmjs.com/package/@mulmobridge/webhook) — generic HTTP webhook bridge
|
|
83
|
+
- [`@mulmobridge/whatsapp`](https://www.npmjs.com/package/@mulmobridge/whatsapp) — WhatsApp Cloud API via MulmoBridge relay
|
|
84
|
+
- [`@mulmobridge/xmpp`](https://www.npmjs.com/package/@mulmobridge/xmpp) — XMPP / Jabber
|
|
85
|
+
- [`@mulmobridge/zulip`](https://www.npmjs.com/package/@mulmobridge/zulip) — Zulip
|
|
56
86
|
|
|
57
87
|
## License
|
|
58
88
|
|
package/dist/api.d.ts
CHANGED
|
@@ -9,12 +9,20 @@ export interface TelegramPhotoSize {
|
|
|
9
9
|
height: number;
|
|
10
10
|
file_size?: number;
|
|
11
11
|
}
|
|
12
|
+
export interface TelegramDocument {
|
|
13
|
+
file_id: string;
|
|
14
|
+
file_unique_id: string;
|
|
15
|
+
file_name?: string;
|
|
16
|
+
mime_type?: string;
|
|
17
|
+
file_size?: number;
|
|
18
|
+
}
|
|
12
19
|
export interface TelegramMessage {
|
|
13
20
|
message_id: number;
|
|
14
21
|
chat: TelegramChat;
|
|
15
22
|
from?: TelegramUser;
|
|
16
23
|
text?: string;
|
|
17
24
|
photo?: TelegramPhotoSize[];
|
|
25
|
+
document?: TelegramDocument;
|
|
18
26
|
caption?: string;
|
|
19
27
|
date: number;
|
|
20
28
|
}
|
|
@@ -44,7 +52,13 @@ export interface GetUpdatesOptions {
|
|
|
44
52
|
}
|
|
45
53
|
export interface TelegramApi {
|
|
46
54
|
getUpdates(opts?: GetUpdatesOptions): Promise<TelegramUpdate[]>;
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
/** Send a message and return its message_id (needed for editMessageText). */
|
|
56
|
+
sendMessage(chatId: number, text: string): Promise<number>;
|
|
57
|
+
/** Edit an existing message in place. Used for streaming text updates. */
|
|
58
|
+
editMessageText(chatId: number, messageId: number, text: string): Promise<void>;
|
|
59
|
+
/** Download any file by file_id via the Telegram getFile API.
|
|
60
|
+
* Returns a data URL (`data:<mime>;base64,...`).
|
|
61
|
+
* Works for photos, documents, audio, etc. */
|
|
62
|
+
downloadFile(fileId: string, fallbackMime?: string): Promise<string>;
|
|
49
63
|
}
|
|
50
64
|
export declare function createTelegramApi(opts: TelegramApiOptions): TelegramApi;
|
package/dist/api.js
CHANGED
|
@@ -36,8 +36,25 @@ export function createTelegramApi(opts) {
|
|
|
36
36
|
if (!body.ok) {
|
|
37
37
|
throw new Error(`sendMessage API error: ${body.description ?? "unknown"}`);
|
|
38
38
|
}
|
|
39
|
+
return body.result?.message_id ?? 0;
|
|
39
40
|
},
|
|
40
|
-
async
|
|
41
|
+
async editMessageText(chatId, messageId, text) {
|
|
42
|
+
const res = await fetchImpl(`${base}/editMessageText`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
chat_id: chatId,
|
|
47
|
+
message_id: messageId,
|
|
48
|
+
text,
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
if (!res.ok) {
|
|
52
|
+
// editMessageText fails if the text is unchanged (Telegram
|
|
53
|
+
// returns 400 "message is not modified"). Swallow silently.
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
async downloadFile(fileId, fallbackMime = "application/octet-stream") {
|
|
41
58
|
const getFileRes = await fetchImpl(`${base}/getFile?file_id=${encodeURIComponent(fileId)}`);
|
|
42
59
|
if (!getFileRes.ok) {
|
|
43
60
|
throw new Error(`getFile failed: ${getFileRes.status} ${await safeText(getFileRes)}`);
|
|
@@ -53,8 +70,11 @@ export function createTelegramApi(opts) {
|
|
|
53
70
|
}
|
|
54
71
|
const buffer = await fileRes.arrayBuffer();
|
|
55
72
|
const b64 = Buffer.from(buffer).toString("base64");
|
|
73
|
+
// Prefer the caller-supplied MIME (Telegram's doc.mime_type is
|
|
74
|
+
// authoritative). Fall back to extension-based inference only
|
|
75
|
+
// when the caller passed the generic default.
|
|
56
76
|
const ext = getFileBody.result.file_path.split(".").pop() ?? "";
|
|
57
|
-
const mediaType = mimeFromExtension(ext,
|
|
77
|
+
const mediaType = fallbackMime !== "application/octet-stream" ? fallbackMime : mimeFromExtension(ext, fallbackMime);
|
|
58
78
|
return buildDataUrl(mediaType, b64);
|
|
59
79
|
},
|
|
60
80
|
};
|
package/dist/index.js
CHANGED
|
@@ -71,9 +71,7 @@ function delay(ms) {
|
|
|
71
71
|
async function main() {
|
|
72
72
|
const { botToken, allowlist, pollTimeoutSec } = readEnv();
|
|
73
73
|
console.log("MulmoClaude Telegram bridge");
|
|
74
|
-
console.log(`Allowlist: ${allowlist.size() > 0
|
|
75
|
-
? allowlist.snapshot().join(", ")
|
|
76
|
-
: "(empty — all chats will be denied)"}`);
|
|
74
|
+
console.log(`Allowlist: ${allowlist.size() > 0 ? allowlist.snapshot().join(", ") : "(empty — all chats will be denied)"}`);
|
|
77
75
|
const api = createTelegramApi({ botToken });
|
|
78
76
|
const client = createBridgeClient({ transportId: TRANSPORT_ID });
|
|
79
77
|
const router = createMessageRouter({
|
|
@@ -81,11 +79,13 @@ async function main() {
|
|
|
81
79
|
allowlist,
|
|
82
80
|
sendToMulmo: (chatId, text, attachments) => client.send(chatId, text, attachments),
|
|
83
81
|
});
|
|
82
|
+
// Server → Telegram streaming text chunks (Phase C of #268).
|
|
83
|
+
client.onTextChunk((chunk) => {
|
|
84
|
+
router.handleTextChunk(chunk);
|
|
85
|
+
});
|
|
84
86
|
// Server → Telegram async push (Phase B of #268).
|
|
85
87
|
client.onPush((ev) => {
|
|
86
|
-
router
|
|
87
|
-
.handlePush(ev)
|
|
88
|
-
.catch((err) => console.error(`[telegram] handlePush failed: ${String(err)}`));
|
|
88
|
+
router.handlePush(ev).catch((err) => console.error(`[telegram] handlePush failed: ${String(err)}`));
|
|
89
89
|
});
|
|
90
90
|
const abortController = new AbortController();
|
|
91
91
|
process.on("SIGINT", () => {
|
package/dist/router.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface RouterDeps {
|
|
|
18
18
|
export interface MessageRouter {
|
|
19
19
|
handleMessage(msg: TelegramMessage): Promise<void>;
|
|
20
20
|
handlePush(ev: PushEvent): Promise<void>;
|
|
21
|
+
/** Called for each streaming text chunk from the server. */
|
|
22
|
+
handleTextChunk(chunk: string): void;
|
|
21
23
|
/** For tests / debugging: chat IDs we've already sent the
|
|
22
24
|
* access-denied notice to. */
|
|
23
25
|
deniedAlreadyNotified(): ReadonlySet<number>;
|
package/dist/router.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Telegram API, no real MulmoClaude socket, no env reads). The
|
|
4
4
|
// entrypoint in index.ts is then just: read env → wire real deps →
|
|
5
5
|
// drive the polling loop.
|
|
6
|
-
import { parseDataUrl
|
|
6
|
+
import { parseDataUrl } from "@mulmobridge/client";
|
|
7
7
|
// Telegram caps a single message at 4096 chars. We split long
|
|
8
8
|
// replies naively; pretty formatting (preserve markdown, break on
|
|
9
9
|
// sentence boundaries) is a follow-up.
|
|
@@ -13,30 +13,107 @@ const defaultLog = {
|
|
|
13
13
|
warn: (m) => console.warn(m),
|
|
14
14
|
error: (m) => console.error(m),
|
|
15
15
|
};
|
|
16
|
+
// Minimum interval between editMessageText calls to avoid Telegram
|
|
17
|
+
// rate limits. 1 second is conservative; Telegram allows ~30 edits
|
|
18
|
+
// per minute per chat.
|
|
19
|
+
const STREAM_EDIT_INTERVAL_MS = 1000;
|
|
16
20
|
export function createMessageRouter(deps) {
|
|
17
21
|
const { api, allowlist, sendToMulmo } = deps;
|
|
18
22
|
const log = deps.log ?? defaultLog;
|
|
19
23
|
// One denial reply per chat per bridge lifetime — restart clears.
|
|
20
24
|
const deniedAlreadyNotified = new Set();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
// Streaming state: while a relay is in flight, chunks accumulate
|
|
26
|
+
// here. A timer periodically flushes them via editMessageText.
|
|
27
|
+
let streamChatId = null;
|
|
28
|
+
let streamMessageId = null;
|
|
29
|
+
let streamAccumulated = "";
|
|
30
|
+
let streamDirty = false;
|
|
31
|
+
let streamTimer = null;
|
|
32
|
+
function startStream(chatId) {
|
|
33
|
+
streamChatId = chatId;
|
|
34
|
+
streamMessageId = null;
|
|
35
|
+
streamAccumulated = "";
|
|
36
|
+
streamDirty = false;
|
|
37
|
+
streamTimer = setInterval(flushStream, STREAM_EDIT_INTERVAL_MS);
|
|
38
|
+
}
|
|
39
|
+
function stopStream() {
|
|
40
|
+
if (streamTimer !== null) {
|
|
41
|
+
clearInterval(streamTimer);
|
|
42
|
+
streamTimer = null;
|
|
43
|
+
}
|
|
44
|
+
streamChatId = null;
|
|
45
|
+
streamMessageId = null;
|
|
46
|
+
streamAccumulated = "";
|
|
47
|
+
streamDirty = false;
|
|
48
|
+
}
|
|
49
|
+
function flushStream() {
|
|
50
|
+
if (!streamDirty || streamChatId === null)
|
|
51
|
+
return;
|
|
52
|
+
streamDirty = false;
|
|
53
|
+
const chatId = streamChatId;
|
|
54
|
+
const text = streamAccumulated || "…";
|
|
55
|
+
if (streamMessageId === null) {
|
|
56
|
+
// First flush: send a new message
|
|
57
|
+
api
|
|
58
|
+
.sendMessage(chatId, text)
|
|
59
|
+
.then((msgId) => {
|
|
60
|
+
streamMessageId = msgId;
|
|
61
|
+
})
|
|
62
|
+
.catch((err) => {
|
|
63
|
+
log.error(`[telegram] stream sendMessage failed: ${String(err)}`);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Subsequent flushes: edit existing message
|
|
68
|
+
api.editMessageText(chatId, streamMessageId, text).catch((err) => {
|
|
69
|
+
log.error(`[telegram] stream editMessage failed: ${String(err)}`);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Download all media from a message. Telegram messages are usually
|
|
74
|
+
// exclusive (photo OR document), but forwarded messages or future
|
|
75
|
+
// API changes could carry both. We collect all available attachments
|
|
76
|
+
// rather than picking one.
|
|
77
|
+
async function tryDownloadAttachments(msg) {
|
|
78
|
+
const attachments = [];
|
|
79
|
+
let anyFailed = false;
|
|
80
|
+
if (Array.isArray(msg.photo) && msg.photo.length > 0) {
|
|
81
|
+
const largest = msg.photo[msg.photo.length - 1];
|
|
82
|
+
try {
|
|
83
|
+
const dataUrl = await api.downloadFile(largest.file_id, "image/jpeg");
|
|
84
|
+
const parsed = parseDataUrl(dataUrl);
|
|
85
|
+
if (parsed) {
|
|
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;
|
|
34
92
|
}
|
|
35
93
|
}
|
|
36
|
-
|
|
37
|
-
|
|
94
|
+
if (msg.document) {
|
|
95
|
+
const doc = msg.document;
|
|
96
|
+
const fallbackMime = doc.mime_type ?? "application/octet-stream";
|
|
97
|
+
try {
|
|
98
|
+
const dataUrl = await api.downloadFile(doc.file_id, fallbackMime);
|
|
99
|
+
const parsed = parseDataUrl(dataUrl);
|
|
100
|
+
if (parsed) {
|
|
101
|
+
attachments.push({
|
|
102
|
+
mimeType: parsed.mimeType,
|
|
103
|
+
data: parsed.data,
|
|
104
|
+
filename: doc.file_name,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
log.error(`[telegram] document download failed: ${String(err)}`);
|
|
110
|
+
anyFailed = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (attachments.length === 0 && anyFailed) {
|
|
114
|
+
return { attachments: [], failed: true };
|
|
38
115
|
}
|
|
39
|
-
return { attachments
|
|
116
|
+
return { attachments, failed: false };
|
|
40
117
|
}
|
|
41
118
|
async function sendReply(chatId, ack) {
|
|
42
119
|
if (ack.ok) {
|
|
@@ -50,23 +127,37 @@ export function createMessageRouter(deps) {
|
|
|
50
127
|
async function handleAllowed(msg) {
|
|
51
128
|
const chatId = msg.chat.id;
|
|
52
129
|
const text = msg.text ?? msg.caption ?? "";
|
|
53
|
-
const
|
|
54
|
-
if (text.trim().length === 0 && !
|
|
130
|
+
const hasMedia = (Array.isArray(msg.photo) && msg.photo.length > 0) || msg.document !== undefined;
|
|
131
|
+
if (text.trim().length === 0 && !hasMedia)
|
|
55
132
|
return;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
133
|
+
const mediaLabel = buildMediaLabel(msg);
|
|
134
|
+
log.info(`[telegram] accepted chat=${chatId} user=@${userLabel(msg)} len=${text.length}${mediaLabel}`);
|
|
135
|
+
const { attachments, failed } = await tryDownloadAttachments(msg);
|
|
136
|
+
// Attachment-only message where download failed: bail out instead
|
|
137
|
+
// of sending a query without the actual file.
|
|
60
138
|
if (failed && text.trim().length === 0) {
|
|
61
|
-
await api
|
|
62
|
-
.sendMessage(chatId, "Sorry, I could not download the photo. Please try again.")
|
|
63
|
-
.catch(() => { });
|
|
139
|
+
await api.sendMessage(chatId, "Sorry, I could not download the attachment. Please try again.").catch(() => { });
|
|
64
140
|
return;
|
|
65
141
|
}
|
|
66
|
-
// Surface the
|
|
142
|
+
// Surface the attachment failure so the user knows the file was lost.
|
|
67
143
|
const messageText = resolveMessageText(text, failed);
|
|
144
|
+
// Start streaming: chunks will arrive via handleTextChunk and
|
|
145
|
+
// be sent/edited in the Telegram chat in real time.
|
|
146
|
+
startStream(chatId);
|
|
68
147
|
const ack = await sendToMulmo(String(chatId), messageText, attachments.length > 0 ? attachments : undefined);
|
|
69
|
-
|
|
148
|
+
// Final flush + capture the message id before stopStream clears it.
|
|
149
|
+
flushStream();
|
|
150
|
+
const sentMsgId = streamMessageId;
|
|
151
|
+
stopStream();
|
|
152
|
+
// If streaming sent the text, do a final edit with the full ack
|
|
153
|
+
// to catch any trailing chunks. If no streaming happened,
|
|
154
|
+
// send the full reply as a new message.
|
|
155
|
+
if (sentMsgId !== null && ack.ok) {
|
|
156
|
+
await api.editMessageText(chatId, sentMsgId, ack.reply ?? "").catch(() => { });
|
|
157
|
+
}
|
|
158
|
+
else if (sentMsgId === null) {
|
|
159
|
+
await sendReply(chatId, ack);
|
|
160
|
+
}
|
|
70
161
|
}
|
|
71
162
|
async function handleDenied(msg) {
|
|
72
163
|
const chatId = msg.chat.id;
|
|
@@ -107,6 +198,12 @@ export function createMessageRouter(deps) {
|
|
|
107
198
|
log.error(`[telegram] push sendMessage failed: ${String(err)}`);
|
|
108
199
|
}
|
|
109
200
|
},
|
|
201
|
+
handleTextChunk(chunk) {
|
|
202
|
+
if (streamChatId === null)
|
|
203
|
+
return;
|
|
204
|
+
streamAccumulated += chunk;
|
|
205
|
+
streamDirty = true;
|
|
206
|
+
},
|
|
110
207
|
deniedAlreadyNotified() {
|
|
111
208
|
return deniedAlreadyNotified;
|
|
112
209
|
},
|
|
@@ -121,14 +218,23 @@ async function sendChunked(api, chatId, text) {
|
|
|
121
218
|
await api.sendMessage(chatId, text.slice(i, i + TELEGRAM_MAX_MESSAGE_CHARS));
|
|
122
219
|
}
|
|
123
220
|
}
|
|
124
|
-
function resolveMessageText(text,
|
|
221
|
+
function resolveMessageText(text, attachmentFailed) {
|
|
125
222
|
const hasText = text.trim().length > 0;
|
|
126
|
-
if (
|
|
127
|
-
return `${text}\n\n(note: attached
|
|
223
|
+
if (attachmentFailed && hasText) {
|
|
224
|
+
return `${text}\n\n(note: attached file could not be downloaded)`;
|
|
128
225
|
}
|
|
129
226
|
if (hasText)
|
|
130
227
|
return text;
|
|
131
|
-
return "
|
|
228
|
+
return "Describe / analyze this file.";
|
|
229
|
+
}
|
|
230
|
+
function buildMediaLabel(msg) {
|
|
231
|
+
if (msg.document) {
|
|
232
|
+
const hint = msg.document.file_name ?? msg.document.mime_type ?? "unknown";
|
|
233
|
+
return ` +file(${hint})`;
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(msg.photo) && msg.photo.length > 0)
|
|
236
|
+
return " +photo";
|
|
237
|
+
return "";
|
|
132
238
|
}
|
|
133
239
|
function userLabel(msg) {
|
|
134
240
|
return msg.from?.username ?? msg.from?.first_name ?? "unknown";
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mulmobridge/telegram",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Telegram bridge for MulmoBridge — connect a Telegram bot to MulmoClaude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
8
16
|
"bin": {
|
|
9
17
|
"mulmobridge-telegram": "./dist/index.js"
|
|
10
18
|
},
|
|
@@ -22,12 +30,12 @@
|
|
|
22
30
|
"license": "MIT",
|
|
23
31
|
"author": "Receptron Team",
|
|
24
32
|
"dependencies": {
|
|
25
|
-
"@mulmobridge/client": "^0.1.
|
|
26
|
-
"@mulmobridge/protocol": "^0.1.
|
|
33
|
+
"@mulmobridge/client": "^0.1.4",
|
|
34
|
+
"@mulmobridge/protocol": "^0.1.4",
|
|
27
35
|
"dotenv": "^17.0.0"
|
|
28
36
|
},
|
|
29
37
|
"devDependencies": {
|
|
30
|
-
"@types/node": "^
|
|
31
|
-
"typescript": "^
|
|
38
|
+
"@types/node": "^25.6.0",
|
|
39
|
+
"typescript": "^6.0.3"
|
|
32
40
|
}
|
|
33
41
|
}
|