@goodandready/dsh-messenger-gateway 0.3.22 → 0.4.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/LICENSE CHANGED
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
21
+ SOFTWARE.
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  - insert:
2
2
  - id: dsh-messenger-gateway
3
3
  name: '@goodandready/dsh-messenger-gateway'
4
- config: {}
4
+ config: {}
@@ -190,4 +190,4 @@ export class DiscordAdapter {
190
190
  const json = typeof res.json === 'function' ? await res.json().catch(() => ({})) : {}
191
191
  return { ok: true, messageId: json?.id }
192
192
  }
193
- }
193
+ }
@@ -154,4 +154,4 @@ export class SlackAdapter {
154
154
  }
155
155
  return { ok: true }
156
156
  }
157
- }
157
+ }
@@ -0,0 +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
+