@nexustechpro/baileys 2.0.5 → 2.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 +71 -0
- package/WAProto/index.js +22 -18
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/index.js +9 -8
- package/lib/Signal/libsignal.js +551 -342
- package/lib/Socket/chats.js +83 -65
- package/lib/Socket/index.js +2 -3
- package/lib/Socket/messages-recv.js +227 -41
- package/lib/Socket/messages-send.js +97 -117
- package/lib/Socket/newsletter.js +87 -36
- package/lib/Socket/nexus-handler.js +326 -89
- package/lib/Socket/registration.js +50 -33
- package/lib/Socket/socket.js +245 -69
- package/lib/Store/make-in-memory-store.js +29 -20
- package/lib/Types/Newsletter.js +37 -29
- package/lib/Types/State.js +43 -0
- package/lib/Utils/auth-utils.js +2 -2
- package/lib/Utils/chat-utils.js +48 -16
- package/lib/Utils/companion-reg-client-utils.js +34 -0
- package/lib/Utils/decode-wa-message.js +39 -22
- package/lib/Utils/generics.js +5 -7
- package/lib/Utils/index.js +4 -0
- package/lib/Utils/key-store.js +1 -1
- package/lib/Utils/link-preview.js +134 -61
- package/lib/Utils/messages-media.js +496 -381
- package/lib/Utils/messages.js +699 -706
- package/lib/Utils/process-message.js +53 -35
- package/lib/Utils/reporting-utils.js +155 -0
- package/lib/Utils/signal.js +134 -104
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/use-multi-file-auth-state.js +174 -91
- package/lib/WABinary/constants.js +6 -0
- package/lib/WABinary/index.js +1 -0
- package/lib/index.js +2 -3
- package/package.json +13 -9
|
@@ -1,98 +1,171 @@
|
|
|
1
1
|
import { getLinkPreview } from 'link-preview-js'
|
|
2
|
+
import mql from '@microlink/mql'
|
|
3
|
+
import { lookup } from 'dns'
|
|
4
|
+
import { promisify } from 'util'
|
|
5
|
+
import { LRUCache } from 'lru-cache'
|
|
2
6
|
import { prepareWAMessageMedia } from './messages.js'
|
|
3
7
|
import { extractImageThumb, getHttpStream } from './messages-media.js'
|
|
4
8
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
// Concurrency pool — max simultaneous link preview fetches
|
|
9
|
+
const dnsLookup = promisify(lookup)
|
|
10
|
+
const THUMBNAIL_WIDTH = 192
|
|
11
|
+
const TIMEOUT = 4_000
|
|
9
12
|
const MAX_CONCURRENT = 20
|
|
13
|
+
const MAX_INFLIGHT = 1000
|
|
14
|
+
|
|
15
|
+
const _previewCache = new LRUCache({ max: 500, ttl: 1000 * 60 * 10 })
|
|
16
|
+
const _negCache = new LRUCache({ max: 500, ttl: 1000 * 60 * 2 })
|
|
17
|
+
const _thumbCache = new LRUCache({ max: 200, ttl: 1000 * 60 * 30 })
|
|
18
|
+
const _inflight = new Map()
|
|
19
|
+
|
|
10
20
|
let _active = 0
|
|
11
21
|
const _queue = []
|
|
22
|
+
|
|
12
23
|
const _drain = () => {
|
|
13
|
-
if (_queue.length
|
|
24
|
+
if (!_queue.length || _active >= MAX_CONCURRENT) return
|
|
14
25
|
_active++
|
|
15
26
|
const { fn, resolve, reject } = _queue.shift()
|
|
16
27
|
fn().then(resolve).catch(reject).finally(() => { _active--; _drain() })
|
|
17
28
|
}
|
|
18
|
-
const _enqueue = fn => new Promise((resolve, reject) => { _queue.push({ fn, resolve, reject }); _drain() })
|
|
19
29
|
|
|
20
|
-
|
|
30
|
+
const _enqueue = fn => new Promise((resolve, reject) => {
|
|
31
|
+
_queue.push({ fn, resolve, reject }); _drain()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const _resolveDNS = async url => {
|
|
35
|
+
try { return (await dnsLookup(new URL(url).hostname)).address }
|
|
36
|
+
catch { return new URL(url).hostname }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const _normalize = text => {
|
|
40
|
+
const t = text.trim()
|
|
41
|
+
return /^https?:\/\//i.test(t) ? t : `https://${t}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const _previewType = (mediaType, image) => {
|
|
45
|
+
if (!mediaType) return image ? 5 : 0
|
|
46
|
+
const mt = mediaType.toLowerCase()
|
|
47
|
+
if (mt === 'video' || mt.startsWith('video.')) return 1
|
|
48
|
+
return mt === 'image' || image ? 5 : 0
|
|
49
|
+
}
|
|
50
|
+
|
|
21
51
|
const _compressedThumb = async (url, opts) => {
|
|
22
52
|
const stream = await getHttpStream(url, opts.fetchOpts)
|
|
23
|
-
|
|
24
|
-
return result.buffer
|
|
53
|
+
return (await extractImageThumb(stream, opts.thumbnailWidth ?? THUMBNAIL_WIDTH)).buffer
|
|
25
54
|
}
|
|
26
55
|
|
|
27
|
-
/** Resolves jpegThumbnail + highQualityThumbnail from an image URL, never throws */
|
|
28
56
|
const _resolveThumbnail = async (image, opts) => {
|
|
29
57
|
if (!image) return {}
|
|
58
|
+
const key = `thumb:${image}`
|
|
59
|
+
const hit = _thumbCache.get(key)
|
|
60
|
+
if (hit) return hit
|
|
61
|
+
|
|
62
|
+
let thumbs = {}
|
|
30
63
|
if (opts.uploadImage) {
|
|
31
64
|
try {
|
|
32
65
|
const { imageMessage } = await prepareWAMessageMedia(
|
|
33
66
|
{ image: { url: image } },
|
|
34
67
|
{ upload: opts.uploadImage, mediaTypeOverride: 'thumbnail-link', options: opts.fetchOpts }
|
|
35
68
|
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
} catch
|
|
41
|
-
|
|
69
|
+
const jpeg = imageMessage?.jpegThumbnail
|
|
70
|
+
? Buffer.from(imageMessage.jpegThumbnail)
|
|
71
|
+
: await _compressedThumb(image, opts).catch(() => undefined)
|
|
72
|
+
thumbs = { jpegThumbnail: jpeg, highQualityThumbnail: imageMessage ?? undefined }
|
|
73
|
+
} catch {
|
|
74
|
+
try { thumbs = { jpegThumbnail: await _compressedThumb(image, opts) } } catch { }
|
|
42
75
|
}
|
|
76
|
+
} else {
|
|
77
|
+
try { thumbs = { jpegThumbnail: await _compressedThumb(image, opts) } } catch { }
|
|
43
78
|
}
|
|
44
|
-
|
|
79
|
+
|
|
80
|
+
if (thumbs.jpegThumbnail) _thumbCache.set(key, thumbs)
|
|
81
|
+
return thumbs
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const _tryLinkPreview = async (url, opts) => {
|
|
45
85
|
try {
|
|
46
|
-
|
|
86
|
+
const info = await getLinkPreview(url, {
|
|
87
|
+
timeout: opts.fetchOpts?.timeout ?? TIMEOUT,
|
|
88
|
+
followRedirects: 'follow',
|
|
89
|
+
resolveDNSHost: _resolveDNS,
|
|
90
|
+
})
|
|
91
|
+
if (info?.title) return info
|
|
47
92
|
} catch (err) {
|
|
48
|
-
opts.logger?.
|
|
49
|
-
return {}
|
|
93
|
+
opts.logger?.warn({ err: err?.message || err, url }, 'getLinkPreview failed')
|
|
50
94
|
}
|
|
95
|
+
return undefined
|
|
51
96
|
}
|
|
52
97
|
|
|
53
|
-
|
|
54
|
-
* Extracts link preview info from a text string or URL.
|
|
55
|
-
* Queued for concurrency control — never throws on preview failure.
|
|
56
|
-
* @param {string} text - Raw text or URL to preview
|
|
57
|
-
* @param {object} opts - Options: thumbnailWidth, fetchOpts, uploadImage, logger
|
|
58
|
-
* @returns {Promise<object|undefined>} urlInfo or undefined if no valid URL/title found
|
|
59
|
-
*/
|
|
60
|
-
export const getUrlInfo = (text, opts = {}) => _enqueue(async () => {
|
|
61
|
-
const fetchOpts = opts.fetchOpts ?? { timeout: PREVIEW_TIMEOUT }
|
|
62
|
-
const thumbnailWidth = opts.thumbnailWidth ?? THUMBNAIL_WIDTH_PX
|
|
63
|
-
const resolvedOpts = { ...opts, fetchOpts, thumbnailWidth }
|
|
98
|
+
const _tryMicrolink = async (url, opts) => {
|
|
64
99
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const info = await getLinkPreview(previewLink, {
|
|
68
|
-
...fetchOpts,
|
|
69
|
-
followRedirects: 'manual',
|
|
70
|
-
handleRedirects: (baseURL, forwardedURL) => {
|
|
71
|
-
if (retries >= MAX_REDIRECTS) return false
|
|
72
|
-
const base = new URL(baseURL)
|
|
73
|
-
const fwd = new URL(forwardedURL)
|
|
74
|
-
const sameHost = fwd.hostname === base.hostname || fwd.hostname === 'www.' + base.hostname || 'www.' + fwd.hostname === base.hostname
|
|
75
|
-
if (sameHost) { retries++; return true }
|
|
76
|
-
return false
|
|
77
|
-
},
|
|
78
|
-
headers: fetchOpts.headers
|
|
79
|
-
})
|
|
80
|
-
if (!info || !('title' in info) || !info.title) return undefined
|
|
81
|
-
const [image] = info.images ?? []
|
|
82
|
-
const thumbs = await _resolveThumbnail(image, resolvedOpts)
|
|
100
|
+
const { data } = await mql(url)
|
|
101
|
+
if (!data?.title) return undefined
|
|
83
102
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
...thumbs
|
|
103
|
+
url: data.url ?? url,
|
|
104
|
+
title: data.title,
|
|
105
|
+
description: data.description ?? '',
|
|
106
|
+
images: [data.image?.url].filter(Boolean),
|
|
107
|
+
mediaType: 'website',
|
|
90
108
|
}
|
|
91
109
|
} catch (err) {
|
|
92
|
-
|
|
93
|
-
if (!err.message?.includes('receive a valid') && err.code !== 'ERR_MODULE_NOT_FOUND' && err.code !== 'MODULE_NOT_FOUND') {
|
|
94
|
-
throw err
|
|
95
|
-
}
|
|
110
|
+
opts.logger?.warn({ err: err?.message || err, url }, 'microlink failed')
|
|
96
111
|
}
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const _buildResult = async (info, text, opts) => {
|
|
116
|
+
const [image] = info.images ?? []
|
|
117
|
+
return {
|
|
118
|
+
'canonical-url': info.url,
|
|
119
|
+
'matched-text': text,
|
|
120
|
+
title: info.title,
|
|
121
|
+
description: info.description,
|
|
122
|
+
originalThumbnailUrl: image,
|
|
123
|
+
previewType: _previewType(info.mediaType, image),
|
|
124
|
+
...await _resolveThumbnail(image, opts),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const getUrlInfo = (text, opts = {}) => {
|
|
129
|
+
const url = _normalize(text)
|
|
130
|
+
|
|
131
|
+
if (_negCache.has(url)) return Promise.resolve(undefined)
|
|
132
|
+
if (_previewCache.has(url)) return Promise.resolve(_previewCache.get(url))
|
|
133
|
+
if (_inflight.has(url)) return _inflight.get(url)
|
|
134
|
+
if (_inflight.size >= MAX_INFLIGHT) return Promise.resolve(undefined)
|
|
135
|
+
|
|
136
|
+
const o = {
|
|
137
|
+
fetchOpts: { timeout: TIMEOUT, ...opts.fetchOpts },
|
|
138
|
+
thumbnailWidth: opts.thumbnailWidth ?? THUMBNAIL_WIDTH,
|
|
139
|
+
uploadImage: opts.uploadImage,
|
|
140
|
+
logger: opts.logger,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const promise = _enqueue(async () => {
|
|
144
|
+
try {
|
|
145
|
+
// Primary: link-preview-js
|
|
146
|
+
let info = await _tryLinkPreview(url, o)
|
|
147
|
+
|
|
148
|
+
// Fallback: microlink (if no result or no image)
|
|
149
|
+
if (!info?.title || !(info.images ?? []).length) {
|
|
150
|
+
o.logger?.debug({ url }, 'falling back to microlink')
|
|
151
|
+
info = await _tryMicrolink(url, o) ?? info
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!info?.title) {
|
|
155
|
+
_negCache.set(url, true)
|
|
156
|
+
return undefined
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const result = await _buildResult(info, text, o)
|
|
160
|
+
_previewCache.set(url, result)
|
|
161
|
+
return result
|
|
162
|
+
} catch (err) {
|
|
163
|
+
_negCache.set(url, true)
|
|
164
|
+
if (!err.message?.includes('receive a valid')) throw err
|
|
165
|
+
return undefined
|
|
166
|
+
}
|
|
167
|
+
}).finally(() => _inflight.delete(url))
|
|
168
|
+
|
|
169
|
+
_inflight.set(url, promise)
|
|
170
|
+
return promise
|
|
171
|
+
}
|