@kanaraa/baileys 3.3.0 → 3.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.
@@ -1,84 +1,122 @@
1
1
  import { prepareWAMessageMedia } from './messages.js';
2
2
  import { extractImageThumb, getHttpStream } from './messages-media.js';
3
+
3
4
  const THUMBNAIL_WIDTH_PX = 192;
4
- /** Fetches an image and generates a thumbnail for it */
5
+
6
+ const htmlDecode = (s) => s
7
+ .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
8
+ .replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(Number(dec)))
9
+ .replace(/"/g, '"')
10
+ .replace(/'/g, "'")
11
+ .replace(/&lt;/g, '<')
12
+ .replace(/&gt;/g, '>')
13
+ .replace(/&amp;/g, '&');
14
+
15
+ const getMetaContent = (html, keys) => {
16
+ const pattern = new RegExp(`<meta[^>]+(?:property|name)=["'](?:${keys.join('|')})["'][^>]*content=["']([^"']*)["']`, 'i');
17
+ const match = html.match(pattern);
18
+ if (match) return htmlDecode(match[1]);
19
+ const reversed = new RegExp(`<meta[^>]+content=["']([^"']*)["'][^>]*(?:property|name)=["'](?:${keys.join('|')})["']`, 'i');
20
+ const alt = html.match(reversed);
21
+ if (alt) return htmlDecode(alt[1]);
22
+ return null;
23
+ };
24
+
25
+ const getTitle = (html) => {
26
+ const meta = getMetaContent(html, ['og:title', 'twitter:title']);
27
+ if (meta) return meta;
28
+ const match = html.match(/<title[^>]*>([^<]*)<\/title>/i);
29
+ return match ? htmlDecode(match[1].trim()) : null;
30
+ };
31
+
32
+ const getFavicon = (html, baseUrl) => {
33
+ const match = html.match(/<link[^>]+href=["']([^"']+)["'][^>]*rel=["'](?:shortcut\s+)?icon["']/i) ||
34
+ html.match(/<link[^>]+rel=["'](?:shortcut\s+)?icon["'][^>]*href=["']([^"']+)["']/i);
35
+ const href = match ? match[1] || match[2] : null;
36
+ try {
37
+ return new URL(href || '/favicon.ico', baseUrl).href;
38
+ } catch {
39
+ return href || `${baseUrl}/favicon.ico`;
40
+ }
41
+ };
42
+
5
43
  const getCompressedJpegThumbnail = async (url, { thumbnailWidth, fetchOpts }) => {
6
44
  const stream = await getHttpStream(url, fetchOpts);
7
45
  const result = await extractImageThumb(stream, thumbnailWidth);
8
46
  return result;
9
47
  };
10
- /**
11
- * Given a piece of text, checks for any URL present, generates link preview for the same and returns it
12
- * Return undefined if the fetch failed or no URL was found
13
- * @param text first matched URL in text
14
- * @returns the URL info required to generate link preview
15
- */
48
+
49
+ const fetchPage = async (url, fetchOpts = {}) => {
50
+ const controller = new AbortController();
51
+ const timer = setTimeout(() => controller.abort(), fetchOpts.timeout || 3000);
52
+ try {
53
+ const response = await fetch(url, {
54
+ method: 'GET',
55
+ redirect: 'follow',
56
+ signal: controller.signal,
57
+ headers: {
58
+ 'user-agent': 'WhatsApp/2.24.6.77',
59
+ accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
60
+ ...(fetchOpts.headers || {})
61
+ }
62
+ });
63
+ if (!response.ok) {
64
+ throw new Error(`HTTP ${response.status}`);
65
+ }
66
+ const html = await response.text();
67
+ return {
68
+ html,
69
+ finalUrl: response.url || url
70
+ };
71
+ } finally {
72
+ clearTimeout(timer);
73
+ }
74
+ };
75
+
16
76
  export const getUrlInfo = async (text, opts = {
17
77
  thumbnailWidth: THUMBNAIL_WIDTH_PX,
18
78
  fetchOpts: { timeout: 3000 }
19
79
  }) => {
20
80
  try {
21
- // retries
22
- let retries = 0;
23
- const maxRetry = 5;
24
- const { getLinkPreview } = await import('link-preview-js');
25
81
  let previewLink = text;
26
- if (!text.startsWith('https://') && !text.startsWith('http://')) {
82
+ if (!previewLink.startsWith('https://') && !previewLink.startsWith('http://')) {
27
83
  previewLink = 'https://' + previewLink;
28
84
  }
29
- const info = await getLinkPreview(previewLink, {
30
- ...opts.fetchOpts,
31
- followRedirects: 'follow',
32
- handleRedirects: (baseURL, forwardedURL) => {
33
- const urlObj = new URL(baseURL);
34
- const forwardedURLObj = new URL(forwardedURL);
35
- if (retries >= maxRetry) {
36
- return false;
37
- }
38
- if (forwardedURLObj.hostname === urlObj.hostname ||
39
- forwardedURLObj.hostname === 'www.' + urlObj.hostname ||
40
- 'www.' + forwardedURLObj.hostname === urlObj.hostname) {
41
- retries += 1;
42
- return true;
43
- }
44
- else {
45
- return false;
46
- }
47
- },
48
- headers: opts.fetchOpts?.headers
49
- });
50
- if (info && 'title' in info && info.title) {
51
- const [image] = info.images;
52
- const urlInfo = {
53
- 'canonical-url': info.url,
54
- 'matched-text': text,
55
- title: info.title,
56
- description: info.description,
57
- originalThumbnailUrl: image
58
- };
59
- if (opts.uploadImage) {
60
- const { imageMessage } = await prepareWAMessageMedia({ image: { url: image } }, {
61
- upload: opts.uploadImage,
62
- mediaTypeOverride: 'thumbnail-link',
63
- options: opts.fetchOpts
64
- });
65
- urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined;
66
- urlInfo.highQualityThumbnail = imageMessage || undefined;
85
+ const { html, finalUrl } = await fetchPage(previewLink, opts.fetchOpts);
86
+ const title = getTitle(html);
87
+ if (!title) {
88
+ return undefined;
89
+ }
90
+ const description = getMetaContent(html, ['og:description', 'twitter:description', 'description']);
91
+ const image = getMetaContent(html, ['og:image', 'twitter:image']);
92
+ const favicon = getFavicon(html, finalUrl);
93
+ const urlInfo = {
94
+ 'canonical-url': finalUrl,
95
+ 'matched-text': text,
96
+ title,
97
+ description,
98
+ originalThumbnailUrl: image || favicon
99
+ };
100
+ if (opts.uploadImage) {
101
+ const { imageMessage } = await prepareWAMessageMedia({ image: { url: urlInfo.originalThumbnailUrl } }, {
102
+ upload: opts.uploadImage,
103
+ mediaTypeOverride: 'thumbnail-link',
104
+ options: opts.fetchOpts
105
+ });
106
+ urlInfo.jpegThumbnail = imageMessage?.jpegThumbnail ? Buffer.from(imageMessage.jpegThumbnail) : undefined;
107
+ urlInfo.highQualityThumbnail = imageMessage || undefined;
108
+ }
109
+ else {
110
+ try {
111
+ urlInfo.jpegThumbnail = (await getCompressedJpegThumbnail(urlInfo.originalThumbnailUrl, opts)).buffer;
67
112
  }
68
- else {
69
- try {
70
- urlInfo.jpegThumbnail = image ? (await getCompressedJpegThumbnail(image, opts)).buffer : undefined;
71
- }
72
- catch (error) {
73
- opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail');
74
- }
113
+ catch (error) {
114
+ opts.logger?.debug({ err: error.stack, url: previewLink }, 'error in generating thumbnail');
75
115
  }
76
- return urlInfo;
77
116
  }
117
+ return urlInfo;
78
118
  }
79
119
  catch (error) {
80
- if (!error.message.includes('receive a valid')) {
81
- throw error;
82
- }
120
+ opts.logger?.debug({ err: error.stack, url: text }, 'error in generating link preview');
83
121
  }
84
122
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kanaraa/baileys",
3
3
  "type": "module",
4
- "version": "3.3.0",
4
+ "version": "3.4.0",
5
5
  "description": "Modded Baileys v7, Rebuilt on top of official @whiskeysockets/baileys 7.0.0-rc13, with the interactive & rich-message content types (buttons, lists, carousel, cards, shop/collection, native flow, AI rich response, sticker packs, admin invite, payments, etc.).",
6
6
  "keywords": [
7
7
  "whatsapp",
@@ -43,10 +43,9 @@
43
43
  "peerDependencies": {
44
44
  "adm-zip": "^0.5.10",
45
45
  "audio-decode": "^2.1.3",
46
- "fluent-ffmpeg": "^2.1.2",
46
+ "fluent-ffmpeg": "^2.1.3",
47
47
  "jimp": "^1.6.1",
48
- "link-preview-js": "^3.0.0",
49
- "sharp": "*"
48
+ "sharp": "^0.35.3"
50
49
  },
51
50
  "peerDependenciesMeta": {
52
51
  "adm-zip": {
@@ -61,9 +60,6 @@
61
60
  "jimp": {
62
61
  "optional": true
63
62
  },
64
- "link-preview-js": {
65
- "optional": true
66
- },
67
63
  "sharp": {
68
64
  "optional": true
69
65
  }