wsper-js 0.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/LICENSE +689 -0
- package/README.md +1616 -0
- package/dist/index.d.ts +2793 -0
- package/dist/index.js +1 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,1616 @@
|
|
|
1
|
+
<img src="https://i.pinimg.com/736x/9a/d3/8c/9ad38cbf49e39e599102b3021b2e88d5.jpg" alt="" align="center" width="1000">
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# wsper
|
|
5
|
+
|
|
6
|
+
library berikut saya buat untuk mempermudah kalian membuat sebuah bot whatsapp, dengan ini anda tidak perlu lagi untuk menambah code yang tidak jelas dan sering error awookawoka
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Daftar Isi
|
|
11
|
+
|
|
12
|
+
- [Instalasi](#instalasi)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [WsperScraper — Kelas Utama](#wsperscraper--kelas-utama)
|
|
15
|
+
- [Response Structure](#response-structure)
|
|
16
|
+
- [Scrapers](#scrapers)
|
|
17
|
+
- [Instagram](#instagram)
|
|
18
|
+
- [Spotify](#spotify)
|
|
19
|
+
- [YouTube](#youtube)
|
|
20
|
+
- [Threads](#threads)
|
|
21
|
+
- [Twitter / X](#twitter--x)
|
|
22
|
+
- [Pinterest](#pinterest)
|
|
23
|
+
- [BiliBili](#bilibili)
|
|
24
|
+
- [CapCut](#capcut)
|
|
25
|
+
- [Komikindo](#komikindo)
|
|
26
|
+
- [Mediafire](#mediafire)
|
|
27
|
+
- [Play Store](#play-store)
|
|
28
|
+
- [Minecraft Addon](#minecraft-addon)
|
|
29
|
+
- [Alkitab](#alkitab)
|
|
30
|
+
- [BMKG](#bmkg)
|
|
31
|
+
- [Cuaca](#cuaca)
|
|
32
|
+
- [Drakor](#drakor)
|
|
33
|
+
- [Dramabox](#dramabox)
|
|
34
|
+
- [SakuraNovel](#sakuranovel)
|
|
35
|
+
- [Uguu](#uguu)
|
|
36
|
+
- [HokInfo](#hokinfo)
|
|
37
|
+
- [IkiruManga](#ikirumanga)
|
|
38
|
+
- [Wallpaper](#wallpaper)
|
|
39
|
+
- [WwChar](#wwchar)
|
|
40
|
+
- [Videy](#videy)
|
|
41
|
+
- [OCR](#ocr)
|
|
42
|
+
- [Webp2Mp4](#webp2mp4)
|
|
43
|
+
- [MConverter](#mconverter)
|
|
44
|
+
- [HtmlToJpg](#htmltojpg)
|
|
45
|
+
- [Stalk](#stalk)
|
|
46
|
+
- [ModAndroid](#modandroid)
|
|
47
|
+
- [Upscaler](#upscaler)
|
|
48
|
+
- [Image](#image)
|
|
49
|
+
- [ImgUpscaler](#imgupscaler)
|
|
50
|
+
- [Faceswap](#faceswap)
|
|
51
|
+
- [PhotoAi](#photoai)
|
|
52
|
+
- [Lyrics](#lyrics)
|
|
53
|
+
- [Resep](#resep)
|
|
54
|
+
- [Top Anime](#top-anime)
|
|
55
|
+
- [Anime Quote](#anime-quote)
|
|
56
|
+
- [Anime Random](#anime-random)
|
|
57
|
+
- [Brat Generator](#brat-generator)
|
|
58
|
+
- [generate — Image](#generate--image)
|
|
59
|
+
- [generate — GIF](#generate--gif)
|
|
60
|
+
- [generate — Video](#generate--video)
|
|
61
|
+
- [withPreset](#withpreset)
|
|
62
|
+
- [imageToSticker](#imagetosticker)
|
|
63
|
+
- [MP4 ↔ GIF Conversion](#mp4--gif-conversion)
|
|
64
|
+
- [convertImage](#convertimage)
|
|
65
|
+
- [Background Config](#background-config)
|
|
66
|
+
- [Text Config](#text-config)
|
|
67
|
+
- [Canvas Config](#canvas-config)
|
|
68
|
+
- [Animation Config](#animation-config)
|
|
69
|
+
- [Output Config](#output-config)
|
|
70
|
+
- [Chart Image Generator](#chart-image-generator)
|
|
71
|
+
- [Credentials](#credentials)
|
|
72
|
+
- [HTTP & Queue Options](#http--queue-options)
|
|
73
|
+
- [Download Outputs](#download-outputs)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Instalasi
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install wsper
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Dependency eksternal yang dibutuhkan untuk fitur tertentu:
|
|
84
|
+
|
|
85
|
+
| Fitur | Dependency |
|
|
86
|
+
|---|---|
|
|
87
|
+
| YouTube download / audio | `yt-dlp` (install terpisah) |
|
|
88
|
+
| Video export, GIF ↔ MP4 | `ffmpeg` di PATH atau `C:\ffmpeg\ffmpeg.exe` |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { WsperScraper, BratGenerator, ChartGenerator } from "wsper";
|
|
96
|
+
|
|
97
|
+
// Semua scraper dalam satu kelas
|
|
98
|
+
const wsper = new WsperScraper();
|
|
99
|
+
|
|
100
|
+
const profile = await wsper.instagram.getProfile("charli_xcx");
|
|
101
|
+
const track = await wsper.spotify.getTrack("https://open.spotify.com/track/...");
|
|
102
|
+
const video = await wsper.youtube.getVideo("https://youtu.be/...");
|
|
103
|
+
|
|
104
|
+
// Brat Generator
|
|
105
|
+
const brat = new BratGenerator();
|
|
106
|
+
const result = await brat.generate({
|
|
107
|
+
text: { value: "brat summer" },
|
|
108
|
+
background: { type: "solid", color: "#8ace00" },
|
|
109
|
+
output: { type: "image", format: "png", path: "./brat.png" },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Chart Generator
|
|
113
|
+
const chart = new ChartGenerator();
|
|
114
|
+
await chart.generateStatsImage({
|
|
115
|
+
model: "modern-dashboard",
|
|
116
|
+
output: "./analytics.png",
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## WsperScraper — Kelas Utama
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { WsperScraper } from "wsper";
|
|
126
|
+
|
|
127
|
+
const wsper = new WsperScraper(config?: WsperScraperConfig);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### WsperScraperConfig
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
interface WsperScraperConfig {
|
|
134
|
+
debug?: boolean;
|
|
135
|
+
spotifyCredentials?: {
|
|
136
|
+
clientId: string;
|
|
137
|
+
clientSecret: string;
|
|
138
|
+
callbackUrl?: string;
|
|
139
|
+
market?: string;
|
|
140
|
+
};
|
|
141
|
+
credentials?: {
|
|
142
|
+
instagram?: { cookie?: string; csrfToken?: string };
|
|
143
|
+
threads?: { cookie?: string; csrfToken?: string };
|
|
144
|
+
twitter?: { cookie?: string; csrfToken?: string };
|
|
145
|
+
pinterest?: { cookie?: string; csrfToken?: string };
|
|
146
|
+
};
|
|
147
|
+
http?: HttpOptions;
|
|
148
|
+
queue?: QueueOptions;
|
|
149
|
+
youtube?: YouTubeScraperOptions;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Properties
|
|
154
|
+
|
|
155
|
+
| Property | Type | Deskripsi |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `wsper.instagram` | `InstagramScraper` | Instagram scraper |
|
|
158
|
+
| `wsper.spotify` | `SpotifyScraper` | Spotify scraper |
|
|
159
|
+
| `wsper.youtube` | `YouTubeScraper` | YouTube scraper |
|
|
160
|
+
| `wsper.threads` | `ThreadsScraper` | Threads scraper |
|
|
161
|
+
| `wsper.twitter` | `TwitterScraper` | Twitter/X scraper |
|
|
162
|
+
| `wsper.pinterest` | `PinterestScraper` | Pinterest scraper |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Response Structure
|
|
167
|
+
|
|
168
|
+
Semua scraper mengembalikan `WsperResponse<T>`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
interface WsperResponse<T, Meta = WsperResponseMeta> {
|
|
172
|
+
ok: boolean;
|
|
173
|
+
data: T | null;
|
|
174
|
+
error: { code: string; message: string; details?: Record<string, unknown> } | null;
|
|
175
|
+
meta: Meta & {
|
|
176
|
+
statusCode: number;
|
|
177
|
+
sourceUrl: string;
|
|
178
|
+
fetchedAt: string; // ISO timestamp
|
|
179
|
+
durationMs: number;
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Pattern penggunaan:**
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const res = await wsper.instagram.getProfile("username");
|
|
188
|
+
|
|
189
|
+
if (res.ok) {
|
|
190
|
+
console.log(res.data.username);
|
|
191
|
+
console.log(res.meta.durationMs, "ms");
|
|
192
|
+
} else {
|
|
193
|
+
console.error(res.error?.code, res.error?.message);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Scrapers
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Instagram
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { InstagramScraper } from "wsper";
|
|
207
|
+
const ig = new InstagramScraper(options?: InstagramScraperOptions);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### `getProfile(username)` → `WsperResponse<InstagramProfile>`
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
await ig.getProfile("charli_xcx")
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
interface InstagramProfile {
|
|
218
|
+
id: string;
|
|
219
|
+
username: string;
|
|
220
|
+
fullName: string;
|
|
221
|
+
biography: string;
|
|
222
|
+
profilePicUrl: string;
|
|
223
|
+
profilePicUrlHd: string | null;
|
|
224
|
+
followersCount: number;
|
|
225
|
+
followingCount: number;
|
|
226
|
+
mediaCount: number;
|
|
227
|
+
isPrivate: boolean;
|
|
228
|
+
isVerified: boolean;
|
|
229
|
+
externalUrl: string | null;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `getFeed(username, options?)` → `WsperResponse<InstagramFeed>`
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await ig.getFeed("charli_xcx", { count: 12, maxId: "cursor" })
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
interface InstagramFeed {
|
|
241
|
+
items: InstagramMediaItem[];
|
|
242
|
+
nextMaxId: string | null;
|
|
243
|
+
hasMore: boolean;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
interface InstagramMediaItem {
|
|
247
|
+
id: string;
|
|
248
|
+
shortcode: string;
|
|
249
|
+
mediaType: number; // 1=image 2=video 8=carousel
|
|
250
|
+
productType: string | null;
|
|
251
|
+
caption: string | null;
|
|
252
|
+
takenAt: number | null;
|
|
253
|
+
likeCount: number | null;
|
|
254
|
+
commentCount: number | null;
|
|
255
|
+
thumbnailUrl: string | null;
|
|
256
|
+
videoUrl: string | null;
|
|
257
|
+
carousel: InstagramCarouselItem[] | null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface InstagramCarouselItem {
|
|
261
|
+
id: string;
|
|
262
|
+
mediaType: number;
|
|
263
|
+
thumbnailUrl: string | null;
|
|
264
|
+
videoUrl: string | null;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### `getPost(input)` → `WsperResponse<InstagramMediaItem>`
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
await ig.getPost("https://www.instagram.com/p/AbCdEfG/")
|
|
272
|
+
await ig.getPost("AbCdEfG") // shortcode langsung
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### `getProfileWithFeed(username, options?)` → `WsperResponse<InstagramProfileWithFeed>`
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
await ig.getProfileWithFeed("charli_xcx", { count: 6 })
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
interface InstagramProfileWithFeed {
|
|
283
|
+
profile: InstagramProfile;
|
|
284
|
+
items: InstagramMediaItem[];
|
|
285
|
+
nextMaxId: string | null;
|
|
286
|
+
hasMore: boolean;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### `downloadPost(input)` → `WsperResponse<InstagramDownloadResult>`
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
await ig.downloadPost({
|
|
294
|
+
postUrl: "https://www.instagram.com/p/AbCdEfG/",
|
|
295
|
+
outputDir: "./downloads/instagram",
|
|
296
|
+
includeMetadata: true,
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
interface InstagramDownloadResult {
|
|
302
|
+
sourceUrl: string;
|
|
303
|
+
outputDir: string;
|
|
304
|
+
metadataPath: string | null;
|
|
305
|
+
assets: Array<{
|
|
306
|
+
url: string;
|
|
307
|
+
outputPath: string;
|
|
308
|
+
type: "image" | "video";
|
|
309
|
+
index: number;
|
|
310
|
+
}>;
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### `downloadProfile(input)` → `WsperResponse<InstagramDownloadResult>`
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
await ig.downloadProfile({
|
|
318
|
+
usernameOrUrl: "charli_xcx",
|
|
319
|
+
outputDir: "./downloads/instagram",
|
|
320
|
+
feedCount: 9,
|
|
321
|
+
includeProfilePicture: true,
|
|
322
|
+
includeInitialPosts: true,
|
|
323
|
+
includeMetadata: true,
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### Spotify
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { SpotifyScraper } from "wsper";
|
|
333
|
+
const spotify = new SpotifyScraper(options?: SpotifyScraperOptions);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### `getTrack(input, options?)` → `WsperResponse<NormalizedSpotifyTrack, SpotifyTrackMeta>`
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
await spotify.getTrack("https://open.spotify.com/track/4iV5W9uYEdYUVa79Axb7Rh")
|
|
340
|
+
await spotify.getTrack("4iV5W9uYEdYUVa79Axb7Rh", { enrichYtDlp: true })
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
interface NormalizedSpotifyTrack {
|
|
345
|
+
id: string;
|
|
346
|
+
name: string;
|
|
347
|
+
uri: string;
|
|
348
|
+
url: string | null;
|
|
349
|
+
durationMs: number;
|
|
350
|
+
durationString: string | null;
|
|
351
|
+
explicit: boolean;
|
|
352
|
+
trackNumber: number | null;
|
|
353
|
+
discNumber: number | null;
|
|
354
|
+
popularity: number | null;
|
|
355
|
+
isPlayable: boolean | null;
|
|
356
|
+
previewUrl: string | null;
|
|
357
|
+
artists: NormalizedSpotifyArtist[];
|
|
358
|
+
album: NormalizedSpotifyAlbum;
|
|
359
|
+
externalIds: Record<string, string>;
|
|
360
|
+
externalSource: ExternalSource | null; // yt-dlp enrichment (jika enrichYtDlp: true)
|
|
361
|
+
download: DownloadInfo | null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Meta tambahan untuk track
|
|
365
|
+
interface SpotifyTrackMeta extends WsperResponseMeta {
|
|
366
|
+
externalSourceUrl: string | null;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
#### `getAlbum(input, options?)` → `WsperResponse<SpotifyAlbumWithTracks>`
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
await spotify.getAlbum("https://open.spotify.com/album/...")
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
interface SpotifyAlbumWithTracks {
|
|
378
|
+
album: NormalizedSpotifyAlbum;
|
|
379
|
+
tracks: NormalizedSpotifyTrack[];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
interface NormalizedSpotifyAlbum {
|
|
383
|
+
id: string;
|
|
384
|
+
name: string;
|
|
385
|
+
albumType: string | null;
|
|
386
|
+
uri: string;
|
|
387
|
+
url: string | null;
|
|
388
|
+
releaseDate: string | null;
|
|
389
|
+
totalTracks: number | null;
|
|
390
|
+
images: SpotifyImage[];
|
|
391
|
+
artists: NormalizedSpotifyArtist[];
|
|
392
|
+
copyrights: Array<{ text: string; type: string }>;
|
|
393
|
+
label: string | null;
|
|
394
|
+
popularity: number | null;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### `getPlaylist(input)` → `WsperResponse<NormalizedSpotifyPlaylist>`
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
await spotify.getPlaylist("https://open.spotify.com/playlist/...")
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### `search(query, options?)` → `WsperResponse<SpotifySearchResult>`
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
await spotify.search("charli xcx brat", { limit: 10, type: ["track", "album"] })
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### `downloadPost(input)` / `downloadProfile(input)`
|
|
411
|
+
|
|
412
|
+
```ts
|
|
413
|
+
await spotify.downloadPost({
|
|
414
|
+
trackUrl: "https://open.spotify.com/track/...",
|
|
415
|
+
outputDir: "./downloads/spotify",
|
|
416
|
+
format: "mp3",
|
|
417
|
+
})
|
|
418
|
+
// → WsperResponse<SpotifyDownloadResult>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
#### OAuth helpers
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
spotify.createAuthUrl({ state: "xyz", scopes: ["user-read-private"] })
|
|
425
|
+
// → { state: string; url: string }
|
|
426
|
+
|
|
427
|
+
await spotify.exchangeCode(code) // → SpotifyOAuthToken
|
|
428
|
+
await spotify.refreshToken(token) // → SpotifyOAuthToken
|
|
429
|
+
await spotify.getAccessToken() // → string
|
|
430
|
+
spotify.invalidateToken() // → void
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
### YouTube
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
import { YouTubeScraper } from "wsper";
|
|
439
|
+
const yt = new YouTubeScraper(options?: YouTubeScraperOptions);
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
interface YouTubeScraperOptions {
|
|
444
|
+
debug?: boolean;
|
|
445
|
+
ytdlpPath?: string; // path ke yt-dlp binary
|
|
446
|
+
ffmpegPath?: string;
|
|
447
|
+
ffprobePath?: string;
|
|
448
|
+
outputDir?: string;
|
|
449
|
+
searchLimit?: number;
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### `getVideo(input)` → `WsperResponse<YoutubeVideoMetadata>`
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
await yt.getVideo("https://youtu.be/dQw4w9WgXcQ")
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
```ts
|
|
460
|
+
interface YoutubeVideoMetadata {
|
|
461
|
+
provider: "youtube";
|
|
462
|
+
id: string;
|
|
463
|
+
title: string;
|
|
464
|
+
url: string;
|
|
465
|
+
durationSeconds: number | null;
|
|
466
|
+
durationString: string | null;
|
|
467
|
+
isLive: boolean;
|
|
468
|
+
uploadDate: string | null;
|
|
469
|
+
description: string | null;
|
|
470
|
+
thumbnail: string | null;
|
|
471
|
+
thumbnails: Array<{ url: string; width: number | null; height: number | null }>;
|
|
472
|
+
channel: {
|
|
473
|
+
id: string | null;
|
|
474
|
+
name: string | null;
|
|
475
|
+
url: string | null;
|
|
476
|
+
handle: string | null;
|
|
477
|
+
isVerified: boolean | null;
|
|
478
|
+
followerCount: number | null;
|
|
479
|
+
};
|
|
480
|
+
stats: {
|
|
481
|
+
viewCount: number | null;
|
|
482
|
+
likeCount: number | null;
|
|
483
|
+
commentCount: number | null;
|
|
484
|
+
};
|
|
485
|
+
formats: {
|
|
486
|
+
audio: YoutubeAudioFormat[];
|
|
487
|
+
video: YoutubeVideoFormat[];
|
|
488
|
+
};
|
|
489
|
+
download: YoutubeDownloadInfo;
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
#### `searchVideos(query, options?)` → `WsperResponse<YoutubeSearchResult>`
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
await yt.searchVideos("charli xcx brat", { limit: 5 })
|
|
497
|
+
// data: { query, total, items: YoutubeVideoMetadata[] }
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### `downloadVideo(input, options?)` → `WsperResponse<YoutubeDownloadResult>`
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
await yt.downloadVideo("https://youtu.be/...", {
|
|
504
|
+
outputDir: "./downloads",
|
|
505
|
+
fileName: "video",
|
|
506
|
+
format: "mp4",
|
|
507
|
+
})
|
|
508
|
+
// data: { sourceUrl, outputDir, format, type: "video", command }
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### `downloadAudio(input, options?)` → `WsperResponse<YoutubeDownloadResult>`
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
await yt.downloadAudio("https://youtu.be/...", {
|
|
515
|
+
outputDir: "./downloads",
|
|
516
|
+
audioFormat: "mp3", // "mp3" | "m4a" | "opus" | "flac" | "wav"
|
|
517
|
+
})
|
|
518
|
+
// data: { sourceUrl, outputDir, format, type: "audio", command }
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
#### `extractAudio(inputPath, options?)` → `WsperResponse<YoutubeAudioExtractResult>`
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
await yt.extractAudio("./video.mp4", {
|
|
525
|
+
outputPath: "./audio.mp3",
|
|
526
|
+
audioFormat: "mp3",
|
|
527
|
+
audioBitrate: "192k",
|
|
528
|
+
})
|
|
529
|
+
// data: { inputPath, outputPath, format, bitrate }
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### `getPlaylist(input)` → `WsperResponse<YoutubePlaylistData>`
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
await yt.getPlaylist("https://www.youtube.com/playlist?list=...")
|
|
536
|
+
// data: { id, title, url, uploader, channel, entriesTotal, entries[] }
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
#### `getChannel(input)` → `WsperResponse<YoutubeChannelData>`
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
await yt.getChannel("https://www.youtube.com/@channelname")
|
|
543
|
+
// data: { id, title, channel, channelId, entriesTotal, entries[] }
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
### Threads
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
import { ThreadsScraper } from "wsper";
|
|
552
|
+
const threads = new ThreadsScraper(options?: ScraperOptions);
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
#### `getProfile(usernameOrUrl)` → `WsperResponse<ThreadsProfile>`
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
await threads.getProfile("zuck")
|
|
559
|
+
await threads.getProfile("https://www.threads.net/@zuck")
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### `getPost(input)` → `WsperResponse<ThreadsPost>`
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
await threads.getPost("https://www.threads.net/@user/post/CgXxXxXxX")
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### Search methods → `WsperResponse<ThreadsSearchResult>`
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
await threads.search("brat summer")
|
|
572
|
+
await threads.searchByTag("bratstyle")
|
|
573
|
+
await threads.searchUsers("charli")
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### `downloadPost(input)` / `downloadProfile(input)` → `WsperResponse<ThreadsDownloadResult>`
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
await threads.downloadPost({
|
|
580
|
+
postUrl: "https://www.threads.net/@user/post/...",
|
|
581
|
+
outputDir: "./downloads/threads",
|
|
582
|
+
})
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
### Twitter / X
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
import { TwitterScraper } from "wsper";
|
|
591
|
+
const twitter = new TwitterScraper(options?: ScraperOptions);
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
> Sebagian besar endpoint membutuhkan `cookie` + `csrfToken` karena API Twitter memerlukan auth.
|
|
595
|
+
|
|
596
|
+
#### `getTweet(input)` → `WsperResponse<TwitterTweetDetail>`
|
|
597
|
+
|
|
598
|
+
```ts
|
|
599
|
+
await twitter.getTweet({ tweetId: "1234567890" })
|
|
600
|
+
await twitter.getTweet({ url: "https://x.com/user/status/1234567890" })
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
#### `searchTweets(query, options?)` → `WsperResponse<TwitterSearchResult>`
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
await twitter.searchTweets("brat summer", { limit: 20 })
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
#### Other search methods
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
await twitter.searchByTag("bratstyle") // → WsperResponse<TwitterSearchResult>
|
|
613
|
+
await twitter.searchTrend("brat") // → WsperResponse<TwitterSearchResult>
|
|
614
|
+
await twitter.searchUsers("charli xcx") // → WsperResponse<TwitterUserSearchResult>
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
#### `getProfile(usernameOrUrl)` → `WsperResponse<TwitterUser>`
|
|
618
|
+
|
|
619
|
+
```ts
|
|
620
|
+
await twitter.getProfile("charli_xcx")
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
### Pinterest
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
import { PinterestScraper } from "wsper";
|
|
629
|
+
const pinterest = new PinterestScraper(options?: ScraperOptions);
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
#### `getPin(input)` → `WsperResponse<PinterestPin>`
|
|
633
|
+
|
|
634
|
+
```ts
|
|
635
|
+
await pinterest.getPin("https://www.pinterest.com/pin/1234567890/")
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
#### `searchPins(query, options?)` → `WsperResponse<PinterestSearchResult>`
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
await pinterest.searchPins("brat aesthetic", { limit: 20 })
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
#### `downloadPin(input)` → `WsperResponse<PinterestDownloadResult>`
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
await pinterest.downloadPin({
|
|
648
|
+
pinUrl: "https://www.pinterest.com/pin/...",
|
|
649
|
+
outputDir: "./downloads/pinterest",
|
|
650
|
+
})
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
### BiliBili
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
import { BiliBiliScraper } from "wsper";
|
|
659
|
+
const bili = new BiliBiliScraper();
|
|
660
|
+
|
|
661
|
+
await bili.getVideoInfo("https://www.bilibili.com/video/BV...")
|
|
662
|
+
// → WsperResponse<BiliBiliResult>
|
|
663
|
+
// data: { title, cover, duration, author, views, plays, streams[] }
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
### CapCut
|
|
669
|
+
|
|
670
|
+
```ts
|
|
671
|
+
import { CapCutScraper } from "wsper";
|
|
672
|
+
const capcut = new CapCutScraper();
|
|
673
|
+
|
|
674
|
+
await capcut.download("https://www.capcut.com/t/...")
|
|
675
|
+
// → WsperResponse<CapCutDownloadResult>
|
|
676
|
+
// data: { title, coverUrl, videoUrl, authorName }
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
### Komikindo
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
import { KomikindoScraper } from "wsper";
|
|
685
|
+
const komik = new KomikindoScraper();
|
|
686
|
+
|
|
687
|
+
await komik.search("one piece")
|
|
688
|
+
// → WsperResponse<KomikindoSearchItem[]>
|
|
689
|
+
// data[]: { title, url, cover, type, status, rating, latestChapter }
|
|
690
|
+
|
|
691
|
+
await komik.getDetail("https://komikindo.tv/manga/...")
|
|
692
|
+
// → WsperResponse<KomikindoDetail>
|
|
693
|
+
// data: { title, cover, synopsis, chapters[], genres[] }
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
### Mediafire
|
|
699
|
+
|
|
700
|
+
```ts
|
|
701
|
+
import { MediafireScraper } from "wsper";
|
|
702
|
+
const mf = new MediafireScraper();
|
|
703
|
+
|
|
704
|
+
await mf.getLink("https://www.mediafire.com/file/.../file")
|
|
705
|
+
// → WsperResponse<MediafireResult>
|
|
706
|
+
// data: { fileName, fileSize, downloadUrl, uploadDate }
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
### Play Store
|
|
712
|
+
|
|
713
|
+
```ts
|
|
714
|
+
import { PlayStoreScraper } from "wsper";
|
|
715
|
+
const store = new PlayStoreScraper();
|
|
716
|
+
|
|
717
|
+
await store.search("spotify", 5)
|
|
718
|
+
// → WsperResponse<PlayStoreApp[]>
|
|
719
|
+
// data[]: { name, packageId, developer, rating, downloads, iconUrl, url }
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
### Minecraft Addon
|
|
725
|
+
|
|
726
|
+
```ts
|
|
727
|
+
import { McAddonScraper } from "wsper";
|
|
728
|
+
const mc = new McAddonScraper();
|
|
729
|
+
|
|
730
|
+
await mc.search("texture pack")
|
|
731
|
+
// → WsperResponse<McAddonSearchItem[]>
|
|
732
|
+
|
|
733
|
+
await mc.getDetail("https://mcpedl.com/addon/...")
|
|
734
|
+
// → WsperResponse<McAddonDetail>
|
|
735
|
+
// data: { title, description, downloadLinks[], images[], author }
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
### Alkitab
|
|
741
|
+
|
|
742
|
+
```ts
|
|
743
|
+
import { AlkitabScraper } from "wsper";
|
|
744
|
+
const alkitab = new AlkitabScraper();
|
|
745
|
+
|
|
746
|
+
await alkitab.search("yohanes 3:16")
|
|
747
|
+
// → WsperResponse<AlkitabVerse[]>
|
|
748
|
+
// data[]: { reference, text, book, chapter, verse }
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
753
|
+
### BMKG
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
import { BMKGScraper } from "wsper";
|
|
757
|
+
const bmkg = new BMKGScraper();
|
|
758
|
+
|
|
759
|
+
await bmkg.getAutogempa()
|
|
760
|
+
// -> WsperResponse<BMKGEarthquakeFeed>
|
|
761
|
+
|
|
762
|
+
await bmkg.getGempaDirasakan()
|
|
763
|
+
// -> WsperResponse<BMKGEarthquakeFeed>
|
|
764
|
+
|
|
765
|
+
await bmkg.getCuacaNowcasting(0, 100)
|
|
766
|
+
// -> WsperResponse<BMKGNowcasting>
|
|
767
|
+
|
|
768
|
+
await bmkg.getPrakiraanCuaca("31")
|
|
769
|
+
// -> WsperResponse<BMKGForecast>
|
|
770
|
+
|
|
771
|
+
await bmkg.downloadShakemap("kode-shakemap", "./downloads")
|
|
772
|
+
// -> WsperResponse<BMKGShakemapDownload>
|
|
773
|
+
|
|
774
|
+
bmkg.getKodeProvinsi()
|
|
775
|
+
// -> WsperResponse<Record<string, string>>
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
---
|
|
779
|
+
|
|
780
|
+
### Cuaca
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
import { CuacaScraper } from "wsper";
|
|
784
|
+
const cuaca = new CuacaScraper({
|
|
785
|
+
warningApiKey: process.env.BMKG_WARNING_API_KEY,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
await cuaca.searchLocation("bandung")
|
|
789
|
+
// -> WsperResponse<CuacaSearchResult[]>
|
|
790
|
+
|
|
791
|
+
await cuaca.getWeather("bandung")
|
|
792
|
+
// -> WsperResponse<CuacaResult>
|
|
793
|
+
|
|
794
|
+
await cuaca.getWeatherByCoord(-6.9175, 107.6191)
|
|
795
|
+
// -> WsperResponse<CuacaResult>
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
### Drakor
|
|
801
|
+
|
|
802
|
+
```ts
|
|
803
|
+
import { DrakorScraper } from "wsper";
|
|
804
|
+
const drakor = new DrakorScraper();
|
|
805
|
+
|
|
806
|
+
await drakor.search("undercover")
|
|
807
|
+
// -> WsperResponse<DrakorList>
|
|
808
|
+
|
|
809
|
+
await drakor.detail("undercover-miss-hong")
|
|
810
|
+
// -> WsperResponse<DrakorDetail>
|
|
811
|
+
|
|
812
|
+
await drakor.ongoing()
|
|
813
|
+
// -> WsperResponse<DrakorList>
|
|
814
|
+
|
|
815
|
+
await drakor.getAll(1)
|
|
816
|
+
// -> WsperResponse<DrakorList>
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
### Dramabox
|
|
822
|
+
|
|
823
|
+
```ts
|
|
824
|
+
import { DramaboxScraper } from "wsper";
|
|
825
|
+
const dramabox = new DramaboxScraper();
|
|
826
|
+
|
|
827
|
+
await dramabox.search("romance")
|
|
828
|
+
// -> WsperResponse<DramaboxResult>
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
### SakuraNovel
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
import { SakuraNovelScraper } from "wsper";
|
|
837
|
+
const sakura = new SakuraNovelScraper();
|
|
838
|
+
|
|
839
|
+
await sakura.search("isekai")
|
|
840
|
+
// -> WsperResponse<SakuraNovelSearchItem[]>
|
|
841
|
+
|
|
842
|
+
await sakura.getDetail("/series/example/")
|
|
843
|
+
// -> WsperResponse<SakuraNovelDetail>
|
|
844
|
+
|
|
845
|
+
await sakura.getChapter("/chapter/example-1/")
|
|
846
|
+
// -> WsperResponse<SakuraNovelChapterContent>
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
### Uguu
|
|
852
|
+
|
|
853
|
+
```ts
|
|
854
|
+
import { UguuScraper } from "wsper";
|
|
855
|
+
const uguu = new UguuScraper();
|
|
856
|
+
|
|
857
|
+
await uguu.upload(Buffer.from("hello"), "hello.txt")
|
|
858
|
+
// -> WsperResponse<UguuUploadResult>
|
|
859
|
+
// data: { url }
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
### HokInfo
|
|
865
|
+
|
|
866
|
+
```ts
|
|
867
|
+
import { HokInfoScraper } from "wsper";
|
|
868
|
+
const hok = new HokInfoScraper();
|
|
869
|
+
|
|
870
|
+
await hok.getCharacter("Sun Wukong")
|
|
871
|
+
// -> WsperResponse<HokInfoResult>
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
---
|
|
875
|
+
|
|
876
|
+
### IkiruManga
|
|
877
|
+
|
|
878
|
+
```ts
|
|
879
|
+
import { IkiruMangaScraper } from "wsper";
|
|
880
|
+
const ikiru = new IkiruMangaScraper();
|
|
881
|
+
|
|
882
|
+
await ikiru.search("solo leveling")
|
|
883
|
+
// -> WsperResponse<IkiruMangaResult>
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
---
|
|
887
|
+
|
|
888
|
+
### Wallpaper
|
|
889
|
+
|
|
890
|
+
```ts
|
|
891
|
+
import { WallpaperScraper } from "wsper";
|
|
892
|
+
const wallpaper = new WallpaperScraper();
|
|
893
|
+
|
|
894
|
+
await wallpaper.search("blue sky")
|
|
895
|
+
// -> WsperResponse<WallpaperResult>
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
---
|
|
899
|
+
|
|
900
|
+
### WwChar
|
|
901
|
+
|
|
902
|
+
```ts
|
|
903
|
+
import { WwCharScraper } from "wsper";
|
|
904
|
+
const ww = new WwCharScraper();
|
|
905
|
+
|
|
906
|
+
await ww.getCharacter("Jin Hsi")
|
|
907
|
+
// -> WsperResponse<WwCharResult>
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
### Videy
|
|
913
|
+
|
|
914
|
+
```ts
|
|
915
|
+
import { VideyScraper } from "wsper";
|
|
916
|
+
const videy = new VideyScraper();
|
|
917
|
+
|
|
918
|
+
await videy.upload("./video.mp4")
|
|
919
|
+
// -> WsperResponse<VideyUploadResult>
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
---
|
|
923
|
+
|
|
924
|
+
### OCR
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
import { OcrScraper } from "wsper";
|
|
928
|
+
const ocr = new OcrScraper();
|
|
929
|
+
|
|
930
|
+
await ocr.scan(imageBuffer)
|
|
931
|
+
// -> WsperResponse<OcrScanResult>
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
### Webp2Mp4
|
|
937
|
+
|
|
938
|
+
```ts
|
|
939
|
+
import { Webp2Mp4Scraper } from "wsper";
|
|
940
|
+
const converter = new Webp2Mp4Scraper();
|
|
941
|
+
|
|
942
|
+
await converter.toMp4(webpBuffer)
|
|
943
|
+
// -> WsperResponse<Webp2Mp4Result>
|
|
944
|
+
|
|
945
|
+
await converter.toPng("https://example.com/image.webp")
|
|
946
|
+
// -> WsperResponse<Webp2Mp4Result>
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
### MConverter
|
|
952
|
+
|
|
953
|
+
```ts
|
|
954
|
+
import { MConverterScraper } from "wsper";
|
|
955
|
+
const converter = new MConverterScraper();
|
|
956
|
+
|
|
957
|
+
await converter.getTargets("image.webp")
|
|
958
|
+
// -> WsperResponse<MConverterTargetsResult>
|
|
959
|
+
|
|
960
|
+
await converter.convertBuffer(imageBuffer, "image.webp", "png")
|
|
961
|
+
// -> WsperResponse<MConverterConvertResult>
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
### HtmlToJpg
|
|
967
|
+
|
|
968
|
+
```ts
|
|
969
|
+
import { HtmlToJpgScraper } from "wsper";
|
|
970
|
+
const converter = new HtmlToJpgScraper();
|
|
971
|
+
|
|
972
|
+
await converter.convertBuffer(htmlBuffer, "page.html")
|
|
973
|
+
// -> WsperResponse<HtmlToJpgResult>
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
---
|
|
977
|
+
|
|
978
|
+
### Stalk
|
|
979
|
+
|
|
980
|
+
```ts
|
|
981
|
+
import { StalkScraper } from "wsper";
|
|
982
|
+
const stalk = new StalkScraper();
|
|
983
|
+
|
|
984
|
+
await stalk.getNpmPackage("wsper")
|
|
985
|
+
// -> WsperResponse<NpmPackage>
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
### ModAndroid
|
|
991
|
+
|
|
992
|
+
```ts
|
|
993
|
+
import { ModAndroidScraper } from "wsper";
|
|
994
|
+
const modAndroid = new ModAndroidScraper();
|
|
995
|
+
|
|
996
|
+
await modAndroid.aptoide("maps")
|
|
997
|
+
// -> WsperResponse<AptoideResult>
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
---
|
|
1001
|
+
|
|
1002
|
+
### Upscaler
|
|
1003
|
+
|
|
1004
|
+
```ts
|
|
1005
|
+
import { UpscalerScraper } from "wsper";
|
|
1006
|
+
const upscaler = new UpscalerScraper();
|
|
1007
|
+
|
|
1008
|
+
await upscaler.upscaleBuffer(imageBuffer, "image/jpeg")
|
|
1009
|
+
// -> WsperResponse<UpscalerResult>
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
### Image
|
|
1015
|
+
|
|
1016
|
+
```ts
|
|
1017
|
+
import { ImageScraper } from "wsper";
|
|
1018
|
+
const image = new ImageScraper();
|
|
1019
|
+
|
|
1020
|
+
await image.safebooru("blue_sky", 0, 10)
|
|
1021
|
+
// -> WsperResponse<ImageSearchResult>
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
---
|
|
1025
|
+
|
|
1026
|
+
### ImgUpscaler
|
|
1027
|
+
|
|
1028
|
+
```ts
|
|
1029
|
+
import { ImgUpscalerScraper } from "wsper";
|
|
1030
|
+
const upscaler = new ImgUpscalerScraper();
|
|
1031
|
+
|
|
1032
|
+
await upscaler.upscaleBuffer(imageBuffer, "image.jpg", 2)
|
|
1033
|
+
// -> WsperResponse<ImgUpscalerResult>
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
---
|
|
1037
|
+
|
|
1038
|
+
### Faceswap
|
|
1039
|
+
|
|
1040
|
+
```ts
|
|
1041
|
+
import { FaceswapScraper } from "wsper";
|
|
1042
|
+
const faceswap = new FaceswapScraper();
|
|
1043
|
+
|
|
1044
|
+
await faceswap.process(sourceImageBuffer, targetImageBuffer)
|
|
1045
|
+
// -> WsperResponse<FaceswapResult>
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1050
|
+
### PhotoAi
|
|
1051
|
+
|
|
1052
|
+
```ts
|
|
1053
|
+
import { PhotoAiScraper } from "wsper";
|
|
1054
|
+
const photoAi = new PhotoAiScraper();
|
|
1055
|
+
|
|
1056
|
+
await photoAi.uploadBuffer(imageBuffer, "image.jpg")
|
|
1057
|
+
// -> WsperResponse<PhotoAiUploadResult>
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
---
|
|
1061
|
+
|
|
1062
|
+
### Lyrics
|
|
1063
|
+
|
|
1064
|
+
```ts
|
|
1065
|
+
import { LyricsScraper } from "wsper";
|
|
1066
|
+
const lyrics = new LyricsScraper();
|
|
1067
|
+
|
|
1068
|
+
await lyrics.search("brat charli xcx")
|
|
1069
|
+
// → WsperResponse<LyricsResult>
|
|
1070
|
+
// data: { title, artist, lyrics, sourceUrl }
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
---
|
|
1074
|
+
|
|
1075
|
+
### Resep
|
|
1076
|
+
|
|
1077
|
+
```ts
|
|
1078
|
+
import { ResepScraper } from "wsper";
|
|
1079
|
+
const resep = new ResepScraper();
|
|
1080
|
+
|
|
1081
|
+
await resep.search("nasi goreng")
|
|
1082
|
+
// → WsperResponse<ResepItem[]>
|
|
1083
|
+
// data[]: { title, url, imageUrl, author, servings, cookTime, ingredients[], steps[] }
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
### Top Anime
|
|
1089
|
+
|
|
1090
|
+
```ts
|
|
1091
|
+
import { TopAnimeScraper } from "wsper";
|
|
1092
|
+
const anime = new TopAnimeScraper();
|
|
1093
|
+
|
|
1094
|
+
await anime.getTopAnime(25)
|
|
1095
|
+
// → WsperResponse<TopAnimeItem[]>
|
|
1096
|
+
// data[]: { rank, title, url, imageUrl, score, type, episodes, members }
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
---
|
|
1100
|
+
|
|
1101
|
+
### Anime Quote
|
|
1102
|
+
|
|
1103
|
+
```ts
|
|
1104
|
+
import { AnimeQuoteScraper } from "wsper";
|
|
1105
|
+
const quote = new AnimeQuoteScraper();
|
|
1106
|
+
|
|
1107
|
+
await quote.getRandom()
|
|
1108
|
+
// → WsperResponse<AnimeQuoteItem>
|
|
1109
|
+
// data: { quote, character, anime, imageUrl }
|
|
1110
|
+
```
|
|
1111
|
+
|
|
1112
|
+
---
|
|
1113
|
+
|
|
1114
|
+
### Anime Random
|
|
1115
|
+
|
|
1116
|
+
```ts
|
|
1117
|
+
import { AnimeRandomScraper, ANIME_CHARACTERS } from "wsper";
|
|
1118
|
+
const random = new AnimeRandomScraper();
|
|
1119
|
+
|
|
1120
|
+
await random.getImage("nezuko")
|
|
1121
|
+
// → WsperResponse<AnimeRandomResult>
|
|
1122
|
+
// data: { character, imageUrl, sourceUrl }
|
|
1123
|
+
|
|
1124
|
+
console.log(ANIME_CHARACTERS);
|
|
1125
|
+
// ["nezuko", "zero-two", "rem", "miku", ...]
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
---
|
|
1129
|
+
|
|
1130
|
+
## Brat Generator
|
|
1131
|
+
|
|
1132
|
+
Generator konten visual bergaya **brat** — teks besar di atas background minimalis. Bisa diekspor sebagai gambar statis, GIF animasi kata per kata, atau video MP4/WebM.
|
|
1133
|
+
|
|
1134
|
+
```ts
|
|
1135
|
+
import { BratGenerator, generateBrat, bratGenerator } from "wsper";
|
|
1136
|
+
|
|
1137
|
+
const brat = new BratGenerator();
|
|
1138
|
+
|
|
1139
|
+
// Atau gunakan singleton global:
|
|
1140
|
+
// bratGenerator.generate(...)
|
|
1141
|
+
|
|
1142
|
+
// Atau shorthand function:
|
|
1143
|
+
// await generateBrat(config)
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
---
|
|
1147
|
+
|
|
1148
|
+
### generate — Image
|
|
1149
|
+
|
|
1150
|
+
```ts
|
|
1151
|
+
const result = await brat.generate({
|
|
1152
|
+
canvas: { preset: "1:1" },
|
|
1153
|
+
background: { type: "solid", color: "#ffffff" },
|
|
1154
|
+
text: {
|
|
1155
|
+
value: "kamu pas kecil pernah nelen magnet ya? menarik banget soalnya",
|
|
1156
|
+
align: "justify",
|
|
1157
|
+
},
|
|
1158
|
+
output: { type: "image", format: "png", path: "./brat.png" },
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1161
|
+
// → BratResult
|
|
1162
|
+
interface BratResult {
|
|
1163
|
+
buffer: Buffer; // konten file dalam memory
|
|
1164
|
+
format: string; // "png" | "jpg" | "webp"
|
|
1165
|
+
path?: string; // path file jika output.path diisi
|
|
1166
|
+
}
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
**Format yang didukung:** `png`, `jpg`, `webp`
|
|
1170
|
+
|
|
1171
|
+
---
|
|
1172
|
+
|
|
1173
|
+
### generate — GIF
|
|
1174
|
+
|
|
1175
|
+
```ts
|
|
1176
|
+
const result = await brat.generate({
|
|
1177
|
+
text: {
|
|
1178
|
+
value: "charli xcx is so brat i can't even",
|
|
1179
|
+
align: "justify",
|
|
1180
|
+
},
|
|
1181
|
+
background: { type: "solid", color: "#8ace00" },
|
|
1182
|
+
animation: {
|
|
1183
|
+
enabled: true,
|
|
1184
|
+
direction: "left-to-right",
|
|
1185
|
+
mode: "word",
|
|
1186
|
+
fps: 12,
|
|
1187
|
+
textSpeed: 150, // ms per kata — prioritas di atas duration
|
|
1188
|
+
duration: 3000, // ms total — fallback jika textSpeed tidak diset
|
|
1189
|
+
},
|
|
1190
|
+
output: { type: "gif", path: "./brat.gif" },
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
// result.buffer → Buffer GIF
|
|
1194
|
+
// result.format → "gif"
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
---
|
|
1198
|
+
|
|
1199
|
+
### generate — Video
|
|
1200
|
+
|
|
1201
|
+
```ts
|
|
1202
|
+
const result = await brat.generate({
|
|
1203
|
+
text: { value: "brat video" },
|
|
1204
|
+
animation: { enabled: true, mode: "word", fps: 30 },
|
|
1205
|
+
output: {
|
|
1206
|
+
type: "video",
|
|
1207
|
+
format: "mp4", // "mp4" | "webm"
|
|
1208
|
+
codec: "libx264", // optional
|
|
1209
|
+
path: "./brat.mp4",
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
// result.buffer → Buffer MP4/WebM
|
|
1214
|
+
// result.format → "mp4" | "webm"
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
> Membutuhkan FFmpeg (`C:\ffmpeg\ffmpeg.exe` atau di PATH).
|
|
1218
|
+
|
|
1219
|
+
---
|
|
1220
|
+
|
|
1221
|
+
### withPreset
|
|
1222
|
+
|
|
1223
|
+
Terapkan style preset ke config:
|
|
1224
|
+
|
|
1225
|
+
```ts
|
|
1226
|
+
const cfg = brat.withPreset("brat-green", {
|
|
1227
|
+
text: { value: "brat summer" },
|
|
1228
|
+
output: { type: "image", format: "png", path: "./out.png" },
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
await brat.generate(cfg);
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
| Preset | Background | Text Color | Text Blur |
|
|
1235
|
+
|---|---|---|---|
|
|
1236
|
+
| `classic` | `#ffffff` | `#111111` | 0 |
|
|
1237
|
+
| `blurred` | `#ffffff` | `#111111` | 2 |
|
|
1238
|
+
| `brat-green` | `#8ace00` | `#111111` | 1 |
|
|
1239
|
+
| `clean` | `#f5f5f5` | `#000000` | 0 |
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
### imageToSticker
|
|
1244
|
+
|
|
1245
|
+
Konversi gambar apa pun menjadi sticker format Telegram/WhatsApp (512×512 WebP):
|
|
1246
|
+
|
|
1247
|
+
```ts
|
|
1248
|
+
// Dari file path
|
|
1249
|
+
const buf = await brat.imageToSticker("./photo.jpg", {
|
|
1250
|
+
size: 512, // ukuran sticker px (default 512)
|
|
1251
|
+
format: "webp", // "webp" | "png"
|
|
1252
|
+
quality: 90, // kualitas WebP 1-100
|
|
1253
|
+
top: "BRAT", // caption atas (otomatis uppercase)
|
|
1254
|
+
bottom: "2025", // caption bawah
|
|
1255
|
+
color: "#ffffff",
|
|
1256
|
+
strokeColor: "#000000",
|
|
1257
|
+
});
|
|
1258
|
+
// → Buffer WebP/PNG
|
|
1259
|
+
|
|
1260
|
+
// Dari Buffer
|
|
1261
|
+
const buf2 = await brat.imageToSticker(pngBuffer, { size: 512, format: "webp" });
|
|
1262
|
+
|
|
1263
|
+
// Langsung simpan ke file
|
|
1264
|
+
await brat.saveSticker("./photo.jpg", "./sticker.webp", {
|
|
1265
|
+
size: 512, top: "TOP", bottom: "BOTTOM",
|
|
1266
|
+
});
|
|
1267
|
+
// → void
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
---
|
|
1271
|
+
|
|
1272
|
+
### MP4 ↔ GIF Conversion
|
|
1273
|
+
|
|
1274
|
+
#### GIF → MP4
|
|
1275
|
+
|
|
1276
|
+
```ts
|
|
1277
|
+
const mp4Buffer = await brat.gifToMp4("./brat.gif", "./brat.mp4", {
|
|
1278
|
+
fps: 24,
|
|
1279
|
+
width: 720, // optional resize
|
|
1280
|
+
});
|
|
1281
|
+
// → Buffer MP4
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
#### GIF → WebM
|
|
1285
|
+
|
|
1286
|
+
```ts
|
|
1287
|
+
const webmBuffer = await brat.gifToWebm("./brat.gif", "./brat.webm", { fps: 24 });
|
|
1288
|
+
// → Buffer WebM
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
#### MP4 → GIF
|
|
1292
|
+
|
|
1293
|
+
```ts
|
|
1294
|
+
const gifBuffer = await brat.mp4ToGif("./clip.mp4", "./clip.gif", {
|
|
1295
|
+
fps: 10, // frame rate GIF (default 10)
|
|
1296
|
+
width: 480, // lebar output GIF (default 480)
|
|
1297
|
+
loop: 0, // 0 = infinite loop
|
|
1298
|
+
startTime: 5, // mulai dari detik ke-5
|
|
1299
|
+
duration: 3, // ambil 3 detik saja
|
|
1300
|
+
});
|
|
1301
|
+
// → Buffer GIF (two-pass palette encoding untuk kualitas warna lebih baik)
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
---
|
|
1305
|
+
|
|
1306
|
+
### convertImage
|
|
1307
|
+
|
|
1308
|
+
Konversi format gambar tanpa teks/canvas:
|
|
1309
|
+
|
|
1310
|
+
```ts
|
|
1311
|
+
const webpBuf = await brat.convertImage("./photo.png", "webp", "./photo.webp");
|
|
1312
|
+
const jpgBuf = await brat.convertImage(pngBuffer, "jpg");
|
|
1313
|
+
// → Buffer dalam format target
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
---
|
|
1317
|
+
|
|
1318
|
+
### Background Config
|
|
1319
|
+
|
|
1320
|
+
```ts
|
|
1321
|
+
// Solid color
|
|
1322
|
+
background: { type: "solid", color: "#8ace00" }
|
|
1323
|
+
background: { type: "solid", color: "white" }
|
|
1324
|
+
|
|
1325
|
+
// Linear gradient
|
|
1326
|
+
background: {
|
|
1327
|
+
type: "linear-gradient",
|
|
1328
|
+
colors: ["#b7ff00", "#ffffff"],
|
|
1329
|
+
direction: "to-bottom-right",
|
|
1330
|
+
// "to-top" | "to-bottom" | "to-left" | "to-right"
|
|
1331
|
+
// "to-bottom-right" | "to-bottom-left" | "to-top-right" | "to-top-left"
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// Radial gradient
|
|
1335
|
+
background: {
|
|
1336
|
+
type: "radial-gradient",
|
|
1337
|
+
colors: ["#ffffff", "#d9d9d9"],
|
|
1338
|
+
position: "center",
|
|
1339
|
+
// "center" | "top" | "bottom" | "left" | "right"
|
|
1340
|
+
// "top-left" | "top-right" | "bottom-left" | "bottom-right"
|
|
1341
|
+
// { x: 50, y: 50 } ← posisi custom dalam persen (0-100)
|
|
1342
|
+
}
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
### Text Config
|
|
1348
|
+
|
|
1349
|
+
```ts
|
|
1350
|
+
text: {
|
|
1351
|
+
value: "teks yang ingin ditampilkan",
|
|
1352
|
+
|
|
1353
|
+
align: "justify", // "left" | "right" | "center" | "justify"
|
|
1354
|
+
|
|
1355
|
+
fontSize: "auto", // atau angka: 48 — "auto" menyesuaikan ukuran canvas
|
|
1356
|
+
minFontSize: 20, // batas bawah auto fit (default 20)
|
|
1357
|
+
maxFontSize: 180, // batas atas auto fit (default 180)
|
|
1358
|
+
|
|
1359
|
+
fontFamily: '"Arial Black", sans-serif',
|
|
1360
|
+
fontWeight: 900, // atau string: "bold"
|
|
1361
|
+
|
|
1362
|
+
lineHeight: 1.05, // default 1.05 — rapat khas brat style
|
|
1363
|
+
color: "#111111",
|
|
1364
|
+
|
|
1365
|
+
blur: 2, // blur teks 0–8 (0 = tidak ada, default 0)
|
|
1366
|
+
}
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
---
|
|
1370
|
+
|
|
1371
|
+
### Canvas Config
|
|
1372
|
+
|
|
1373
|
+
```ts
|
|
1374
|
+
// Preset aspect ratio
|
|
1375
|
+
canvas: { preset: "1:1" } // 1080 × 1080
|
|
1376
|
+
canvas: { preset: "9:16" } // 1080 × 1920 (portrait / story)
|
|
1377
|
+
canvas: { preset: "16:9" } // 1920 × 1080 (landscape / widescreen)
|
|
1378
|
+
|
|
1379
|
+
// Preset nama
|
|
1380
|
+
canvas: { preset: "square" } // 1024 × 1024
|
|
1381
|
+
canvas: { preset: "story" } // 1080 × 1920
|
|
1382
|
+
canvas: { preset: "post" } // 1080 × 1350
|
|
1383
|
+
canvas: { preset: "landscape" } // 1920 × 1080
|
|
1384
|
+
|
|
1385
|
+
// Ukuran manual
|
|
1386
|
+
canvas: { width: 1200, height: 800 }
|
|
1387
|
+
|
|
1388
|
+
// Aspect ratio + satu dimensi → dimensi lain dihitung otomatis
|
|
1389
|
+
canvas: { aspectRatio: "4:3", width: 1200 } // → height 900
|
|
1390
|
+
canvas: { aspectRatio: "16:9", height: 720 } // → width 1280
|
|
1391
|
+
canvas: { aspectRatio: "4:3" } // → 1080 × 810 (base 1080)
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
**Padding:**
|
|
1395
|
+
|
|
1396
|
+
```ts
|
|
1397
|
+
layout: { padding: 64 }
|
|
1398
|
+
layout: { padding: { top: 80, right: 64, bottom: 80, left: 64 } }
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
---
|
|
1402
|
+
|
|
1403
|
+
### Animation Config
|
|
1404
|
+
|
|
1405
|
+
```ts
|
|
1406
|
+
animation: {
|
|
1407
|
+
enabled: true,
|
|
1408
|
+
direction: "left-to-right", // "left-to-right" | "right-to-left"
|
|
1409
|
+
mode: "word", // "word" | "line" | "character"
|
|
1410
|
+
fps: 12,
|
|
1411
|
+
|
|
1412
|
+
// Pilih satu untuk kecepatan:
|
|
1413
|
+
textSpeed: 150, // ms per step — prioritas utama
|
|
1414
|
+
duration: 3000, // ms total dibagi rata ke semua step — fallback
|
|
1415
|
+
}
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
| Mode | Deskripsi |
|
|
1419
|
+
|---|---|
|
|
1420
|
+
| `word` | Kata muncul satu per satu — cocok untuk brat GIF |
|
|
1421
|
+
| `line` | Baris muncul satu per satu — lebih dramatis |
|
|
1422
|
+
| `character` | Huruf muncul satu per satu — paling smooth |
|
|
1423
|
+
|
|
1424
|
+
---
|
|
1425
|
+
|
|
1426
|
+
### Output Config
|
|
1427
|
+
|
|
1428
|
+
```ts
|
|
1429
|
+
// Image
|
|
1430
|
+
output: { type: "image", format: "png", path: "./out.png" }
|
|
1431
|
+
output: { type: "image", format: "jpg" }
|
|
1432
|
+
output: { type: "image", format: "webp" }
|
|
1433
|
+
|
|
1434
|
+
// GIF
|
|
1435
|
+
output: { type: "gif", path: "./out.gif" }
|
|
1436
|
+
|
|
1437
|
+
// Video
|
|
1438
|
+
output: { type: "video", format: "mp4", path: "./out.mp4" }
|
|
1439
|
+
output: { type: "video", format: "webm", path: "./out.webm" }
|
|
1440
|
+
output: { type: "video", format: "mp4", codec: "libx264", path: "./out.mp4" }
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
> `path` opsional. Jika tidak diisi, file tidak disimpan ke disk — hanya `buffer` yang dikembalikan di `BratResult`.
|
|
1444
|
+
|
|
1445
|
+
---
|
|
1446
|
+
|
|
1447
|
+
## Chart Image Generator
|
|
1448
|
+
|
|
1449
|
+
Generator chart analytics berbasis canvas. Default-nya memakai model poster vintage dari `analytics-stat-image`, tetapi tersedia beberapa model dan bisa di-override per render.
|
|
1450
|
+
|
|
1451
|
+
```ts
|
|
1452
|
+
import {
|
|
1453
|
+
ChartGenerator,
|
|
1454
|
+
generateAnalyticsStatsImage,
|
|
1455
|
+
getAnalyticsChartModels,
|
|
1456
|
+
} from "wsper";
|
|
1457
|
+
|
|
1458
|
+
const chart = new ChartGenerator();
|
|
1459
|
+
|
|
1460
|
+
await chart.generateStatsImage({
|
|
1461
|
+
model: "modern-dashboard",
|
|
1462
|
+
title: "Group Analytics Report",
|
|
1463
|
+
subtitle: "Monthly and weekly usage overview",
|
|
1464
|
+
monthly: [
|
|
1465
|
+
{ label: "JAN", group: 1200, user: 810 },
|
|
1466
|
+
{ label: "FEB", group: 980, user: 760 },
|
|
1467
|
+
{ label: "MAR", group: 1600, user: 920 },
|
|
1468
|
+
],
|
|
1469
|
+
weekly: [
|
|
1470
|
+
{ label: "MON", group: 320, user: 120 },
|
|
1471
|
+
{ label: "TUE", group: 210, user: 90 },
|
|
1472
|
+
{ label: "WED", group: 450, user: 170 },
|
|
1473
|
+
],
|
|
1474
|
+
output: "./analytics.png",
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
await generateAnalyticsStatsImage({ model: "compact-card" });
|
|
1478
|
+
|
|
1479
|
+
console.log(getAnalyticsChartModels());
|
|
1480
|
+
```
|
|
1481
|
+
|
|
1482
|
+
Model bawaan:
|
|
1483
|
+
|
|
1484
|
+
| Model | Layout | Cocok untuk |
|
|
1485
|
+
|---|---|---|
|
|
1486
|
+
| `vintage-poster` | poster statistik vintage | report visual penuh |
|
|
1487
|
+
| `modern-dashboard` | dashboard KPI | ringkasan operasional |
|
|
1488
|
+
| `minimal-report` | report bersih | dokumen atau lampiran |
|
|
1489
|
+
| `dark-neon` | dark analytics | preview sosial atau tema gelap |
|
|
1490
|
+
| `compact-card` | kartu ringkas | thumbnail dan summary kecil |
|
|
1491
|
+
|
|
1492
|
+
Kustomisasi fleksibel:
|
|
1493
|
+
|
|
1494
|
+
```ts
|
|
1495
|
+
await generateAnalyticsStatsImage({
|
|
1496
|
+
model: "modern-dashboard",
|
|
1497
|
+
modelOverrides: {
|
|
1498
|
+
monthlyChart: "grouped-bars",
|
|
1499
|
+
weeklyChart: "radial-bars",
|
|
1500
|
+
showAnnotations: true,
|
|
1501
|
+
theme: {
|
|
1502
|
+
background: "#ffffff",
|
|
1503
|
+
groupLine: "#111827",
|
|
1504
|
+
userLine: "#0f766e",
|
|
1505
|
+
},
|
|
1506
|
+
},
|
|
1507
|
+
});
|
|
1508
|
+
```
|
|
1509
|
+
|
|
1510
|
+
`monthly` dan `weekly` opsional. Jika tidak diisi, generator memakai data demo internal agar output tetap valid.
|
|
1511
|
+
|
|
1512
|
+
---
|
|
1513
|
+
|
|
1514
|
+
## Credentials
|
|
1515
|
+
|
|
1516
|
+
Credential tidak dibaca dari `.env`. Dua cara konfigurasi:
|
|
1517
|
+
|
|
1518
|
+
```ts
|
|
1519
|
+
// 1. Tanpa credential → library pakai internal defaults
|
|
1520
|
+
const wsper = new WsperScraper();
|
|
1521
|
+
|
|
1522
|
+
// 2. Custom credential per platform
|
|
1523
|
+
const wsper = new WsperScraper({
|
|
1524
|
+
spotifyCredentials: {
|
|
1525
|
+
clientId: "your-client-id",
|
|
1526
|
+
clientSecret: "your-client-secret",
|
|
1527
|
+
},
|
|
1528
|
+
credentials: {
|
|
1529
|
+
twitter: {
|
|
1530
|
+
cookie: "auth_token=xxx; ct0=yyy",
|
|
1531
|
+
csrfToken: "yyy",
|
|
1532
|
+
},
|
|
1533
|
+
threads: {
|
|
1534
|
+
cookie: "sessionid=xxx",
|
|
1535
|
+
csrfToken: "zzz",
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
});
|
|
1539
|
+
```
|
|
1540
|
+
|
|
1541
|
+
> Jangan pernah commit cookie, token, atau secret ke repository.
|
|
1542
|
+
|
|
1543
|
+
---
|
|
1544
|
+
|
|
1545
|
+
## HTTP & Queue Options
|
|
1546
|
+
|
|
1547
|
+
```ts
|
|
1548
|
+
interface HttpOptions {
|
|
1549
|
+
timeoutMs?: number; // default 30000
|
|
1550
|
+
retries?: number; // default 3
|
|
1551
|
+
retryDelayMs?: number;
|
|
1552
|
+
maxRetryAfterMs?: number;
|
|
1553
|
+
maxRedirects?: number;
|
|
1554
|
+
headers?: Record<string, string>;
|
|
1555
|
+
userAgent?: string;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
interface QueueOptions {
|
|
1559
|
+
concurrency?: number; // default 1
|
|
1560
|
+
intervalMs?: number;
|
|
1561
|
+
intervalCap?: number;
|
|
1562
|
+
minDelayMs?: number;
|
|
1563
|
+
maxDelayMs?: number;
|
|
1564
|
+
}
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
Contoh konfigurasi rate-limit:
|
|
1568
|
+
|
|
1569
|
+
```ts
|
|
1570
|
+
const wsper = new WsperScraper({
|
|
1571
|
+
http: { timeoutMs: 20000, retries: 2 },
|
|
1572
|
+
queue: { concurrency: 1, minDelayMs: 3000, maxDelayMs: 7000 },
|
|
1573
|
+
});
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
---
|
|
1577
|
+
|
|
1578
|
+
## Download Outputs
|
|
1579
|
+
|
|
1580
|
+
File yang didownload disimpan ke direktori yang ditentukan di tiap method:
|
|
1581
|
+
|
|
1582
|
+
```
|
|
1583
|
+
downloads/
|
|
1584
|
+
├── instagram/
|
|
1585
|
+
│ └── charli_xcx/
|
|
1586
|
+
│ ├── post_AbCdEfG_0.mp4
|
|
1587
|
+
│ ├── post_AbCdEfG_1.jpg
|
|
1588
|
+
│ └── metadata.json
|
|
1589
|
+
├── spotify/
|
|
1590
|
+
│ └── track_4iV5W9.mp3
|
|
1591
|
+
├── youtube/
|
|
1592
|
+
│ └── video_dQw4w9.mp4
|
|
1593
|
+
├── threads/
|
|
1594
|
+
│ └── post_CgXxXxX_0.mp4
|
|
1595
|
+
└── brat/
|
|
1596
|
+
├── brat.png
|
|
1597
|
+
├── brat-green.png
|
|
1598
|
+
├── brat.gif
|
|
1599
|
+
├── brat.mp4
|
|
1600
|
+
└── brat.sticker.webp
|
|
1601
|
+
```
|
|
1602
|
+
|
|
1603
|
+
**CLI examples:**
|
|
1604
|
+
|
|
1605
|
+
```bash
|
|
1606
|
+
npx tsx examples/instagram.example.ts <username> 6 <post-url>
|
|
1607
|
+
npx tsx examples/spotify.example.ts <track-url> "" "" "" downloads/spotify
|
|
1608
|
+
npx tsx examples/threads.example.ts <username-or-url> <post-url>
|
|
1609
|
+
npx tsx examples/twitter.example.ts <tweet-url> <username-or-url>
|
|
1610
|
+
npx tsx examples/brat.example.ts
|
|
1611
|
+
```
|
|
1612
|
+
|
|
1613
|
+
## READ THIS INFORMATION
|
|
1614
|
+
|
|
1615
|
+
baca rek!!
|
|
1616
|
+
kalo ada bug gabung aja ke trus bilang ke ke yang buat lib
|