@goodandready/dsh-messenger-gateway 0.4.0 → 0.4.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.
@@ -1,141 +1,141 @@
1
- import { basename } from 'node:path'
2
- import {
3
- IMAGE_EXT_TO_MIME, VIDEO_EXT_TO_MIME, classifyDocument,
4
- extOf, TELEGRAM_MAX_DOC_BYTES,
5
- } from '../media.js'
6
- import { TEXT_INJECT_EXTS } from '../documents.js'
7
-
8
- export async function extractTelegramInboundMedia(adapter, msg, initialText, maxDocBytes = TELEGRAM_MAX_DOC_BYTES, maxTextInjectBytes = 100 * 1024) {
9
- let text = initialText || ''
10
- const attachments = []
11
-
12
- if (msg.photo?.length) {
13
- const largest = msg.photo[msg.photo.length - 1]
14
- const { path, file } = await adapter.downloadByFileId(largest.file_id, 'photo', extOf('', '') || '.jpg')
15
- const ext = extOf(file.file_path, '') || '.jpg'
16
- attachments.push({ kind: 'photo', path, mime: IMAGE_EXT_TO_MIME[ext] || 'image/jpeg' })
17
- }
18
- if (msg.sticker) {
19
- const st = msg.sticker
20
- if (st.is_video) {
21
- try {
22
- const { path } = await adapter.downloadByFileId(st.file_id, 'sticker-video', '.webm', st.file_unique_id || '')
23
- attachments.push({ kind: 'animation', path, mime: 'video/webm', emoji: st.emoji || '', name: `sticker${st.emoji || ''}.webm` })
24
- if (st.emoji) text = `${text}\n[Video sticker ${st.emoji}]`.trim()
25
- } catch (e) {
26
- text = `${text}\n[Video sticker ${st.emoji || ''} (download failed: ${e.message})]`.trim()
27
- }
28
- } else if (st.is_animated) {
29
- try {
30
- const { path } = await adapter.downloadByFileId(st.file_id, 'sticker-anim', '.tgs', st.file_unique_id || '')
31
- attachments.push({ kind: 'document', path, mime: 'application/x-tgsticker', emoji: st.emoji || '', name: `sticker${st.emoji || ''}.tgs` })
32
- if (st.emoji) text = `${text}\n[Animated sticker ${st.emoji}]`.trim()
33
- } catch (e) {
34
- text = `${text}\n[Animated sticker ${st.emoji || ''} (download failed: ${e.message})]`.trim()
35
- }
36
- } else {
37
- const { path } = await adapter.downloadByFileId(st.file_id, 'sticker', '.webp', st.file_unique_id || '')
38
- attachments.push({ kind: 'sticker', path, mime: 'image/webp', emoji: st.emoji || '', name: `sticker${st.emoji || ''}.webp` })
39
- }
40
- }
41
- if (msg.voice) {
42
- const { path } = await adapter.downloadByFileId(msg.voice.file_id, 'voice', '.ogg')
43
- attachments.push({ kind: 'voice', path, mime: 'audio/ogg' })
44
- }
45
- if (msg.audio) {
46
- const ext = extOf(msg.audio.file_name, msg.audio.mime_type) || '.mp3'
47
- const { path } = await adapter.downloadByFileId(msg.audio.file_id, 'audio', ext, msg.audio.file_name || '')
48
- attachments.push({ kind: 'audio', path, mime: msg.audio.mime_type || 'audio/mpeg' })
49
- }
50
- if (msg.video) {
51
- const ext = extOf(msg.video.file_name, msg.video.mime_type) || '.mp4'
52
- if ((msg.video.file_size || 0) > maxDocBytes) {
53
- text = `${text}\n[Video too large]`.trim()
54
- } else {
55
- const { path } = await adapter.downloadByFileId(msg.video.file_id, 'video', ext, msg.video.file_name || '')
56
- attachments.push({ kind: 'video', path, mime: msg.video.mime_type || VIDEO_EXT_TO_MIME[ext] || 'video/mp4', name: msg.video.file_name || basename(path) })
57
- }
58
- }
59
- if (msg.video_note) {
60
- const vn = msg.video_note
61
- if ((vn.file_size || 0) > maxDocBytes) {
62
- text = `${text}\n[Video note too large]`.trim()
63
- } else {
64
- const { path } = await adapter.downloadByFileId(vn.file_id, 'videonote', '.mp4')
65
- attachments.push({ kind: 'video', path, mime: 'video/mp4', name: 'video_note.mp4' })
66
- }
67
- }
68
- if (msg.animation) {
69
- const an = msg.animation
70
- const ext = extOf(an.file_name, an.mime_type) || '.mp4'
71
- if ((an.file_size || 0) > maxDocBytes) {
72
- text = `${text}\n[Animation too large]`.trim()
73
- } else {
74
- const { path } = await adapter.downloadByFileId(an.file_id, 'anim', ext, an.file_name || '')
75
- attachments.push({ kind: 'animation', path, mime: an.mime_type || 'video/mp4', name: an.file_name || basename(path) })
76
- }
77
- }
78
- if (msg.document) {
79
- const doc = msg.document
80
- const ext = extOf(doc.file_name, doc.mime_type)
81
- const kind = classifyDocument(ext, doc.mime_type)
82
- if (doc.file_size > maxDocBytes) {
83
- text = `${text}\n[Document too large: ${doc.file_name || 'file'}]`.trim()
84
- } else if (kind === 'unsupported') {
85
- const { path } = await adapter.downloadByFileId(doc.file_id, 'doc', ext, doc.file_name || '')
86
- attachments.push({ kind: 'document', path, mime: doc.mime_type || 'application/octet-stream', name: doc.file_name || basename(path) })
87
- } else {
88
- const { path, bytes } = await adapter.downloadByFileId(doc.file_id, 'doc', ext, doc.file_name || '')
89
- if (kind === 'image') attachments.push({ kind: 'photo', path, mime: IMAGE_EXT_TO_MIME[ext] || doc.mime_type || 'image/jpeg', name: doc.file_name })
90
- else if (kind === 'video') attachments.push({ kind: 'video', path, mime: VIDEO_EXT_TO_MIME[ext] || doc.mime_type || 'video/mp4', name: doc.file_name })
91
- else if (bytes.length <= maxTextInjectBytes && TEXT_INJECT_EXTS.has(ext)) {
92
- const body = new TextDecoder('utf-8', { fatal: false }).decode(bytes).slice(0, maxTextInjectBytes)
93
- text = `${text}\n\n[Document ${doc.file_name}]\n${body}`.trim()
94
- } else attachments.push({ kind: 'document', path, mime: doc.mime_type || 'application/octet-stream', name: doc.file_name || basename(path) })
95
- }
96
- }
97
-
98
-
99
- return { text, attachments }
100
- }
101
-
102
- export async function probeTelegramHealth(adapter, timeoutMs = 10000) {
103
- if (!adapter.token) {
104
- return { ok: false, error: 'Telegram bot token is empty' }
105
- }
106
- const start = Date.now()
107
- try {
108
- const res = await fetch(`https://api.telegram.org/bot${adapter.token}/getMe`, {
109
- method: 'POST',
110
- headers: { 'Content-Type': 'application/json' },
111
- body: JSON.stringify({}),
112
- signal: AbortSignal.timeout(Math.max(1000, Number(timeoutMs) || 10000)),
113
- })
114
- const latencyMs = Date.now() - start
115
- const json = await res.json().catch(() => ({}))
116
- if (!res.ok || json.ok === false) {
117
- return {
118
- ok: false,
119
- latencyMs,
120
- error: json.description || `HTTP ${res.status}`,
121
- }
122
- }
123
- const me = json.result || {}
124
- adapter.botId = Number(me.id) || adapter.botId
125
- adapter.botUsername = String(me.username || adapter.botUsername)
126
- return {
127
- ok: true,
128
- latencyMs,
129
- botId: adapter.botId,
130
- botUsername: adapter.botUsername,
131
- firstName: me.first_name || '',
132
- }
133
- } catch (err) {
134
- return {
135
- ok: false,
136
- latencyMs: Date.now() - start,
137
- error: err instanceof Error ? err.message : String(err),
138
- }
139
- }
140
- }
141
-
1
+ import { basename } from 'node:path'
2
+ import {
3
+ IMAGE_EXT_TO_MIME, VIDEO_EXT_TO_MIME, classifyDocument,
4
+ extOf, TELEGRAM_MAX_DOC_BYTES,
5
+ } from '../media.js'
6
+ import { TEXT_INJECT_EXTS } from '../documents.js'
7
+
8
+ export async function extractTelegramInboundMedia(adapter, msg, initialText, maxDocBytes = TELEGRAM_MAX_DOC_BYTES, maxTextInjectBytes = 100 * 1024) {
9
+ let text = initialText || ''
10
+ const attachments = []
11
+
12
+ if (msg.photo?.length) {
13
+ const largest = msg.photo[msg.photo.length - 1]
14
+ const { path, file } = await adapter.downloadByFileId(largest.file_id, 'photo', extOf('', '') || '.jpg')
15
+ const ext = extOf(file.file_path, '') || '.jpg'
16
+ attachments.push({ kind: 'photo', path, mime: IMAGE_EXT_TO_MIME[ext] || 'image/jpeg' })
17
+ }
18
+ if (msg.sticker) {
19
+ const st = msg.sticker
20
+ if (st.is_video) {
21
+ try {
22
+ const { path } = await adapter.downloadByFileId(st.file_id, 'sticker-video', '.webm', st.file_unique_id || '')
23
+ attachments.push({ kind: 'animation', path, mime: 'video/webm', emoji: st.emoji || '', name: `sticker${st.emoji || ''}.webm` })
24
+ if (st.emoji) text = `${text}\n[Video sticker ${st.emoji}]`.trim()
25
+ } catch (e) {
26
+ text = `${text}\n[Video sticker ${st.emoji || ''} (download failed: ${e.message})]`.trim()
27
+ }
28
+ } else if (st.is_animated) {
29
+ try {
30
+ const { path } = await adapter.downloadByFileId(st.file_id, 'sticker-anim', '.tgs', st.file_unique_id || '')
31
+ attachments.push({ kind: 'document', path, mime: 'application/x-tgsticker', emoji: st.emoji || '', name: `sticker${st.emoji || ''}.tgs` })
32
+ if (st.emoji) text = `${text}\n[Animated sticker ${st.emoji}]`.trim()
33
+ } catch (e) {
34
+ text = `${text}\n[Animated sticker ${st.emoji || ''} (download failed: ${e.message})]`.trim()
35
+ }
36
+ } else {
37
+ const { path } = await adapter.downloadByFileId(st.file_id, 'sticker', '.webp', st.file_unique_id || '')
38
+ attachments.push({ kind: 'sticker', path, mime: 'image/webp', emoji: st.emoji || '', name: `sticker${st.emoji || ''}.webp` })
39
+ }
40
+ }
41
+ if (msg.voice) {
42
+ const { path } = await adapter.downloadByFileId(msg.voice.file_id, 'voice', '.ogg')
43
+ attachments.push({ kind: 'voice', path, mime: 'audio/ogg' })
44
+ }
45
+ if (msg.audio) {
46
+ const ext = extOf(msg.audio.file_name, msg.audio.mime_type) || '.mp3'
47
+ const { path } = await adapter.downloadByFileId(msg.audio.file_id, 'audio', ext, msg.audio.file_name || '')
48
+ attachments.push({ kind: 'audio', path, mime: msg.audio.mime_type || 'audio/mpeg' })
49
+ }
50
+ if (msg.video) {
51
+ const ext = extOf(msg.video.file_name, msg.video.mime_type) || '.mp4'
52
+ if ((msg.video.file_size || 0) > maxDocBytes) {
53
+ text = `${text}\n[Video too large]`.trim()
54
+ } else {
55
+ const { path } = await adapter.downloadByFileId(msg.video.file_id, 'video', ext, msg.video.file_name || '')
56
+ attachments.push({ kind: 'video', path, mime: msg.video.mime_type || VIDEO_EXT_TO_MIME[ext] || 'video/mp4', name: msg.video.file_name || basename(path) })
57
+ }
58
+ }
59
+ if (msg.video_note) {
60
+ const vn = msg.video_note
61
+ if ((vn.file_size || 0) > maxDocBytes) {
62
+ text = `${text}\n[Video note too large]`.trim()
63
+ } else {
64
+ const { path } = await adapter.downloadByFileId(vn.file_id, 'videonote', '.mp4')
65
+ attachments.push({ kind: 'video', path, mime: 'video/mp4', name: 'video_note.mp4' })
66
+ }
67
+ }
68
+ if (msg.animation) {
69
+ const an = msg.animation
70
+ const ext = extOf(an.file_name, an.mime_type) || '.mp4'
71
+ if ((an.file_size || 0) > maxDocBytes) {
72
+ text = `${text}\n[Animation too large]`.trim()
73
+ } else {
74
+ const { path } = await adapter.downloadByFileId(an.file_id, 'anim', ext, an.file_name || '')
75
+ attachments.push({ kind: 'animation', path, mime: an.mime_type || 'video/mp4', name: an.file_name || basename(path) })
76
+ }
77
+ }
78
+ if (msg.document) {
79
+ const doc = msg.document
80
+ const ext = extOf(doc.file_name, doc.mime_type)
81
+ const kind = classifyDocument(ext, doc.mime_type)
82
+ if (doc.file_size > maxDocBytes) {
83
+ text = `${text}\n[Document too large: ${doc.file_name || 'file'}]`.trim()
84
+ } else if (kind === 'unsupported') {
85
+ const { path } = await adapter.downloadByFileId(doc.file_id, 'doc', ext, doc.file_name || '')
86
+ attachments.push({ kind: 'document', path, mime: doc.mime_type || 'application/octet-stream', name: doc.file_name || basename(path) })
87
+ } else {
88
+ const { path, bytes } = await adapter.downloadByFileId(doc.file_id, 'doc', ext, doc.file_name || '')
89
+ if (kind === 'image') attachments.push({ kind: 'photo', path, mime: IMAGE_EXT_TO_MIME[ext] || doc.mime_type || 'image/jpeg', name: doc.file_name })
90
+ else if (kind === 'video') attachments.push({ kind: 'video', path, mime: VIDEO_EXT_TO_MIME[ext] || doc.mime_type || 'video/mp4', name: doc.file_name })
91
+ else if (bytes.length <= maxTextInjectBytes && TEXT_INJECT_EXTS.has(ext)) {
92
+ const body = new TextDecoder('utf-8', { fatal: false }).decode(bytes).slice(0, maxTextInjectBytes)
93
+ text = `${text}\n\n[Document ${doc.file_name}]\n${body}`.trim()
94
+ } else attachments.push({ kind: 'document', path, mime: doc.mime_type || 'application/octet-stream', name: doc.file_name || basename(path) })
95
+ }
96
+ }
97
+
98
+
99
+ return { text, attachments }
100
+ }
101
+
102
+ export async function probeTelegramHealth(adapter, timeoutMs = 10000) {
103
+ if (!adapter.token) {
104
+ return { ok: false, error: 'Telegram bot token is empty' }
105
+ }
106
+ const start = Date.now()
107
+ try {
108
+ const res = await fetch(`https://api.telegram.org/bot${adapter.token}/getMe`, {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify({}),
112
+ signal: AbortSignal.timeout(Math.max(1000, Number(timeoutMs) || 10000)),
113
+ })
114
+ const latencyMs = Date.now() - start
115
+ const json = await res.json().catch(() => ({}))
116
+ if (!res.ok || json.ok === false) {
117
+ return {
118
+ ok: false,
119
+ latencyMs,
120
+ error: json.description || `HTTP ${res.status}`,
121
+ }
122
+ }
123
+ const me = json.result || {}
124
+ adapter.botId = Number(me.id) || adapter.botId
125
+ adapter.botUsername = String(me.username || adapter.botUsername)
126
+ return {
127
+ ok: true,
128
+ latencyMs,
129
+ botId: adapter.botId,
130
+ botUsername: adapter.botUsername,
131
+ firstName: me.first_name || '',
132
+ }
133
+ } catch (err) {
134
+ return {
135
+ ok: false,
136
+ latencyMs: Date.now() - start,
137
+ error: err instanceof Error ? err.message : String(err),
138
+ }
139
+ }
140
+ }
141
+