@soyaxell09/zenbot-scraper 1.1.0 → 1.1.2

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AxelDev09
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,8 +1,24 @@
1
- # @soyaxell09/zenbot-scraper
1
+ <div align="center">
2
2
 
3
- Módulo npm de scrapers y herramientas para bots de WhatsApp. Incluye descargadores, buscadores, utilidades y contenido NSFW.
3
+ <img src="https://readme-typing-svg.demolab.com?font=Fira+Code&weight=600&size=24&pause=1000&color=9B59B6&center=true&vCenter=true&width=600&lines=@soyaxell09%2Fzenbot-scraper+%F0%9F%A4%96;Scrapers+%7C+Downloaders+%7C+Tools;Built+for+WhatsApp+Bots" alt="Typing SVG" />
4
4
 
5
- ## Instalación
5
+ <br/>
6
+
7
+ [![NPM Version](https://img.shields.io/npm/v/@soyaxell09/zenbot-scraper?color=9b59b6&style=for-the-badge&logo=npm)](https://www.npmjs.com/package/@soyaxell09/zenbot-scraper)
8
+ [![NPM Downloads](https://img.shields.io/npm/dm/@soyaxell09/zenbot-scraper?color=9b59b6&style=for-the-badge)](https://www.npmjs.com/package/@soyaxell09/zenbot-scraper)
9
+ [![License](https://img.shields.io/npm/l/@soyaxell09/zenbot-scraper?color=9b59b6&style=for-the-badge)](LICENSE)
10
+ [![Node](https://img.shields.io/node/v/@soyaxell09/zenbot-scraper?color=9b59b6&style=for-the-badge&logo=nodedotjs)](https://nodejs.org)
11
+
12
+ <br/>
13
+
14
+ > Módulo npm de scrapers y herramientas para bots de WhatsApp.
15
+ > Incluye descargadores, buscadores, utilidades y contenido NSFW.
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## 📦 Instalación
6
22
 
7
23
  ```bash
8
24
  npm install @soyaxell09/zenbot-scraper
@@ -14,17 +30,18 @@ npm install @soyaxell09/zenbot-scraper
14
30
 
15
31
  ```
16
32
  src/
17
- ├── scrapers/ → Descargadores de media
18
- ├── search/ → Buscadores
19
- ├── tools/ → Utilidades
20
- └── nsfw/ → Contenido adulto
33
+ ├── scrapers/ → YouTube, TikTok, Instagram, Facebook, Twitter...
34
+ ├── search/ → Pinterest, GIFs, Spotify, Google...
35
+ ├── tools/ → Traductor, Clima, QR, Lyrics, ATTP...
36
+ └── nsfw/ → XNXX, PornHub, XVideos, XHamster, Rule34
21
37
  ```
22
38
 
23
39
  ---
24
40
 
25
41
  ## 🎬 Scrapers
26
42
 
27
- ### YouTube
43
+ <details>
44
+ <summary><b>▶ YouTube</b></summary>
28
45
 
29
46
  ```js
30
47
  import { ytSearch, ytDownload, ytInfo, getFileSize } from '@soyaxell09/zenbot-scraper'
@@ -33,7 +50,7 @@ const results = await ytSearch('bad bunny', 5)
33
50
  // → [{ id, title, url, thumbnail, duration, views, channel, published }]
34
51
 
35
52
  const info = await ytInfo('https://youtu.be/dQw4w9WgXcQ')
36
- // → { id, title, uploader, views, thumb, duration, qualities: [{ id, type, quality, size, sizeB, duration }] }
53
+ // → { id, title, uploader, views, thumb, duration, qualities: [...] }
37
54
 
38
55
  const video = await ytDownload('https://youtu.be/dQw4w9WgXcQ', 'video', '360p')
39
56
  const audio = await ytDownload('https://youtu.be/dQw4w9WgXcQ', 'mp3', '128k')
@@ -43,28 +60,31 @@ const size = await getFileSize('https://example.com/file.mp4')
43
60
  // → '14.5 MB'
44
61
  ```
45
62
 
46
- ### TikTok
63
+ </details>
64
+
65
+ <details>
66
+ <summary><b>▶ TikTok</b></summary>
47
67
 
48
68
  ```js
49
69
  import { tiktokDownload, tiktokInfo } from '@soyaxell09/zenbot-scraper'
50
70
 
51
71
  const result = await tiktokDownload('https://www.tiktok.com/@user/video/123')
52
- // → { id, title, author, thumbnail, duration, nowatermark, watermark, audio, images, music, plays, likes, comments, shares }
72
+ // → { id, title, author, thumbnail, duration, nowatermark, watermark,
73
+ // audio, images, music, plays, likes, comments, shares }
53
74
  ```
54
75
 
55
- ### Instagram
76
+ </details>
77
+
78
+ <details>
79
+ <summary><b>▶ Instagram</b></summary>
56
80
 
57
81
  ```js
58
82
  import { igDownload, igReelDownload, igStalk, igStories } from '@soyaxell09/zenbot-scraper'
59
83
 
60
84
  const post = await igDownload('https://www.instagram.com/p/ABC123/')
61
- // → { type, items: [{ type, url }] }
62
-
63
85
  const reel = await igReelDownload('https://www.instagram.com/reel/ABC123/')
64
- // → { type, items: [{ type, url }] }
65
-
66
86
  const story = await igDownload('https://www.instagram.com/stories/user/123456789/')
67
- // → { type: 'story', items: [{ type, url }] }
87
+ // → { type, items: [{ type, url }] }
68
88
 
69
89
  const perfil = await igStalk('siamusic')
70
90
  // → { username, fullName, bio, followers, following, posts, isPrivate, isVerified, avatar, url }
@@ -73,7 +93,10 @@ const stories = await igStories('siamusic')
73
93
  // → { username, items: [{ type, url }] }
74
94
  ```
75
95
 
76
- ### Facebook
96
+ </details>
97
+
98
+ <details>
99
+ <summary><b>▶ Facebook</b></summary>
77
100
 
78
101
  ```js
79
102
  import { fbDownload } from '@soyaxell09/zenbot-scraper'
@@ -82,19 +105,25 @@ const result = await fbDownload('https://www.facebook.com/watch?v=123')
82
105
  // → { videoId, title, hd, sd, thumb, source }
83
106
  ```
84
107
 
85
- ### Twitter / X
108
+ </details>
109
+
110
+ <details>
111
+ <summary><b>▶ Twitter / X</b></summary>
86
112
 
87
113
  ```js
88
114
  import { tweetInfo, tweetDownload } from '@soyaxell09/zenbot-scraper'
89
115
 
90
116
  const info = await tweetInfo('https://x.com/user/status/123')
91
- // → { id, text, lang, createdAt, likes, replies, author: { name, username, avatar }, hashtags, mentions, medias }
117
+ // → { id, text, lang, createdAt, likes, replies, author, hashtags, mentions, medias }
92
118
 
93
119
  const media = await tweetDownload('https://x.com/user/status/123')
94
- // → { id, text, author, videos: [{ type, url, thumbnail, variants }], photos: [{ type, url, width, height }] }
120
+ // → { id, text, author, videos: [...], photos: [...] }
95
121
  ```
96
122
 
97
- ### Threads
123
+ </details>
124
+
125
+ <details>
126
+ <summary><b>▶ Threads</b></summary>
98
127
 
99
128
  ```js
100
129
  import { threadsDownload } from '@soyaxell09/zenbot-scraper'
@@ -103,7 +132,10 @@ const result = await threadsDownload('https://www.threads.net/t/xxx')
103
132
  // → { title, medias: [{ type, url }] }
104
133
  ```
105
134
 
106
- ### Spotify (SpotiDown)
135
+ </details>
136
+
137
+ <details>
138
+ <summary><b>▶ Spotify (SpotiDown)</b></summary>
107
139
 
108
140
  ```js
109
141
  import { spotidownTrack } from '@soyaxell09/zenbot-scraper'
@@ -112,16 +144,22 @@ const result = await spotidownTrack('https://open.spotify.com/track/xxx')
112
144
  // → { name, artist, album, year, duration, cover, mp3, coverHd }
113
145
  ```
114
146
 
115
- ### MediaFire
147
+ </details>
148
+
149
+ <details>
150
+ <summary><b>▶ MediaFire</b></summary>
116
151
 
117
152
  ```js
118
153
  import { mediafireInfo } from '@soyaxell09/zenbot-scraper'
119
154
 
120
- const result = await mediafireInfo('https://www.mediafire.com/file/abc123/archivo.apk/file')
155
+ const result = await mediafireInfo('https://www.mediafire.com/file/abc123/file')
121
156
  // → { key, name, size, download, url }
122
157
  ```
123
158
 
124
- ### GitHub
159
+ </details>
160
+
161
+ <details>
162
+ <summary><b>▶ GitHub</b></summary>
125
163
 
126
164
  ```js
127
165
  import { githubInfo, githubRelease, githubContents, githubSearch } from '@soyaxell09/zenbot-scraper'
@@ -132,7 +170,10 @@ const contents = await githubContents('facebook/react', 'src')
132
170
  const repos = await githubSearch('whatsapp bot', 'repositories', 5)
133
171
  ```
134
172
 
135
- ### APK (APKPure)
173
+ </details>
174
+
175
+ <details>
176
+ <summary><b>▶ APK (APKPure)</b></summary>
136
177
 
137
178
  ```js
138
179
  import { apkSearch, apkInfo } from '@soyaxell09/zenbot-scraper'
@@ -144,7 +185,10 @@ const info = await apkInfo('com.whatsapp')
144
185
  // → { name, developer, pkg, date, icon, download, dlLinks, url }
145
186
  ```
146
187
 
147
- ### Google Drive
188
+ </details>
189
+
190
+ <details>
191
+ <summary><b>▶ Google Drive</b></summary>
148
192
 
149
193
  ```js
150
194
  import { gdriveInfo, gdriveDownload } from '@soyaxell09/zenbot-scraper'
@@ -156,29 +200,33 @@ const file = await gdriveDownload('https://drive.google.com/file/d/1ABC.../view'
156
200
  // → { fileId, buffer, contentType, size, url }
157
201
  ```
158
202
 
203
+ </details>
204
+
159
205
  ---
160
206
 
161
207
  ## 🔍 Search
162
208
 
163
- ### Google (DuckDuckGo)
209
+ <details>
210
+ <summary><b>▶ Pinterest</b></summary>
164
211
 
165
212
  ```js
166
- import { googleSearch } from '@soyaxell09/zenbot-scraper'
167
-
168
- const results = await googleSearch('node.js tutorial', 5)
169
- // → [{ title, url, snippet }]
170
- ```
213
+ import { pinsearch, pinimg, pinvid } from '@soyaxell09/zenbot-scraper'
171
214
 
172
- ### Spotify / Deezer
215
+ const imgs = await pinsearch('anime wallpaper', 50)
216
+ // → [{ index, image, url, pinId }] — paginación infinita
173
217
 
174
- ```js
175
- import { spotify } from '@soyaxell09/zenbot-scraper'
218
+ const pin = await pinimg('https://www.pinterest.com/pin/123/')
219
+ // { id, title, description, altText, image, images, width, height,
220
+ // dominantColor, saves, repins, createdAt, tags, domain, link,
221
+ // board, creator, pinner, url }
176
222
 
177
- const tracks = await spotify('bad bunny', 'tracks', 5)
178
- // → [{ title, artist, album, duration, thumbnail, url, preview }]
223
+ const vids = await pinvid('anime', 5)
179
224
  ```
180
225
 
181
- ### Tenor (GIFs)
226
+ </details>
227
+
228
+ <details>
229
+ <summary><b>▶ GIFs (Tenor)</b></summary>
182
230
 
183
231
  ```js
184
232
  import { giphy, gifNext, giphyBuffer } from '@soyaxell09/zenbot-scraper'
@@ -187,265 +235,131 @@ const gifs = await giphy('funny cat', 5)
187
235
  // → [{ id, title, url, gif, preview, mp4, width, height }]
188
236
 
189
237
  const next = await gifNext('funny cat')
190
- // → { id, title, url, gif, preview, mp4, width, height }
191
- // Mantiene una cola paginada — cada llamada devuelve el siguiente GIF sin repetir
238
+ // → cola paginada cada llamada devuelve el siguiente sin repetir
192
239
 
193
- const buf = await giphyBuffer('funny cat')
240
+ const buf = await giphyBuffer('funny cat')
194
241
  // → { buffer, mimetype, title, url }
195
242
  ```
196
243
 
197
- ### Pinterest
198
-
199
- ```js
200
- import { pinsearch, pinimg, pinvid } from '@soyaxell09/zenbot-scraper'
201
-
202
- const imgs = await pinsearch('anime wallpaper', 50)
203
- // → [{ index, image, url, pinId }]
204
- // Paginación infinita — carga más resultados automáticamente si limit > resultados en caché
205
-
206
- const pin = await pinimg('https://www.pinterest.com/pin/123/')
207
- // → { id, title, description, altText, image, images, width, height, dominantColor, saves, repins, createdAt, tags, domain, link, board, creator, pinner, url }
208
-
209
- const vids = await pinvid('anime', 5)
210
- ```
244
+ </details>
211
245
 
212
- ### Imágenes de Anime
246
+ <details>
247
+ <summary><b>▶ Spotify Search</b></summary>
213
248
 
214
249
  ```js
215
- import { animeImage } from '@soyaxell09/zenbot-scraper'
250
+ import { spotify } from '@soyaxell09/zenbot-scraper'
216
251
 
217
- const results = await animeImage('naruto', 5)
218
- // → [{ image, url, source }]
252
+ const tracks = await spotify('bad bunny', 'tracks', 5)
253
+ // → [{ title, artist, album, duration, thumbnail, url, preview }]
219
254
  ```
220
255
 
221
- ### Wallpapers
256
+ </details>
222
257
 
223
- ```js
224
- import { wallpaperSearch } from '@soyaxell09/zenbot-scraper'
225
-
226
- const results = await wallpaperSearch('nature', 10)
227
- // → [{ title, image, url }]
228
- ```
229
-
230
- ### Stickers
258
+ <details>
259
+ <summary><b>▶ Google, Anime, Wallpapers, Stickers</b></summary>
231
260
 
232
261
  ```js
233
- import { stickerSearch } from '@soyaxell09/zenbot-scraper'
262
+ import { googleSearch, animeImage, wallpaperSearch, stickerSearch } from '@soyaxell09/zenbot-scraper'
234
263
 
235
- const result = await stickerSearch('anime', 10)
236
- // { status, nombre, creador, total, fotos, url }
264
+ const web = await googleSearch('node.js tutorial', 5)
265
+ const anime = await animeImage('naruto', 5)
266
+ const walls = await wallpaperSearch('nature', 10)
267
+ const stickers = await stickerSearch('anime', 10)
237
268
  ```
238
269
 
270
+ </details>
271
+
239
272
  ---
240
273
 
241
274
  ## 🛠️ Tools
242
275
 
243
- ### TikTok Stalk
276
+ <details>
277
+ <summary><b>▶ Traductor, Clima, QR, Lyrics, Noticias, Screenshot, Upload, URLs</b></summary>
244
278
 
245
279
  ```js
246
- import { tiktokStalk } from '@soyaxell09/zenbot-scraper'
247
-
248
- const profile = await tiktokStalk('charlidamelio')
249
- // → { id, username, nickname, bio, avatar, verified, private, bioLink, stats, url }
250
- ```
280
+ import { translate, weather, qrGenerate, qrRead,
281
+ lyricsSearch, lyricsGet, news, screenshot,
282
+ upload, shortenUrl, expandUrl, tiktokStalk } from '@soyaxell09/zenbot-scraper'
251
283
 
252
- ### Letras de canciones
284
+ await translate('Hello', 'es')
285
+ // → { original, translated, from, to, fromName, toName }
253
286
 
254
- ```js
255
- import { lyricsSearch, lyricsGet } from '@soyaxell09/zenbot-scraper'
287
+ await weather('Buenos Aires')
288
+ // { location, temp, feelsLike, humidity, wind, description, forecast }
256
289
 
257
- const results = await lyricsSearch('bad bunny tití me preguntó', 3)
258
- const song = await lyricsGet('Bad Bunny', 'Tití Me Preguntó')
259
- // → { id, title, artist, album, duration, lyrics, lrc }
260
- ```
261
-
262
- ### Traductor
263
-
264
- ```js
265
- import { translate, getLangs } from '@soyaxell09/zenbot-scraper'
266
-
267
- const result = await translate('Hello, how are you?', 'es')
268
- // → { original, translated, from, to, fromName, toName, source }
269
-
270
- const langs = getLangs()
271
- // → [{ code, name }]
272
- ```
273
-
274
- ### Clima
275
-
276
- ```js
277
- import { weather } from '@soyaxell09/zenbot-scraper'
278
-
279
- const w = await weather('Buenos Aires')
280
- // → { location, temp, feelsLike, humidity, wind, windDir, visibility, pressure, uvIndex, description, forecast }
281
- ```
282
-
283
- ### QR
284
-
285
- ```js
286
- import { qrGenerate, qrRead } from '@soyaxell09/zenbot-scraper'
287
-
288
- const qr = await qrGenerate('https://github.com/axeldev09', 300)
290
+ await qrGenerate('https://github.com/axeldev09', 300)
289
291
  // → { url, buffer, size, text }
290
292
 
291
- const result = await qrRead('https://example.com/qr.png')
292
- // → { text, type }
293
- ```
294
-
295
- ### Acortador de URLs
293
+ await lyricsGet('Bad Bunny', 'Tití Me Preguntó')
294
+ // → { id, title, artist, album, duration, lyrics }
296
295
 
297
- ```js
298
- import { shortenUrl, expandUrl } from '@soyaxell09/zenbot-scraper'
296
+ await news('es', 5)
297
+ // { category, items: [{ title, description, url, image, published }] }
299
298
 
300
- const short = await shortenUrl('https://github.com/axeldev09/zenbot-scraper')
301
- // → { short, source }
299
+ await screenshot('https://github.com', 1280, 720)
300
+ // → { url, buffer, width, height }
302
301
 
303
- const expanded = await expandUrl('https://is.gd/xxxxx')
304
- // → { original, expanded }
302
+ await upload(buffer, 'video.mp4')
303
+ // → { url, filename }
305
304
  ```
306
305
 
307
- ### Noticias
308
-
309
- ```js
310
- import { news, newsCategories } from '@soyaxell09/zenbot-scraper'
306
+ </details>
311
307
 
312
- const n = await news('es', 5)
313
- // { category, source, items: [{ title, description, url, image, published, source }] }
314
-
315
- const cats = newsCategories()
316
- // → ['es', 'en', 'pt', 'tech', 'sports', 'science', 'world']
317
- ```
318
-
319
- ### Screenshot
308
+ <details>
309
+ <summary><b>▶ ATTP (Texto animado)</b></summary>
320
310
 
321
311
  ```js
322
- import { screenshot } from '@soyaxell09/zenbot-scraper'
312
+ import { attp } from '@soyaxell09/zenbot-scraper'
323
313
 
324
- const result = await screenshot('https://github.com', 1280, 720)
325
- // → { url, width, height, size, buffer }
314
+ const gif = await attp('HoLa MuNdO', 5)
315
+ // → Buffer (GIF animado con fondo transparente)
326
316
  ```
327
317
 
328
- ### Upload (Catbox.moe)
329
-
330
- ```js
331
- import { upload } from '@soyaxell09/zenbot-scraper'
318
+ Genera un GIF animado con el texto dado. Fondo transparente, outline negro y colores arcoíris que ciclan frame a frame. El segundo parámetro `scale` es opcional (por defecto `5`).
332
319
 
333
- const r1 = await upload('/sdcard/foto.jpg')
334
- const r2 = await upload('https://example.com/image.jpg')
335
- const r3 = await upload(buffer, 'video.mp4')
336
- // → { url, filename }
337
- ```
320
+ </details>
338
321
 
339
322
  ---
340
323
 
341
324
  ## 🔞 NSFW
342
325
 
343
- > ⚠️ Solo para bots con verificación de edad. Usá responsablemente.
326
+ > ⚠️ Solo para bots con verificación de edad.
344
327
 
345
- ### XNXX
328
+ <details>
329
+ <summary><b>▶ XNXX · PornHub · XVideos · XHamster · Rule34</b></summary>
346
330
 
347
331
  ```js
348
- import { xnxxSearch, xnxxDownload } from '@soyaxell09/zenbot-scraper'
332
+ import { xnxxSearch, xnxxDownload,
333
+ phSearch, phDownload, phDownloadBuffer,
334
+ xvideosSearch, xvideosDownload,
335
+ xhamsterSearch, xhamsterDownload, xhamsterDownloadBuffer,
336
+ rule34Random } from '@soyaxell09/zenbot-scraper'
349
337
 
350
- const results = await xnxxSearch('query', 10)
351
- // → [{ title, url, thumb, preview, duration, views, quality, uploader }]
352
-
353
- const result = await xnxxDownload('https://www.xnxx.com/video-xxx/...')
354
- // → { title, thumb, uploader, duration, views, uploadDate, download: { low, high, hls } }
355
- ```
356
-
357
- ### PornHub
358
-
359
- ```js
360
- import { phSearch, phDownload, phDownloadBuffer } from '@soyaxell09/zenbot-scraper'
361
-
362
- const results = await phSearch('query', 10)
363
- // → [{ title, url, thumb, preview, duration, vkey }]
364
-
365
- const result = await phDownload('https://www.pornhub.com/view_video.php?viewkey=xxx')
366
- // → { title, thumb, duration, uploadDate, hls: { '1080p', '720p', '480p', '240p' } }
367
-
368
- const video = await phDownloadBuffer('https://www.pornhub.com/view_video.php?viewkey=xxx', '720')
369
- // → { title, thumb, duration, uploadDate, buffer, quality }
370
- ```
338
+ await xnxxSearch('query', 10)
339
+ await phSearch('query', 10)
340
+ await xvideosSearch('query', 10)
341
+ await xhamsterSearch('query', 10)
371
342
 
372
- ### XVideos
373
-
374
- ```js
375
- import { xvideosSearch, xvideosDownload } from '@soyaxell09/zenbot-scraper'
376
-
377
- const results = await xvideosSearch('query', 10)
378
- // → [{ title, url, thumb, preview, duration, views, quality }]
379
-
380
- const result = await xvideosDownload('https://www.xvideos.com/video.xxx/...')
381
- // → { title, thumb, duration, views, uploadDate, download: { low, high, hls } }
382
- ```
383
-
384
- ### XHamster
385
-
386
- ```js
387
- import { xhamsterSearch, xhamsterDownload, xhamsterDownloadBuffer } from '@soyaxell09/zenbot-scraper'
388
-
389
- const results = await xhamsterSearch('query', 10)
390
- // → [{ title, url, thumb, preview, duration, views }]
391
-
392
- const result = await xhamsterDownload('https://xhamster.com/videos/...')
393
- // → { title, thumb, duration, views, download: { '144p', '240p', '480p', '720p', '1080p' } }
394
-
395
- const video = await xhamsterDownloadBuffer('https://xhamster.com/videos/...', '480p')
396
- // → { title, thumb, duration, views, buffer, quality }
397
- ```
398
-
399
- ### Rule34 (paheal)
400
-
401
- ```js
402
- import { rule34Random } from '@soyaxell09/zenbot-scraper'
403
-
404
- const random = await rule34Random('cat_girl')
405
- const any = await rule34Random()
343
+ await rule34Random('cat_girl')
406
344
  // → { id, url, full, preview, tags, score, width, height }
407
345
  ```
408
346
 
409
- ---
410
-
411
- ## 📦 Importación por módulo
412
-
413
- ```js
414
- import { ytDownload, tiktokDownload, igDownload, translate, weather } from '@soyaxell09/zenbot-scraper'
415
- import { ytDownload, fbDownload, igDownload } from '@soyaxell09/zenbot-scraper/scrapers'
416
- import { googleSearch, spotify, gifNext, pinsearch } from '@soyaxell09/zenbot-scraper/search'
417
- import { translate, weather, news, screenshot } from '@soyaxell09/zenbot-scraper/tools'
418
- import { xnxxSearch, phSearch, rule34Random, xhamsterSearch } from '@soyaxell09/zenbot-scraper/nsfw'
419
- ```
347
+ </details>
420
348
 
421
349
  ---
422
350
 
423
351
  ## ⚙️ Requisitos
424
352
 
425
- - Node.js >= 18.0.0
426
- - Dependencias: `axios`, `cheerio`, `yt-search`, `form-data`
427
- - Para `phDownloadBuffer` y `xhamsterDownloadBuffer`: requiere `ffmpeg` instalado en el sistema
353
+ - Node.js `>= 18.0.0`
354
+ - `axios` · `cheerio` · `yt-search` · `form-data` · `gifenc`
355
+ - `ffmpeg` en el sistema para `phDownloadBuffer` y `xhamsterDownloadBuffer`
428
356
 
429
357
  ---
430
358
 
431
- ## 👤 Autor
359
+ <div align="center">
432
360
 
433
- **AxelDev09** [GitHub](https://github.com/axeldev09)
434
-
435
- ---
436
-
437
- ## ⭐ Apoyá el proyecto
438
-
439
- Si este módulo te fue útil, dejá una ⭐ en el repositorio.
440
-
441
- **Dejá los créditos** si usás este módulo en tu bot — es lo único que se pide 🙏
442
-
443
- 📸 Seguime en Instagram: [@axeldev09](https://instagram.com/axeldev09)
444
-
445
- 📢 Canal oficial de ZenBot en WhatsApp: [Unirse al canal](https://whatsapp.com/channel/0029Vb6OR9O2v1IvoXO5oT2c)
446
-
447
- ---
361
+ **Hecho por [AxelDev09](https://github.com/AxelDev09)** · [npm](https://www.npmjs.com/package/@soyaxell09/zenbot-scraper) · [Instagram](https://instagram.com/axeldev09)
448
362
 
449
- ## 📄 Licencia
363
+ **Dejá los créditos si usás este módulo 🙏**
450
364
 
451
- MIT
365
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soyaxell09/zenbot-scraper",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Scrapers de descarga y búsqueda para bots de WhatsApp — YouTube, TikTok, Instagram, Facebook, Twitter, Pinterest, MediaFire, GitHub, APK, Google Drive, XNXX, PornHub, XVideos, XHamster, Rule34, Screenshot y más.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/AxelDev09 ADDED
@@ -0,0 +1 @@
1
+ Tu papi Axel 👻
package/src/index.js CHANGED
@@ -32,6 +32,7 @@ export { shortenUrl, expandUrl } from './tools
32
32
  export { news, newsCategories } from './tools/news.js'
33
33
  export { upload } from './tools/upload.js'
34
34
  export { screenshot } from './tools/screenshot.js'
35
+ export { attp } from './tools/attp.js'
35
36
  export { xnxxSearch } from './nsfw/xnxxsearch.js'
36
37
  export { xnxxDownload } from './nsfw/xnxxdl.js'
37
38
  export { phSearch } from './nsfw/phsearch.js'
@@ -0,0 +1 @@
1
+ Tu papi Axel 👻
@@ -0,0 +1 @@
1
+ Tu papi Axel 👻
@@ -0,0 +1,189 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import axios from 'axios';
9
+ import * as cheerio from 'cheerio';
10
+
11
+ const UA = 'Mozilla/5.0 (Linux; Android 11; Redmi Note 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36';
12
+ const AJAX_URL = 'https://igsnapinsta.com/wp-admin/admin-ajax.php';
13
+ const BASE_URL = 'https://igsnapinsta.com';
14
+
15
+ function decodeUrl(encodedUrl) {
16
+ try { return Buffer.from(encodedUrl, 'base64').toString('utf-8'); }
17
+ catch { return encodedUrl; }
18
+ }
19
+
20
+ function parseItems(html) {
21
+ const $ = cheerio.load(html);
22
+ const items = [];
23
+ const seen = new Set();
24
+
25
+ function add(type, url) {
26
+ const clean = url.replace(/&amp;/g, '&').trim();
27
+ if (!clean || seen.has(clean)) return;
28
+ seen.add(clean);
29
+ items.push({ type, url: clean });
30
+ }
31
+
32
+ $('source[src]').each((_, el) => {
33
+ const src = $(el).attr('src');
34
+ if (src && !src.includes('kdnsd')) add('video', src);
35
+ });
36
+
37
+ $('img[src]').each((_, el) => {
38
+ const src = $(el).attr('src') || '';
39
+ if ((src.includes('cdninstagram') || src.includes('fbcdn')) && !src.includes('kdnsd'))
40
+ add('image', src);
41
+ });
42
+
43
+ if (!items.length) {
44
+ $('a[href]').each((_, el) => {
45
+ const href = $(el).attr('href') || '';
46
+ if (!href.includes('kdnsd/v1/download')) return;
47
+ const b64 = href.split('url=')[1] || '';
48
+ const decoded = decodeUrl(decodeURIComponent(b64));
49
+ const type = decoded.includes('.mp4') ? 'video' : 'image';
50
+ add(type, href.replace(/&amp;/g, '&'));
51
+ });
52
+
53
+ $('img[src]').each((_, el) => {
54
+ const src = $(el).attr('src') || '';
55
+ if (src.includes('kdnsd/v1/download')) add('image', src);
56
+ });
57
+ }
58
+
59
+ return items;
60
+ }
61
+
62
+ function detectType(url) {
63
+ if (url.includes('/reel/')) return 'reel';
64
+ if (url.includes('/p/')) return 'post';
65
+ if (url.includes('/stories/')) return 'story';
66
+ if (url.includes('/tv/')) return 'video';
67
+ const path = new URL(url).pathname.replace(/\/$/, '');
68
+ if (path.split('/').length === 2) return 'profile';
69
+ return 'post';
70
+ }
71
+
72
+ export async function igDownload(url) {
73
+ if (!url.includes('instagram.com'))
74
+ throw new Error('URL inválida. Debe ser un link de Instagram.');
75
+
76
+ // Limpiar URL: quitar parámetros UTM y asegurar trailing slash
77
+ const base = url.split('?')[0].split('#')[0]
78
+ url = base.endsWith('/') ? base : base + '/'
79
+
80
+
81
+
82
+ const { data } = await axios.post(
83
+ AJAX_URL,
84
+ new URLSearchParams({ action: 'kdnsd_get_instagram_video', url }),
85
+ {
86
+ headers: {
87
+ 'User-Agent': UA,
88
+ 'Content-Type': 'application/x-www-form-urlencoded',
89
+ 'Referer': `${BASE_URL}/es/`,
90
+ 'Origin': BASE_URL,
91
+ 'X-Requested-With': 'XMLHttpRequest',
92
+ },
93
+ timeout: 20000
94
+ }
95
+ );
96
+
97
+ if (!data?.success || !data?.data?.html) {
98
+ const html = data?.data?.html || '';
99
+ if (html.includes('private') || html.includes('privado') || data?.data?.message?.includes('private'))
100
+ throw new Error('Perfil privado. Solo se puede descargar contenido de perfiles públicos.');
101
+ throw new Error('No se pudo obtener el contenido. Verificá que el perfil/post sea público.');
102
+ }
103
+
104
+ const type = detectType(url);
105
+ const items = parseItems(data.data.html);
106
+
107
+ if (!items.length)
108
+ throw new Error('No se encontró contenido descargable.');
109
+
110
+ return { type, items };
111
+ }
112
+
113
+ export async function igReelDownload(url) {
114
+ if (!url.includes('instagram.com') || (!url.includes('/reel/') && !url.includes('/p/')))
115
+ throw new Error('URL inválida. Debe ser un link de reel o post de Instagram.')
116
+ return igDownload(url)
117
+ }
118
+
119
+ export async function igStalk(input) {
120
+ const username = input.replace(/https?:\/\/(www\.)?instagram\.com\/?/, '').replace(/\/$/, '').replace('@', '').split('?')[0].trim()
121
+ if (!username) throw new Error('Usuario inválido.')
122
+
123
+ const UAs = [
124
+ 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
125
+ 'Twitterbot/1.0',
126
+ 'WhatsApp/2.23.1 A',
127
+ ]
128
+
129
+ for (const ua of UAs) {
130
+ try {
131
+ const { data: html } = await axios.get(`https://www.instagram.com/${username}/`, {
132
+ headers: { 'User-Agent': ua, 'Accept-Language': 'en-US,en;q=0.9' },
133
+ timeout: 12000
134
+ })
135
+ const $ = cheerio.load(html)
136
+ const meta = $('meta[property="og:description"]').attr('content') || ''
137
+ const name = $('meta[property="og:title"]').attr('content') || ''
138
+ const avatar = $('meta[property="og:image"]').attr('content') || ''
139
+ if (!name && !meta) continue
140
+ const m = meta.match(/([\d,.KkMm]+)\s*Followers[,\s]+([\d,.KkMm]+)\s*Following[,\s]+([\d,.KkMm]+)\s*Posts?/i)
141
+ const bioRaw = meta.replace(/[\d,.KkMm]+\s*Followers[^-–—]*[-–—]\s*/i, '').trim()
142
+ const bioClean = bioRaw.replace(/^See Instagram photos and videos from .+/i, '').trim()
143
+ const fullNameClean = name
144
+ .replace(/\s*\(@?[^)]*\)\s*/g, '')
145
+ .replace(/\s*[•·]\s*Instagram.*/i, '')
146
+ .trim()
147
+ const isVerified = html.includes('"is_verified":true') || html.includes('"verified":true') || html.includes('aria-label="Verified"')
148
+ const isPrivate = html.includes('"is_private":true')
149
+ return {
150
+ username,
151
+ fullName: fullNameClean || username,
152
+ bio: bioClean || '',
153
+ followers: m ? m[1] : '?',
154
+ following: m ? m[2] : '?',
155
+ posts: m ? m[3] : '?',
156
+ isPrivate,
157
+ isVerified,
158
+ avatar,
159
+ url: `https://www.instagram.com/${username}/`,
160
+ }
161
+ } catch {}
162
+ }
163
+
164
+ throw new Error('No se pudo obtener información del perfil.')
165
+ }
166
+
167
+ export async function igStories(input) {
168
+ const username = input.replace(/https?:\/\/(www\.)?instagram\.com\/?/, '').replace(/\/$/, '').replace('@', '').split('?')[0].trim()
169
+ if (!username) throw new Error('Usuario inválido.')
170
+ for (const base of ['https://storiesig.info', 'https://imginn.com']) {
171
+ try {
172
+ const { data: html } = await axios.get(`${base}/stories/${username}/`, {
173
+ headers: { 'User-Agent': UA },
174
+ timeout: 12000
175
+ })
176
+ const $ = cheerio.load(html)
177
+ const items = []
178
+ $('img[src], source[src], video[src]').each((_, el) => {
179
+ const src = $(el).attr('src') || ''
180
+ if (src && (src.includes('cdninstagram') || src.includes('fbcdn') || src.includes(base))) {
181
+ const type = $(el).is('source, video') ? 'video' : 'image'
182
+ items.push({ type, url: src })
183
+ }
184
+ })
185
+ if (items.length) return { username, items }
186
+ } catch {}
187
+ }
188
+ throw new Error('No se encontraron stories o el perfil es privado.')
189
+ }
@@ -0,0 +1 @@
1
+ Tu papi Axel 👻
@@ -0,0 +1 @@
1
+ Tu papi Axel 👻
@@ -0,0 +1,176 @@
1
+ /*
2
+ * © Created by AxelDev09 🔥
3
+ * GitHub: https://github.com/AxelDev09
4
+ * Instagram: @axeldev09
5
+ * Deja los créditos we 🗣️
6
+ */
7
+
8
+ import gifencPkg from 'gifenc'
9
+ const { GIFEncoder, quantize, applyPalette } = gifencPkg
10
+
11
+ const FONT_7x9 = {
12
+ 'A': ['0011100','0100010','1000001','1000001','1111111','1000001','1000001','1000001','0000000'],
13
+ 'B': ['1111110','1000001','1000001','1111110','1000001','1000001','1000001','1111110','0000000'],
14
+ 'C': ['0111110','1000001','1000000','1000000','1000000','1000000','1000001','0111110','0000000'],
15
+ 'D': ['1111100','1000010','1000001','1000001','1000001','1000001','1000010','1111100','0000000'],
16
+ 'E': ['1111111','1000000','1000000','1111110','1000000','1000000','1000000','1111111','0000000'],
17
+ 'F': ['1111111','1000000','1000000','1111110','1000000','1000000','1000000','1000000','0000000'],
18
+ 'G': ['0111110','1000001','1000000','1000000','1001111','1000001','1000001','0111111','0000000'],
19
+ 'H': ['1000001','1000001','1000001','1111111','1000001','1000001','1000001','1000001','0000000'],
20
+ 'I': ['0111110','0001000','0001000','0001000','0001000','0001000','0001000','0111110','0000000'],
21
+ 'J': ['0001111','0000010','0000010','0000010','0000010','1000010','1000010','0111100','0000000'],
22
+ 'K': ['1000001','1000010','1000100','1001000','1110000','1001000','1000100','1000010','0000000'],
23
+ 'L': ['1000000','1000000','1000000','1000000','1000000','1000000','1000000','1111111','0000000'],
24
+ 'M': ['1000001','1100011','1010101','1001001','1000001','1000001','1000001','1000001','0000000'],
25
+ 'N': ['1000001','1100001','1010001','1001001','1000101','1000011','1000001','1000001','0000000'],
26
+ 'O': ['0111110','1000001','1000001','1000001','1000001','1000001','1000001','0111110','0000000'],
27
+ 'P': ['1111110','1000001','1000001','1111110','1000000','1000000','1000000','1000000','0000000'],
28
+ 'Q': ['0111110','1000001','1000001','1000001','1000001','1000101','1000010','0111101','0000000'],
29
+ 'R': ['1111110','1000001','1000001','1111110','1001000','1000100','1000010','1000001','0000000'],
30
+ 'S': ['0111110','1000001','1000000','0111110','0000001','0000001','1000001','0111110','0000000'],
31
+ 'T': ['1111111','0001000','0001000','0001000','0001000','0001000','0001000','0001000','0000000'],
32
+ 'U': ['1000001','1000001','1000001','1000001','1000001','1000001','1000001','0111110','0000000'],
33
+ 'V': ['1000001','1000001','1000001','1000001','1000001','0100010','0010100','0001000','0000000'],
34
+ 'W': ['1000001','1000001','1000001','1001001','1001001','1010101','1100011','1000001','0000000'],
35
+ 'X': ['1000001','0100010','0010100','0001000','0001000','0010100','0100010','1000001','0000000'],
36
+ 'Y': ['1000001','0100010','0010100','0001000','0001000','0001000','0001000','0001000','0000000'],
37
+ 'Z': ['1111111','0000001','0000010','0000100','0001000','0010000','0100000','1111111','0000000'],
38
+ 'a': ['0000000','0111110','0000001','0111111','1000001','1000011','0111101','0000000','0000000'],
39
+ 'b': ['1000000','1000000','1000000','1111110','1000001','1000001','1000001','1111110','0000000'],
40
+ 'c': ['0000000','0111110','1000001','1000000','1000000','1000001','1000001','0111110','0000000'],
41
+ 'd': ['0000001','0000001','0000001','0111111','1000001','1000001','1000001','0111111','0000000'],
42
+ 'e': ['0000000','0111110','1000001','1111111','1000000','1000000','1000001','0111110','0000000'],
43
+ 'f': ['0001110','0010001','0010000','0111100','0010000','0010000','0010000','0010000','0000000'],
44
+ 'g': ['0000000','0111111','1000001','1000001','0111111','0000001','0000001','0111110','0000000'],
45
+ 'h': ['1000000','1000000','1000000','1111110','1000001','1000001','1000001','1000001','0000000'],
46
+ 'i': ['0001000','0000000','0011000','0001000','0001000','0001000','0001000','0111110','0000000'],
47
+ 'j': ['0000100','0000000','0001100','0000100','0000100','0000100','1000100','0111000','0000000'],
48
+ 'k': ['1000000','1000000','1000010','1000100','1001000','1110000','1001000','1000110','0000000'],
49
+ 'l': ['0011000','0001000','0001000','0001000','0001000','0001000','0001000','0111110','0000000'],
50
+ 'm': ['0000000','0000000','1101101','1010101','1010101','1000001','1000001','1000001','0000000'],
51
+ 'n': ['0000000','0000000','1111110','1000001','1000001','1000001','1000001','1000001','0000000'],
52
+ 'o': ['0000000','0111110','1000001','1000001','1000001','1000001','1000001','0111110','0000000'],
53
+ 'p': ['0000000','1111110','1000001','1000001','1111110','1000000','1000000','1000000','0000000'],
54
+ 'q': ['0000000','0111111','1000001','1000001','0111111','0000001','0000001','0000001','0000000'],
55
+ 'r': ['0000000','0000000','1011110','1100001','1000000','1000000','1000000','1000000','0000000'],
56
+ 's': ['0000000','0111110','1000000','0111110','0000001','0000001','1000001','0111110','0000000'],
57
+ 't': ['0010000','0010000','1111110','0010000','0010000','0010000','0010001','0001110','0000000'],
58
+ 'u': ['0000000','0000000','1000001','1000001','1000001','1000001','1000011','0111101','0000000'],
59
+ 'v': ['0000000','0000000','1000001','1000001','0100010','0100010','0010100','0001000','0000000'],
60
+ 'w': ['0000000','0000000','1000001','1000001','1001001','1010101','1100011','1000001','0000000'],
61
+ 'x': ['0000000','0000000','1000001','0100010','0011100','0011100','0100010','1000001','0000000'],
62
+ 'y': ['0000000','1000001','1000001','0111111','0000001','0000001','1000001','0111110','0000000'],
63
+ 'z': ['0000000','0000000','1111111','0000010','0000100','0001000','0010000','1111111','0000000'],
64
+ '0': ['0111110','1000001','1000011','1000101','1001001','1010001','1100001','0111110','0000000'],
65
+ '1': ['0001000','0011000','0001000','0001000','0001000','0001000','0001000','0111110','0000000'],
66
+ '2': ['0111110','1000001','0000001','0000110','0011000','0100000','1000000','1111111','0000000'],
67
+ '3': ['0111110','1000001','0000001','0011110','0000001','0000001','1000001','0111110','0000000'],
68
+ '4': ['0000100','0001100','0010100','0100100','1000100','1111111','0000100','0000100','0000000'],
69
+ '5': ['1111111','1000000','1000000','1111110','0000001','0000001','1000001','0111110','0000000'],
70
+ '6': ['0111110','1000001','1000000','1111110','1000001','1000001','1000001','0111110','0000000'],
71
+ '7': ['1111111','0000001','0000010','0000100','0001000','0010000','0010000','0010000','0000000'],
72
+ '8': ['0111110','1000001','1000001','0111110','1000001','1000001','1000001','0111110','0000000'],
73
+ '9': ['0111110','1000001','1000001','0111111','0000001','0000001','1000001','0111110','0000000'],
74
+ ' ': ['0000000','0000000','0000000','0000000','0000000','0000000','0000000','0000000','0000000'],
75
+ '!': ['0001000','0001000','0001000','0001000','0001000','0000000','0000000','0001000','0000000'],
76
+ '?': ['0111110','1000001','0000001','0000110','0001000','0001000','0000000','0001000','0000000'],
77
+ '.': ['0000000','0000000','0000000','0000000','0000000','0000000','0000000','0001000','0000000'],
78
+ ',': ['0000000','0000000','0000000','0000000','0000000','0001100','0001000','0010000','0000000'],
79
+ '-': ['0000000','0000000','0000000','1111111','0000000','0000000','0000000','0000000','0000000'],
80
+ ':': ['0000000','0001000','0000000','0000000','0000000','0001000','0000000','0000000','0000000'],
81
+ }
82
+
83
+ const CHAR_W = 7
84
+ const CHAR_H = 9
85
+ const CHAR_GAP = 1
86
+
87
+ const COLORS = [
88
+ [255, 0, 0 ],
89
+ [255, 127, 0 ],
90
+ [255, 220, 0 ],
91
+ [0, 210, 0 ],
92
+ [0, 120, 255],
93
+ [148, 0, 211],
94
+ ]
95
+
96
+ function markPixel(mask, px, py, W, H) {
97
+ if (px < 0 || py < 0 || px >= W || py >= H) return
98
+ mask[py * W + px] = true
99
+ }
100
+
101
+ function setPixel(pixels, px, py, r, g, b, a, W, H) {
102
+ if (px < 0 || py < 0 || px >= W || py >= H) return
103
+ const i = (py * W + px) * 4
104
+ pixels[i] = r
105
+ pixels[i + 1] = g
106
+ pixels[i + 2] = b
107
+ pixels[i + 3] = a
108
+ }
109
+
110
+ export async function attp(text, scale = 5) {
111
+ if (!text || typeof text !== 'string') throw new Error('El texto es requerido')
112
+ const str = text.toUpperCase().slice(0, 40)
113
+ const PAD = 12 + scale
114
+ const W = str.length * (CHAR_W + CHAR_GAP) * scale + PAD * 2
115
+ const H = CHAR_H * scale + PAD * 2
116
+ const gif = GIFEncoder()
117
+
118
+ for (const [fr, fg, fb] of COLORS) {
119
+ const pixels = new Uint8Array(W * H * 4)
120
+ const letterMask = new Uint8Array(W * H)
121
+
122
+ let cx = PAD
123
+ for (const ch of str) {
124
+ const glyph = FONT_7x9[ch] || FONT_7x9[' ']
125
+ for (let row = 0; row < glyph.length; row++) {
126
+ for (let col = 0; col < glyph[row].length; col++) {
127
+ if (glyph[row][col] === '1') {
128
+ for (let sy = 0; sy < scale; sy++)
129
+ for (let sx = 0; sx < scale; sx++)
130
+ markPixel(letterMask, cx + col * scale + sx, PAD + row * scale + sy, W, H)
131
+ }
132
+ }
133
+ }
134
+ cx += (CHAR_W + CHAR_GAP) * scale
135
+ }
136
+
137
+ const outlineThickness = Math.max(1, Math.round(scale * 0.35))
138
+ for (let py = 0; py < H; py++) {
139
+ for (let px = 0; px < W; px++) {
140
+ if (letterMask[py * W + px]) continue
141
+ let near = false
142
+ outer: for (let dy = -outlineThickness; dy <= outlineThickness; dy++) {
143
+ for (let dx = -outlineThickness; dx <= outlineThickness; dx++) {
144
+ const nx = px + dx, ny = py + dy
145
+ if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue
146
+ if (letterMask[ny * W + nx]) { near = true; break outer }
147
+ }
148
+ }
149
+ if (near) setPixel(pixels, px, py, 0, 0, 0, 255, W, H)
150
+ }
151
+ }
152
+
153
+ for (let py = 0; py < H; py++)
154
+ for (let px = 0; px < W; px++)
155
+ if (letterMask[py * W + px])
156
+ setPixel(pixels, px, py, fr, fg, fb, 255, W, H)
157
+
158
+ const palette = quantize(pixels, 256, { format: 'rgba4444' })
159
+ const index = applyPalette(pixels, palette, 'rgba4444')
160
+
161
+ let transparentIndex = 0
162
+ for (let i = 0; i < palette.length; i++) {
163
+ if (palette[i][3] === 0) { transparentIndex = i; break }
164
+ }
165
+
166
+ gif.writeFrame(index, W, H, {
167
+ palette,
168
+ delay: 130,
169
+ transparent: true,
170
+ transparentIndex,
171
+ })
172
+ }
173
+
174
+ gif.finish()
175
+ return Buffer.from(gif.bytesView())
176
+ }
@@ -14,3 +14,4 @@ export { shortenUrl, expandUrl } from './urlshortener.js'
14
14
  export { news, newsCategories } from './news.js'
15
15
  export { upload } from './upload.js'
16
16
  export { screenshot } from './screenshot.js'
17
+ export { attp } from './attp.js'