wsper-js 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +478 -1415
- package/dist/index.d.ts +23 -4
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,1616 +1,679 @@
|
|
|
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
1
|
|
|
79
|
-
|
|
80
|
-
|
|
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` |
|
|
2
|
+
<h1 align="center">
|
|
3
|
+
<img alt="ShikanokoBail banner" src="https://i.pinimg.com/736x/0c/ff/62/0cff624a04a81495f4b8e69bcedd34aa.jpg" width="100%"/>
|
|
4
|
+
</h1>
|
|
89
5
|
|
|
90
|
-
|
|
6
|
+
<div align="center">
|
|
91
7
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/wsper-js)
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
</div>
|
|
96
14
|
|
|
97
|
-
|
|
98
|
-
|
|
15
|
+
# wsper-js
|
|
16
|
+
`wsper-js` is a TypeScript-first scraper toolkit for building reusable, typed, and testable scrapers across multiple public platforms. It provides platform-specific scraper classes, shared HTTP and queue primitives, safe downloader utilities, parser helpers, media utilities, and runnable examples for controlled and ethical data access.
|
|
99
17
|
|
|
100
|
-
|
|
101
|
-
const track = await wsper.spotify.getTrack("https://open.spotify.com/track/...");
|
|
102
|
-
const video = await wsper.youtube.getVideo("https://youtu.be/...");
|
|
18
|
+
The package is ESM-only and targets Node.js 18 or newer.
|
|
103
19
|
|
|
104
|
-
|
|
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
|
-
```
|
|
20
|
+
## Features
|
|
183
21
|
|
|
184
|
-
|
|
22
|
+
- Modular scraper architecture under `src/scrapers/`.
|
|
23
|
+
- TypeScript strict mode with exported response, option, and scraper data types.
|
|
24
|
+
- Standard `WsperResponse<T>` shape for scraper results.
|
|
25
|
+
- Shared HTTP client with timeouts, bounded retries, redirects, URL safety checks, browser-compatible public headers, and optional request queue pacing.
|
|
26
|
+
- Unit-tested scraper behavior and parser behavior.
|
|
27
|
+
- `examples/alllexamp.ts` runner for direct scraper demos and subprocess example checks.
|
|
28
|
+
- Local mock server support for testing WordPress-style endpoints, AI tools, and Fandom API responses without depending on live external services.
|
|
29
|
+
- Public API integrations where available, including LRCLIB, Wallhaven, Fandom MediaWiki, Spotify Web API, BMKG, npm registry, and BiliBili APIs.
|
|
30
|
+
- Cookie or credential support for platforms that legitimately require authenticated sessions, including Instagram, Twitter/X, Threads, Pinterest, BiliBili, and Spotify credentials.
|
|
31
|
+
- Additional modules for downloads, Brat image/GIF/video generation, and analytics chart image generation.
|
|
185
32
|
|
|
186
|
-
|
|
187
|
-
const res = await wsper.instagram.getProfile("username");
|
|
33
|
+
## Installation
|
|
188
34
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log(res.meta.durationMs, "ms");
|
|
192
|
-
} else {
|
|
193
|
-
console.error(res.error?.code, res.error?.message);
|
|
194
|
-
}
|
|
35
|
+
```bash
|
|
36
|
+
npm install wsper-js
|
|
195
37
|
```
|
|
196
38
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
## Scrapers
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
### Instagram
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
import { InstagramScraper } from "wsper";
|
|
207
|
-
const ig = new InstagramScraper(options?: InstagramScraperOptions);
|
|
39
|
+
```bash
|
|
40
|
+
pnpm add wsper-js
|
|
208
41
|
```
|
|
209
42
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
await ig.getProfile("charli_xcx")
|
|
43
|
+
```bash
|
|
44
|
+
yarn add wsper-js
|
|
214
45
|
```
|
|
215
46
|
|
|
216
|
-
|
|
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
|
-
```
|
|
47
|
+
Some optional capabilities need external binaries:
|
|
232
48
|
|
|
233
|
-
|
|
49
|
+
| Feature | External tool |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| YouTube and Spotify media enrichment/download helpers | `yt-dlp` |
|
|
52
|
+
| Video export and GIF/MP4 conversion | `ffmpeg` in `PATH` or configured path |
|
|
234
53
|
|
|
235
|
-
|
|
236
|
-
await ig.getFeed("charli_xcx", { count: 12, maxId: "cursor" })
|
|
237
|
-
```
|
|
54
|
+
## Quick Start
|
|
238
55
|
|
|
239
56
|
```ts
|
|
240
|
-
|
|
241
|
-
items: InstagramMediaItem[];
|
|
242
|
-
nextMaxId: string | null;
|
|
243
|
-
hasMore: boolean;
|
|
244
|
-
}
|
|
57
|
+
import { LyricsScraper } from "wsper-js";
|
|
245
58
|
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
}
|
|
59
|
+
const lyrics = new LyricsScraper();
|
|
60
|
+
const result = await lyrics.search("after hours the weeknd");
|
|
259
61
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
mediaType: number;
|
|
263
|
-
thumbnailUrl: string | null;
|
|
264
|
-
videoUrl: string | null;
|
|
62
|
+
if (!result.ok) {
|
|
63
|
+
throw new Error(`${result.error?.code}: ${result.error?.message}`);
|
|
265
64
|
}
|
|
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
65
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
}
|
|
66
|
+
console.log(result.statusCode);
|
|
67
|
+
console.log(result.data?.title);
|
|
68
|
+
console.log(result.data?.lyrics);
|
|
288
69
|
```
|
|
289
70
|
|
|
290
|
-
|
|
71
|
+
You can also use `WsperScraper` for the aggregate social/media scraper entrypoint:
|
|
291
72
|
|
|
292
73
|
```ts
|
|
293
|
-
|
|
294
|
-
postUrl: "https://www.instagram.com/p/AbCdEfG/",
|
|
295
|
-
outputDir: "./downloads/instagram",
|
|
296
|
-
includeMetadata: true,
|
|
297
|
-
})
|
|
298
|
-
```
|
|
74
|
+
import { WsperScraper } from "wsper-js";
|
|
299
75
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
```
|
|
76
|
+
const wsper = new WsperScraper({
|
|
77
|
+
queue: { concurrency: 1, minDelayMs: 500, maxDelayMs: 1500 },
|
|
78
|
+
});
|
|
313
79
|
|
|
314
|
-
|
|
80
|
+
const track = await wsper.spotify.search("never gonna give you up", { limit: 3 });
|
|
81
|
+
const video = await wsper.youtube.getVideo("dQw4w9WgXcQ");
|
|
315
82
|
|
|
316
|
-
|
|
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
|
-
})
|
|
83
|
+
console.log(track.ok, video.ok);
|
|
325
84
|
```
|
|
326
85
|
|
|
327
|
-
|
|
86
|
+
`WsperScraper` currently exposes `spotify`, `twitter`, `threads`, `instagram`, `pinterest`, and `youtube`. Other scrapers are exported as named classes.
|
|
328
87
|
|
|
329
|
-
|
|
88
|
+
## Response Shape
|
|
330
89
|
|
|
331
|
-
|
|
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
|
-
```
|
|
90
|
+
Scraper methods return `WsperResponse<TData>`:
|
|
342
91
|
|
|
343
92
|
```ts
|
|
344
|
-
interface
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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;
|
|
93
|
+
export interface WsperResponse<TData, TMeta extends WsperResponseMeta = WsperResponseMeta> {
|
|
94
|
+
ok: boolean;
|
|
95
|
+
statusCode: number;
|
|
96
|
+
data: TData | null;
|
|
97
|
+
error: {
|
|
98
|
+
code: string;
|
|
99
|
+
message: string;
|
|
100
|
+
details?: Record<string, unknown>;
|
|
101
|
+
} | null;
|
|
102
|
+
meta: TMeta;
|
|
362
103
|
}
|
|
363
104
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
105
|
+
export interface WsperResponseMeta {
|
|
106
|
+
statusCode: number;
|
|
107
|
+
sourceUrl: string;
|
|
108
|
+
fetchedAt: string;
|
|
109
|
+
durationMs: number;
|
|
367
110
|
}
|
|
368
111
|
```
|
|
369
112
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
```ts
|
|
373
|
-
await spotify.getAlbum("https://open.spotify.com/album/...")
|
|
374
|
-
```
|
|
113
|
+
Recommended handling:
|
|
375
114
|
|
|
376
115
|
```ts
|
|
377
|
-
|
|
378
|
-
album: NormalizedSpotifyAlbum;
|
|
379
|
-
tracks: NormalizedSpotifyTrack[];
|
|
380
|
-
}
|
|
116
|
+
const response = await scraper.search("query");
|
|
381
117
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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;
|
|
118
|
+
if (response.ok && response.data !== null) {
|
|
119
|
+
console.log(response.data);
|
|
120
|
+
} else {
|
|
121
|
+
console.error(response.statusCode, response.error?.code, response.error?.message);
|
|
395
122
|
}
|
|
396
123
|
```
|
|
397
124
|
|
|
398
|
-
|
|
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
|
-
```
|
|
125
|
+
## Usage Examples
|
|
409
126
|
|
|
410
|
-
|
|
127
|
+
The examples below use public exports from `src/index.ts` and representative response fields from implementation and tests.
|
|
411
128
|
|
|
412
|
-
|
|
413
|
-
await spotify.downloadPost({
|
|
414
|
-
trackUrl: "https://open.spotify.com/track/...",
|
|
415
|
-
outputDir: "./downloads/spotify",
|
|
416
|
-
format: "mp3",
|
|
417
|
-
})
|
|
418
|
-
// → WsperResponse<SpotifyDownloadResult>
|
|
419
|
-
```
|
|
129
|
+
### LyricsScraper
|
|
420
130
|
|
|
421
|
-
|
|
131
|
+
Source: LRCLIB JSON API at `https://lrclib.net/api/search`.
|
|
422
132
|
|
|
423
133
|
```ts
|
|
424
|
-
|
|
425
|
-
// → { state: string; url: string }
|
|
134
|
+
import { LyricsScraper } from "wsper-js";
|
|
426
135
|
|
|
427
|
-
|
|
428
|
-
await
|
|
429
|
-
await spotify.getAccessToken() // → string
|
|
430
|
-
spotify.invalidateToken() // → void
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
---
|
|
136
|
+
const scraper = new LyricsScraper();
|
|
137
|
+
const result = await scraper.search("after hours the weeknd");
|
|
434
138
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
```ts
|
|
438
|
-
import { YouTubeScraper } from "wsper";
|
|
439
|
-
const yt = new YouTubeScraper(options?: YouTubeScraperOptions);
|
|
139
|
+
console.log(result.data);
|
|
440
140
|
```
|
|
441
141
|
|
|
442
|
-
|
|
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
|
-
```
|
|
142
|
+
Representative output:
|
|
458
143
|
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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;
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"title": "After Hours",
|
|
147
|
+
"lyrics": "Oh, baby, where are you now?",
|
|
148
|
+
"link": "https://lrclib.net/api/get/98765"
|
|
490
149
|
}
|
|
491
150
|
```
|
|
492
151
|
|
|
493
|
-
|
|
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
|
-
---
|
|
152
|
+
### WallpaperScraper
|
|
723
153
|
|
|
724
|
-
|
|
154
|
+
Source: Wallhaven search API at `https://wallhaven.cc/api/v1/search`.
|
|
725
155
|
|
|
726
156
|
```ts
|
|
727
|
-
import {
|
|
728
|
-
const mc = new McAddonScraper();
|
|
157
|
+
import { WallpaperScraper } from "wsper-js";
|
|
729
158
|
|
|
730
|
-
|
|
731
|
-
|
|
159
|
+
const scraper = new WallpaperScraper();
|
|
160
|
+
const result = await scraper.search("cyberpunk");
|
|
732
161
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
// data: { title, description, downloadLinks[], images[], author }
|
|
162
|
+
console.log(result.data?.total);
|
|
163
|
+
console.log(result.data?.results[0]);
|
|
736
164
|
```
|
|
737
165
|
|
|
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>
|
|
166
|
+
Representative output:
|
|
773
167
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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>
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"total": 1,
|
|
171
|
+
"results": [
|
|
172
|
+
{
|
|
173
|
+
"title": "Wallpaper sky123",
|
|
174
|
+
"resolution": "1920x1080",
|
|
175
|
+
"image": "https://w.wallhaven.cc/full/sky123.jpg",
|
|
176
|
+
"page": "https://wallhaven.cc/w/sky123"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
}
|
|
962
180
|
```
|
|
963
181
|
|
|
964
|
-
|
|
182
|
+
### WwCharScraper
|
|
965
183
|
|
|
966
|
-
|
|
184
|
+
Source: Wuthering Waves Fandom MediaWiki parse API.
|
|
967
185
|
|
|
968
186
|
```ts
|
|
969
|
-
import {
|
|
970
|
-
const converter = new HtmlToJpgScraper();
|
|
971
|
-
|
|
972
|
-
await converter.convertBuffer(htmlBuffer, "page.html")
|
|
973
|
-
// -> WsperResponse<HtmlToJpgResult>
|
|
974
|
-
```
|
|
187
|
+
import { WwCharScraper } from "wsper-js";
|
|
975
188
|
|
|
976
|
-
|
|
189
|
+
const scraper = new WwCharScraper();
|
|
190
|
+
const result = await scraper.getCharacter("Jin Hsi");
|
|
977
191
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
```ts
|
|
981
|
-
import { StalkScraper } from "wsper";
|
|
982
|
-
const stalk = new StalkScraper();
|
|
983
|
-
|
|
984
|
-
await stalk.getNpmPackage("wsper")
|
|
985
|
-
// -> WsperResponse<NpmPackage>
|
|
192
|
+
console.log(result.data);
|
|
986
193
|
```
|
|
987
194
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
### ModAndroid
|
|
195
|
+
Representative output:
|
|
991
196
|
|
|
992
|
-
```
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"title": "Jiyan",
|
|
200
|
+
"slug": "Jin_Hsi",
|
|
201
|
+
"url": "https://wutheringwaves.fandom.com/wiki/Jin_Hsi",
|
|
202
|
+
"bio": "Bio karakter.",
|
|
203
|
+
"profile": {},
|
|
204
|
+
"images": []
|
|
205
|
+
}
|
|
998
206
|
```
|
|
999
207
|
|
|
1000
|
-
|
|
208
|
+
### HokInfoScraper
|
|
1001
209
|
|
|
1002
|
-
|
|
210
|
+
Source: Honor of Kings Fandom MediaWiki parse API.
|
|
1003
211
|
|
|
1004
212
|
```ts
|
|
1005
|
-
import {
|
|
1006
|
-
const upscaler = new UpscalerScraper();
|
|
213
|
+
import { HokInfoScraper } from "wsper-js";
|
|
1007
214
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
```
|
|
215
|
+
const scraper = new HokInfoScraper();
|
|
216
|
+
const result = await scraper.getCharacter("Angela");
|
|
1011
217
|
|
|
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>
|
|
218
|
+
console.log(result.data?.profile);
|
|
1022
219
|
```
|
|
1023
220
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
### ImgUpscaler
|
|
1027
|
-
|
|
1028
|
-
```ts
|
|
1029
|
-
import { ImgUpscalerScraper } from "wsper";
|
|
1030
|
-
const upscaler = new ImgUpscalerScraper();
|
|
221
|
+
Representative output:
|
|
1031
222
|
|
|
1032
|
-
|
|
1033
|
-
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"title": "Sun Wukong",
|
|
226
|
+
"image": null,
|
|
227
|
+
"profile": {
|
|
228
|
+
"Role": "Fighter"
|
|
229
|
+
},
|
|
230
|
+
"bio": "Bio singkat karakter.",
|
|
231
|
+
"skills": [],
|
|
232
|
+
"lore": null,
|
|
233
|
+
"url": "https://honor-of-kings.fandom.com/wiki/Sun%20Wukong"
|
|
234
|
+
}
|
|
1034
235
|
```
|
|
1035
236
|
|
|
1036
|
-
|
|
237
|
+
### CapCutScraper
|
|
1037
238
|
|
|
1038
|
-
|
|
239
|
+
Source: WordPress-style resolver endpoint, defaulting to `https://capdownloader.com/wp-json/aio-dl/video-data/`.
|
|
1039
240
|
|
|
1040
241
|
```ts
|
|
1041
|
-
import {
|
|
1042
|
-
const faceswap = new FaceswapScraper();
|
|
1043
|
-
|
|
1044
|
-
await faceswap.process(sourceImageBuffer, targetImageBuffer)
|
|
1045
|
-
// -> WsperResponse<FaceswapResult>
|
|
1046
|
-
```
|
|
1047
|
-
|
|
1048
|
-
---
|
|
242
|
+
import { CapCutScraper } from "wsper-js";
|
|
1049
243
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
```ts
|
|
1053
|
-
import { PhotoAiScraper } from "wsper";
|
|
1054
|
-
const photoAi = new PhotoAiScraper();
|
|
244
|
+
const scraper = new CapCutScraper();
|
|
245
|
+
const result = await scraper.download("https://www.capcut.com/t/Zs82gHj1a/");
|
|
1055
246
|
|
|
1056
|
-
|
|
1057
|
-
// -> WsperResponse<PhotoAiUploadResult>
|
|
247
|
+
console.log(result.data);
|
|
1058
248
|
```
|
|
1059
249
|
|
|
1060
|
-
|
|
250
|
+
Representative output:
|
|
1061
251
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const lyrics = new LyricsScraper();
|
|
1067
|
-
|
|
1068
|
-
await lyrics.search("brat charli xcx")
|
|
1069
|
-
// → WsperResponse<LyricsResult>
|
|
1070
|
-
// data: { title, artist, lyrics, sourceUrl }
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"videoUrl": "https://cdn.example/video.mp4"
|
|
255
|
+
}
|
|
1071
256
|
```
|
|
1072
257
|
|
|
1073
|
-
|
|
258
|
+
### ImgUpscalerScraper
|
|
1074
259
|
|
|
1075
|
-
|
|
260
|
+
Source: `https://get1.imglarger.com` upload and status endpoints.
|
|
1076
261
|
|
|
1077
262
|
```ts
|
|
1078
|
-
import {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
await resep.search("nasi goreng")
|
|
1082
|
-
// → WsperResponse<ResepItem[]>
|
|
1083
|
-
// data[]: { title, url, imageUrl, author, servings, cookTime, ingredients[], steps[] }
|
|
1084
|
-
```
|
|
263
|
+
import { readFile } from "node:fs/promises";
|
|
264
|
+
import { ImgUpscalerScraper } from "wsper-js";
|
|
1085
265
|
|
|
1086
|
-
|
|
266
|
+
const image = await readFile("./photo.jpg");
|
|
267
|
+
const scraper = new ImgUpscalerScraper();
|
|
268
|
+
const result = await scraper.upscaleBuffer(image, "photo.jpg", 4);
|
|
1087
269
|
|
|
1088
|
-
|
|
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 }
|
|
270
|
+
console.log(result.data);
|
|
1097
271
|
```
|
|
1098
272
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
### Anime Quote
|
|
273
|
+
Representative output:
|
|
1102
274
|
|
|
1103
|
-
```
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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", ...]
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"originalPath": null,
|
|
278
|
+
"outputPath": null,
|
|
279
|
+
"resultUrl": "https://cdn.test/out.jpg",
|
|
280
|
+
"scale": 4
|
|
281
|
+
}
|
|
1126
282
|
```
|
|
1127
283
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
## Brat Generator
|
|
284
|
+
### PhotoAiScraper
|
|
1131
285
|
|
|
1132
|
-
|
|
286
|
+
Source: `https://photoai.imglarger.com`.
|
|
1133
287
|
|
|
1134
288
|
```ts
|
|
1135
|
-
import {
|
|
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
|
-
---
|
|
289
|
+
import { readFile } from "node:fs/promises";
|
|
290
|
+
import { PhotoAiScraper } from "wsper-js";
|
|
1147
291
|
|
|
1148
|
-
|
|
292
|
+
const image = await readFile("./portrait.jpg");
|
|
293
|
+
const scraper = new PhotoAiScraper();
|
|
1149
294
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
|
295
|
+
const upload = await scraper.uploadBuffer(image, "portrait.jpg");
|
|
296
|
+
if (upload.ok && upload.data !== null) {
|
|
297
|
+
const status = await scraper.checkStatus(upload.data.code);
|
|
298
|
+
console.log(status.data);
|
|
1166
299
|
}
|
|
1167
300
|
```
|
|
1168
301
|
|
|
1169
|
-
|
|
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
|
|
302
|
+
Representative output:
|
|
1200
303
|
|
|
1201
|
-
```
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
},
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
// result.buffer → Buffer MP4/WebM
|
|
1214
|
-
// result.format → "mp4" | "webm"
|
|
304
|
+
```json
|
|
305
|
+
{
|
|
306
|
+
"status": "success",
|
|
307
|
+
"downloadUrl": "https://cdn.test/out.jpg",
|
|
308
|
+
"raw": {
|
|
309
|
+
"status": "success",
|
|
310
|
+
"downloadUrl": "https://cdn.test/out.jpg"
|
|
311
|
+
}
|
|
312
|
+
}
|
|
1215
313
|
```
|
|
1216
314
|
|
|
1217
|
-
|
|
315
|
+
### FaceswapScraper
|
|
1218
316
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
### withPreset
|
|
1222
|
-
|
|
1223
|
-
Terapkan style preset ke config:
|
|
317
|
+
Source: `https://api.lovefaceswap.com`.
|
|
1224
318
|
|
|
1225
319
|
```ts
|
|
1226
|
-
|
|
1227
|
-
|
|
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
|
-
---
|
|
320
|
+
import { readFile } from "node:fs/promises";
|
|
321
|
+
import { FaceswapScraper } from "wsper-js";
|
|
1242
322
|
|
|
1243
|
-
|
|
323
|
+
const [source, target] = await Promise.all([
|
|
324
|
+
readFile("./source-face.jpg"),
|
|
325
|
+
readFile("./target.jpg"),
|
|
326
|
+
]);
|
|
1244
327
|
|
|
1245
|
-
|
|
328
|
+
const scraper = new FaceswapScraper();
|
|
329
|
+
const result = await scraper.process(source, target);
|
|
1246
330
|
|
|
1247
|
-
|
|
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
|
|
331
|
+
console.log(result.data);
|
|
1268
332
|
```
|
|
1269
333
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
### MP4 ↔ GIF Conversion
|
|
1273
|
-
|
|
1274
|
-
#### GIF → MP4
|
|
334
|
+
Representative output:
|
|
1275
335
|
|
|
1276
|
-
```
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
}
|
|
1281
|
-
// → Buffer MP4
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"job_id": "job-1",
|
|
339
|
+
"image": "https://cdn.test/out.jpg"
|
|
340
|
+
}
|
|
1282
341
|
```
|
|
1283
342
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
```ts
|
|
1287
|
-
const webmBuffer = await brat.gifToWebm("./brat.gif", "./brat.webm", { fps: 24 });
|
|
1288
|
-
// → Buffer WebM
|
|
1289
|
-
```
|
|
343
|
+
### UpscalerScraper
|
|
1290
344
|
|
|
1291
|
-
|
|
345
|
+
Source: `https://aienhancer.ai`.
|
|
1292
346
|
|
|
1293
347
|
```ts
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
-
```
|
|
348
|
+
import { readFile } from "node:fs/promises";
|
|
349
|
+
import { UpscalerScraper } from "wsper-js";
|
|
1303
350
|
|
|
1304
|
-
|
|
351
|
+
const image = await readFile("./photo.jpg");
|
|
352
|
+
const scraper = new UpscalerScraper();
|
|
353
|
+
const result = await scraper.upscaleBuffer(image, "image/jpeg");
|
|
1305
354
|
|
|
1306
|
-
|
|
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
|
|
355
|
+
console.log(result.data);
|
|
1314
356
|
```
|
|
1315
357
|
|
|
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
|
-
}
|
|
358
|
+
Representative output:
|
|
1333
359
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
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)
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"id": "task-1",
|
|
363
|
+
"input": "https://cdn.test/in.jpg",
|
|
364
|
+
"output": "https://cdn.test/out.jpg"
|
|
1342
365
|
}
|
|
1343
366
|
```
|
|
1344
367
|
|
|
1345
|
-
|
|
368
|
+
### StalkScraper
|
|
1346
369
|
|
|
1347
|
-
|
|
370
|
+
Source: npm registry API at `https://registry.npmjs.org`.
|
|
1348
371
|
|
|
1349
372
|
```ts
|
|
1350
|
-
|
|
1351
|
-
value: "teks yang ingin ditampilkan",
|
|
373
|
+
import { StalkScraper } from "wsper-js";
|
|
1352
374
|
|
|
1353
|
-
|
|
375
|
+
const scraper = new StalkScraper();
|
|
376
|
+
const result = await scraper.getNpmPackage("axios");
|
|
1354
377
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
fontFamily: '"Arial Black", sans-serif',
|
|
1360
|
-
fontWeight: 900, // atau string: "bold"
|
|
378
|
+
console.log(result.data?.title);
|
|
379
|
+
console.log(result.data?.install);
|
|
380
|
+
```
|
|
1361
381
|
|
|
1362
|
-
|
|
1363
|
-
color: "#111111",
|
|
382
|
+
Representative output:
|
|
1364
383
|
|
|
1365
|
-
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"title": "@scope/pkg",
|
|
387
|
+
"language": "",
|
|
388
|
+
"publish": "2026-05-01T00:00:00.000Z",
|
|
389
|
+
"readme": "",
|
|
390
|
+
"explore": "https://www.npmjs.com/package/@scope/pkg",
|
|
391
|
+
"dependencies": "0",
|
|
392
|
+
"dependents": "0",
|
|
393
|
+
"version_count": "1",
|
|
394
|
+
"keywords": [],
|
|
395
|
+
"install": "npm install @scope/pkg",
|
|
396
|
+
"info": [],
|
|
397
|
+
"collaborator": []
|
|
1366
398
|
}
|
|
1367
399
|
```
|
|
1368
400
|
|
|
1369
|
-
|
|
401
|
+
### MediafireScraper
|
|
1370
402
|
|
|
1371
|
-
|
|
403
|
+
Source: Mediafire public file page HTML.
|
|
1372
404
|
|
|
1373
405
|
```ts
|
|
1374
|
-
|
|
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
|
-
```
|
|
406
|
+
import { MediafireScraper } from "wsper-js";
|
|
1393
407
|
|
|
1394
|
-
|
|
408
|
+
const scraper = new MediafireScraper();
|
|
409
|
+
const result = await scraper.getLink(
|
|
410
|
+
"https://www.mediafire.com/file/ipnyzofjcwri357/test-10mb.bin/file",
|
|
411
|
+
);
|
|
1395
412
|
|
|
1396
|
-
|
|
1397
|
-
layout: { padding: 64 }
|
|
1398
|
-
layout: { padding: { top: 80, right: 64, bottom: 80, left: 64 } }
|
|
413
|
+
console.log(result.data);
|
|
1399
414
|
```
|
|
1400
415
|
|
|
1401
|
-
|
|
416
|
+
Representative output:
|
|
1402
417
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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
|
|
418
|
+
```json
|
|
419
|
+
{
|
|
420
|
+
"downloadUrl": "https://download.example/myfile.zip",
|
|
421
|
+
"fileName": "My_File.zip",
|
|
422
|
+
"fileSize": "50 MB",
|
|
423
|
+
"fileType": "ZIP"
|
|
1415
424
|
}
|
|
1416
425
|
```
|
|
1417
426
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
|
1421
|
-
|
|
|
1422
|
-
| `
|
|
1423
|
-
|
|
1424
|
-
|
|
427
|
+
## Available Scrapers
|
|
428
|
+
|
|
429
|
+
| Scraper | Purpose | Source/API | Auth/Cookie required? | Example file | Notes |
|
|
430
|
+
| --- | --- | --- | --- | --- | --- |
|
|
431
|
+
| `AlkitabScraper` | Bible verse search | `alkitab.me` | No | `examples/alkitab.example.ts` | `search(query)` |
|
|
432
|
+
| `AnimeQuoteScraper` | Random anime quote | `otakotaku.com` | No | `examples/anime-quote.example.ts` | `getRandom()` |
|
|
433
|
+
| `AnimeRandomScraper` | Random anime character image | GitHub raw anime dataset | No | `examples/anime-random.example.ts` | `getImage(character)`, `random()` |
|
|
434
|
+
| `BiliBiliScraper` | BiliBili search and video info | `api.bilibili.com` | Optional cookie | `examples/bilibili.example.ts` | Cookie may unlock authenticated stream access |
|
|
435
|
+
| `BMKGScraper` | Indonesian earthquake and weather feeds | `data.bmkg.go.id`, `nowcasting.bmkg.go.id` | No | `examples/bmkg.example.ts` | Autogempa, gempa dirasakan, nowcasting, forecast |
|
|
436
|
+
| `CapCutScraper` | Resolve CapCut template video URL | `capdownloader.com/wp-json/aio-dl/video-data/` | No | `examples/capcut.example.ts` | Mocked in example runner |
|
|
437
|
+
| `CuacaScraper` | Indonesian weather by location/coordinate | BMKG weather APIs | Optional API key for warnings | `examples/cuaca.example.ts` | Reads optional `BMKG_WARNING_API_KEY` in example |
|
|
438
|
+
| `DrakorScraper` | Korean drama search/list/detail | `drakorkita30.kita.baby` | No | `examples/drakor.example.ts` | `search`, `detail`, `ongoing`, `getAll` |
|
|
439
|
+
| `DramaboxScraper` | Dramabox search | `dramabox.com` | No | `examples/dramabox.example.ts` | `search(query)` |
|
|
440
|
+
| `FaceswapScraper` | Face-swap image processing | `api.lovefaceswap.com` | No | `examples/faceswap.example.ts` | Mocked in example runner |
|
|
441
|
+
| `HokInfoScraper` | Honor of Kings character info | Fandom MediaWiki parse API | No | `examples/hok-info.example.ts` | Uses `api.php?action=parse` |
|
|
442
|
+
| `HtmlToJpgScraper` | HTML file to JPG conversion | `api.freeconvert.com` | No credential in code | `examples/html-to-jpg.example.ts` | File-based conversion; skipped in runner without fixture |
|
|
443
|
+
| `IkiruMangaScraper` | Manga search | `02.ikiru.wtf` | No | `examples/ikiru-manga.example.ts` | Mock server fallback available |
|
|
444
|
+
| `ImageScraper` | Safebooru image search | `safebooru.org` | No | `examples/image.example.ts` | Current site type: `safebooru` |
|
|
445
|
+
| `ImgUpscalerScraper` | Image upscaling | `get1.imglarger.com` | No | `examples/img-upscaler.example.ts` | Mocked in example runner |
|
|
446
|
+
| `InstagramScraper` | Profile, feed, post, download | `instagram.com` web/API endpoints | Internal defaults; custom cookie supported | `examples/instagram.example.ts` | Use only legitimate session cookies |
|
|
447
|
+
| `KomikindoScraper` | Manga search/detail | `komikindo.ch` | No | `examples/komikindo.example.ts` | `search`, `getDetail` |
|
|
448
|
+
| `LyricsScraper` | Lyrics search | `lrclib.net` JSON API | No | `examples/lyrics.example.ts` | Replaced blocked HTML scraping with API integration |
|
|
449
|
+
| `MConverterScraper` | File conversion helpers | `mconverter.eu` | No | `examples/mconverter.example.ts` | `getTargets`, `convert`, `convertBuffer` |
|
|
450
|
+
| `McAddonScraper` | Minecraft addon search/detail | `mmcreviews.com` | No | `examples/mcaddon.example.ts` | `search`, `getDetail`, `getAddon` |
|
|
451
|
+
| `MediafireScraper` | Resolve Mediafire download link | Mediafire public HTML page | No | `examples/mediafire.example.ts` | Default example uses active 10MB test file |
|
|
452
|
+
| `ModAndroidScraper` | Android APK/mod search aggregations | `an1.com`, `modyolo.com`, `aptoide.com`, `uptodown.com` | No | `examples/mod-android.example.ts` | `android1`, `modyolo`, `aptoide`, `uptodown`, `searchAll` |
|
|
453
|
+
| `OcrScraper` | OCR image scan | `newocr.com` | No | `examples/ocr.example.ts` | File/buffer based |
|
|
454
|
+
| `PhotoAiScraper` | Photo AI upload/status | `photoai.imglarger.com` | No | `examples/photo-ai.example.ts` | Mocked in example runner |
|
|
455
|
+
| `PinterestScraper` | Pin search, detail, download | `pinterest.com` | Internal defaults; custom cookie supported | `examples/pinterest.example.ts` | Supports `credentials` option |
|
|
456
|
+
| `PlayStoreScraper` | Google Play app search | `play.google.com` | No | `examples/playstore.example.ts` | `search(query, limit)` |
|
|
457
|
+
| `ResepScraper` | Recipe search | `cookpad.com` | No | `examples/resep.example.ts` | Returns recipe items |
|
|
458
|
+
| `SakuraNovelScraper` | Novel search/detail/chapter | `sakuranovel.id` | No | `examples/sakura-novel.example.ts` | Mock server fallback available |
|
|
459
|
+
| `SpotifyScraper` | Spotify track, album, playlist, search, downloads | Spotify Web API and Accounts API | Internal defaults; custom client credentials supported | `examples/spotify.example.ts` | Partial custom credentials are rejected |
|
|
460
|
+
| `StalkScraper` | npm package metadata lookup | `registry.npmjs.org` | No | `examples/stalk.example.ts` | Default example query is `axios` |
|
|
461
|
+
| `ThreadsScraper` | Threads profile, post, search, download | `threads.net` web/API endpoints | Internal defaults; custom cookie supported | `examples/threads.example.ts` | Use only legitimate session cookies |
|
|
462
|
+
| `TopAnimeScraper` | MyAnimeList top anime list | `myanimelist.net` | No | `examples/top-anime.example.ts` | `getTopAnime(limit)` |
|
|
463
|
+
| `TwitterScraper` | Tweet, profile, search, timeline, downloads | `x.com/i/api/graphql` | Cookie/CSRF commonly required | `examples/twitter.example.ts` | Supports `credentials` option |
|
|
464
|
+
| `UguuScraper` | Temporary file upload | `uguu.se/upload` | No | `examples/uguu.example.ts` | `upload(buffer, filename)` |
|
|
465
|
+
| `UpscalerScraper` | Image enhancement | `aienhancer.ai` | No | `examples/upscaler.example.ts` | Rejects remote URL string input |
|
|
466
|
+
| `VideyScraper` | Video upload | `videy.co/api/upload` | No | `examples/videy.example.ts` | `upload`, `uploadBuffer` |
|
|
467
|
+
| `WallpaperScraper` | Wallpaper search | `wallhaven.cc/api/v1/search` | No | `examples/wallpaper.example.ts` | Replaced blocked HTML scraping with API integration |
|
|
468
|
+
| `Webp2Mp4Scraper` | WebP to MP4/PNG conversion | `ezgif.com` | No | `examples/webp2mp4.example.ts` | `toMp4`, `toPng` |
|
|
469
|
+
| `WwCharScraper` | Wuthering Waves character info | Fandom MediaWiki parse API | No | `examples/ww-char.example.ts` | Uses `api.php?action=parse` |
|
|
470
|
+
| `YouTubeScraper` | YouTube metadata, search, playlist, channel, downloads | `yt-dlp`, `play-dl`, YouTube pages | No cookie option exposed in current scraper options | `examples/youtube.example.ts` | Media download features need external tools |
|
|
471
|
+
|
|
472
|
+
## What's New
|
|
473
|
+
|
|
474
|
+
The latest scraper reliability pass fixed 12 failing scraper functions and their corresponding tests.
|
|
475
|
+
|
|
476
|
+
- `LyricsScraper` now uses LRCLIB's public JSON API instead of a Cloudflare-protected Musixmatch HTML page.
|
|
477
|
+
- `WallpaperScraper` now uses Wallhaven's public search API instead of a Cloudflare-protected Wallpaperflare HTML page.
|
|
478
|
+
- `WwCharScraper` and `HokInfoScraper` now use Fandom's MediaWiki parse API (`api.php?action=parse`) and prepend a synthetic `#firstHeading` node before running the existing parsers.
|
|
479
|
+
- `examples/mock-server.ts` now covers WordPress resolver routes, AI tool polling/upload routes, and Fandom API responses.
|
|
480
|
+
- `examples/alllexamp.ts` starts and stops the mock server during the runner lifecycle.
|
|
481
|
+
- `WSPER_MOCK_BASE_URL` is injected into spawned example subprocesses so individual examples can use the local mock endpoint automatically.
|
|
482
|
+
- Default example inputs were updated: `StalkScraper` uses `axios`, `MediafireScraper` uses an active 10MB test file URL, and `HokInfoScraper` uses `Angela`.
|
|
483
|
+
- Verification recorded in `walkthrough.md`: 82 test files passed, 314 tests passed, and the examples runner reported 83 OK, 0 FAIL, 2 SKIP.
|
|
484
|
+
|
|
485
|
+
## Mock Server and Testing
|
|
486
|
+
|
|
487
|
+
`examples/mock-server.ts` is a local HTTP server used by the examples runner to provide deterministic responses for endpoints that are rate-limited, depend on third-party availability, or are inconvenient to call during automated checks.
|
|
488
|
+
|
|
489
|
+
Currently mocked routes include:
|
|
490
|
+
|
|
491
|
+
| Area | Routes |
|
|
492
|
+
| --- | --- |
|
|
493
|
+
| CapCut | `/wp-json/aio-dl/video-data/` |
|
|
494
|
+
| ImgUpscaler | `/api/UpscalerNew/UploadNew`, `/api/UpscalerNew/CheckStatusNew` |
|
|
495
|
+
| PhotoAi | `/api/PhoAi/Upload`, `/api/PhoAi/CheckStatus` |
|
|
496
|
+
| Faceswap | `/api/face-swap/create-poll`, `/api/common/get` |
|
|
497
|
+
| Upscaler | `/api/v1/r/image-enhance/create`, `/api/v1/r/image-enhance/result` |
|
|
498
|
+
| Fandom Wiki | `/api.php` |
|
|
499
|
+
| WordPress search fixtures | `/wp-admin/admin-ajax.php` |
|
|
500
|
+
|
|
501
|
+
`examples/alllexamp.ts` starts the mock server, sets `process.env.WSPER_MOCK_BASE_URL`, routes direct scraper demos to the mock server with `http: { allowPrivateNetwork: true }` where needed, runs individual example files as subprocesses, then closes the mock server.
|
|
502
|
+
|
|
503
|
+
This keeps example validation safer and more repeatable. It avoids making every test depend on live third-party services, Cloudflare-protected HTML pages, or rate-limited AI tool endpoints.
|
|
504
|
+
|
|
505
|
+
## Running Examples
|
|
506
|
+
|
|
507
|
+
Run all direct scraper demos and individual example files:
|
|
1425
508
|
|
|
1426
|
-
|
|
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" }
|
|
509
|
+
```bash
|
|
510
|
+
npx tsx examples/alllexamp.ts
|
|
1441
511
|
```
|
|
1442
512
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
---
|
|
513
|
+
The runner writes scraper JSON results to `downloads/output/scrapers/` and subprocess logs to `downloads/output/examples/`.
|
|
1446
514
|
|
|
1447
|
-
|
|
515
|
+
Summary fields:
|
|
1448
516
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
-
});
|
|
517
|
+
| Field | Meaning |
|
|
518
|
+
| --- | --- |
|
|
519
|
+
| `OK` | The scraper/example returned a successful result. |
|
|
520
|
+
| `FAIL` | The scraper/example returned a failed response or subprocess exit. Auth-required scrapers may fail without valid credentials. |
|
|
521
|
+
| `SKIP` | The runner intentionally skipped an entry, usually because a local fixture is unavailable. |
|
|
1476
522
|
|
|
1477
|
-
|
|
523
|
+
Recorded walkthrough result:
|
|
1478
524
|
|
|
1479
|
-
|
|
525
|
+
```txt
|
|
526
|
+
OK : 83
|
|
527
|
+
FAIL : 0
|
|
528
|
+
SKIP : 2
|
|
529
|
+
Total: 85
|
|
1480
530
|
```
|
|
1481
531
|
|
|
1482
|
-
|
|
532
|
+
Individual examples can also be run directly:
|
|
1483
533
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
`
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
Credential tidak dibaca dari `.env`. Dua cara konfigurasi:
|
|
534
|
+
```bash
|
|
535
|
+
npx tsx examples/lyrics.example.ts "after hours the weeknd"
|
|
536
|
+
npx tsx examples/wallpaper.example.ts cyberpunk
|
|
537
|
+
npx tsx examples/stalk.example.ts axios
|
|
538
|
+
npx tsx examples/mediafire.example.ts "https://www.mediafire.com/file/ipnyzofjcwri357/test-10mb.bin/file"
|
|
539
|
+
npx tsx examples/upscaler.example.ts testassets/photo.jpg
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
For scrapers that support cookies or credentials, use only accounts and sessions you are authorized to access. Do not hardcode real cookies, tokens, client secrets, or API keys in source files.
|
|
543
|
+
|
|
544
|
+
## Running Tests and Validation
|
|
545
|
+
|
|
546
|
+
Package scripts from `package.json`:
|
|
547
|
+
|
|
548
|
+
| Command | Purpose |
|
|
549
|
+
| --- | --- |
|
|
550
|
+
| `npm run test` | Run all Vitest tests once. |
|
|
551
|
+
| `npm run test:watch` | Run Vitest in watch mode. |
|
|
552
|
+
| `npm run typecheck` | Run TypeScript with `--noEmit`. |
|
|
553
|
+
| `npm run build` | Build production ESM output through `script/build.mjs`. |
|
|
554
|
+
| `npm run build:dev` | Build development output. |
|
|
555
|
+
| `npm run build:prod` | Build production output. |
|
|
556
|
+
| `npm run build:bytecode` | Build bytecode output with `script/build-bytecode.mjs`. |
|
|
557
|
+
| `npm run build:all` | Build production output and bytecode. |
|
|
558
|
+
| `npm run test:instagram` | Run Instagram tests only. |
|
|
559
|
+
| `npm run test:spotify` | Run Spotify tests only. |
|
|
560
|
+
| `npm run test:youtube` | Run YouTube tests only. |
|
|
561
|
+
| `npm run test:threads` | Run Threads tests only. |
|
|
562
|
+
| `npm run test:pinterest` | Run Pinterest tests only. |
|
|
563
|
+
| `npm run test:brat` | Run Brat tests only. |
|
|
564
|
+
|
|
565
|
+
Recommended validation before publishing or changing behavior:
|
|
1517
566
|
|
|
1518
|
-
```
|
|
1519
|
-
|
|
1520
|
-
|
|
567
|
+
```bash
|
|
568
|
+
npm run typecheck
|
|
569
|
+
npm run test
|
|
570
|
+
npm run build
|
|
571
|
+
npx tsx examples/alllexamp.ts
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## Project Structure
|
|
575
|
+
|
|
576
|
+
```txt
|
|
577
|
+
src/
|
|
578
|
+
index.ts Public package exports
|
|
579
|
+
WsperScraper.ts Aggregate scraper entrypoint
|
|
580
|
+
core/
|
|
581
|
+
credentials/ Credential normalization and platform headers
|
|
582
|
+
error/ WsperError, ValidationError, HttpError, ParseError, DownloadError, ScraperError
|
|
583
|
+
http/ HTTP client, retries, timeouts, safe URL validation
|
|
584
|
+
parser/ Shared HTML and JSON parser helpers
|
|
585
|
+
queue/ Request pacing and concurrency control
|
|
586
|
+
modules/
|
|
587
|
+
brat/ Brat image/GIF/video generator and converters
|
|
588
|
+
chart/ Analytics image generator
|
|
589
|
+
download/ Safe downloader primitives
|
|
590
|
+
scrapers/ Platform-specific scraper implementations
|
|
591
|
+
types/ Shared response, option, and common types
|
|
592
|
+
utils/ Sleep, URL, validation, browser-profile, and helper utilities
|
|
593
|
+
examples/
|
|
594
|
+
alllexamp.ts Full example runner
|
|
595
|
+
mock-server.ts Local deterministic mock server
|
|
596
|
+
*.example.ts Individual runnable examples
|
|
597
|
+
tests/
|
|
598
|
+
*/*.test.ts Unit and parser tests
|
|
599
|
+
dist/ Build output only; do not edit manually
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## Environment Variables
|
|
603
|
+
|
|
604
|
+
These variables appear in the repository:
|
|
605
|
+
|
|
606
|
+
| Variable | Used by | Required? | Notes |
|
|
607
|
+
| --- | --- | --- | --- |
|
|
608
|
+
| `WSPER_MOCK_BASE_URL` | `examples/alllexamp.ts`, AI tool examples | No | Set by the all-examples runner for subprocesses. Points examples to the local mock server. |
|
|
609
|
+
| `BMKG_WARNING_API_KEY` | `examples/cuaca.example.ts` | No | Optional warning API key passed to `CuacaScraper({ warningApiKey })`. |
|
|
610
|
+
| `INSTAGRAM_COOKIE` | `examples/instagram.example.ts` comments | No | Optional example input for constructor credentials. Use `<YOUR_COOKIE_HERE>` style placeholders in docs and never commit real cookies. |
|
|
611
|
+
| `INSTAGRAM_CSRF_TOKEN` | `examples/instagram.example.ts` comments | No | Optional example input for constructor credentials. |
|
|
612
|
+
| `BILI_COOKIE` | `examples/bilibili.example.ts` | No | Optional BiliBili cookie for authenticated stream access. |
|
|
613
|
+
| `WSPER_COOKIE` | `tests/core/credentials.test.ts` | No | Test-only variable proving runtime credential resolution does not read env credentials automatically. |
|
|
614
|
+
|
|
615
|
+
Credential configuration is constructor-based. The library does not rely on `.env` files for runtime credentials.
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
import { WsperScraper } from "wsper-js";
|
|
1521
619
|
|
|
1522
|
-
// 2. Custom credential per platform
|
|
1523
620
|
const wsper = new WsperScraper({
|
|
1524
621
|
spotifyCredentials: {
|
|
1525
|
-
clientId:
|
|
622
|
+
clientId: "your-client-id",
|
|
1526
623
|
clientSecret: "your-client-secret",
|
|
1527
624
|
},
|
|
1528
625
|
credentials: {
|
|
1529
626
|
twitter: {
|
|
1530
|
-
cookie:
|
|
1531
|
-
csrfToken: "
|
|
627
|
+
cookie: "<YOUR_COOKIE_HERE>",
|
|
628
|
+
csrfToken: "<YOUR_CSRF_TOKEN_HERE>",
|
|
1532
629
|
},
|
|
1533
|
-
|
|
1534
|
-
cookie:
|
|
1535
|
-
csrfToken: "
|
|
630
|
+
instagram: {
|
|
631
|
+
cookie: "<YOUR_COOKIE_HERE>",
|
|
632
|
+
csrfToken: "<YOUR_CSRF_TOKEN_HERE>",
|
|
1536
633
|
},
|
|
1537
634
|
},
|
|
1538
635
|
});
|
|
1539
636
|
```
|
|
1540
637
|
|
|
1541
|
-
|
|
638
|
+
Spotify custom credentials must include both `clientId` and `clientSecret`. Partial custom Spotify credentials are rejected with `ValidationError`.
|
|
1542
639
|
|
|
1543
|
-
|
|
640
|
+
## Cloudflare, Rate Limits, and Reliability
|
|
1544
641
|
|
|
1545
|
-
|
|
642
|
+
Some websites protect HTML pages with Cloudflare, require active authenticated sessions, or apply strict rate limits. `wsper-js` favors public APIs when they are available and documented by implementation, such as LRCLIB, Wallhaven, Fandom MediaWiki, Spotify, BMKG, BiliBili, and npm registry endpoints.
|
|
1546
643
|
|
|
1547
|
-
|
|
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
|
-
}
|
|
644
|
+
The mock server exists to make tests and examples deterministic. It should be used for local validation of rate-limited, Cloudflare-protected, or external-service-dependent flows instead of repeatedly hitting live services.
|
|
1557
645
|
|
|
1558
|
-
|
|
1559
|
-
concurrency?: number; // default 1
|
|
1560
|
-
intervalMs?: number;
|
|
1561
|
-
intervalCap?: number;
|
|
1562
|
-
minDelayMs?: number;
|
|
1563
|
-
maxDelayMs?: number;
|
|
1564
|
-
}
|
|
1565
|
-
```
|
|
646
|
+
This project does not provide CAPTCHA bypass logic, credential harvesting, session theft, anti-bot evasion, or rate-limit abuse. Use cookies only for accounts and sessions you legitimately control, respect platform terms, and keep request pacing conservative.
|
|
1566
647
|
|
|
1567
|
-
|
|
648
|
+
## Contributing
|
|
1568
649
|
|
|
1569
|
-
```
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
650
|
+
```bash
|
|
651
|
+
git clone <repository-url>
|
|
652
|
+
cd wsper
|
|
653
|
+
npm install
|
|
654
|
+
npm run typecheck
|
|
655
|
+
npm run test
|
|
1574
656
|
```
|
|
1575
657
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
## Download Outputs
|
|
658
|
+
When adding or changing a scraper:
|
|
1579
659
|
|
|
1580
|
-
|
|
660
|
+
1. Keep scraper-specific logic under `src/scrapers/<name>/`.
|
|
661
|
+
2. Export the scraper from `src/scrapers/<name>/index.ts` and `src/scrapers/index.ts`.
|
|
662
|
+
3. Return typed `WsperResponse<T>` results.
|
|
663
|
+
4. Keep HTTP, parsing, queueing, and file download responsibilities separated.
|
|
664
|
+
5. Add or update parser and scraper tests under `tests/`.
|
|
665
|
+
6. Add or update a runnable example under `examples/`.
|
|
666
|
+
7. Use the mock server for flows that should not depend on live third-party behavior in default tests.
|
|
667
|
+
8. Update this README when public API, usage, behavior, examples, or validation results change.
|
|
1581
668
|
|
|
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
|
-
```
|
|
669
|
+
## Security
|
|
1602
670
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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
|
-
```
|
|
671
|
+
- Validate and normalize user-provided URLs before requesting them.
|
|
672
|
+
- Use only `http:` and `https:` unless a module explicitly supports something else.
|
|
673
|
+
- Keep SSRF protections enabled; private network requests require explicit `allowPrivateNetwork: true` and should be reserved for local mocks or trusted endpoints.
|
|
674
|
+
- Do not log secrets, cookies, client secrets, authorization headers, access tokens, refresh tokens, or raw credential objects.
|
|
675
|
+
- Do not commit credentials, cookies, tokens, private fixtures, or real session material.
|
|
1612
676
|
|
|
1613
|
-
##
|
|
677
|
+
## License
|
|
1614
678
|
|
|
1615
|
-
|
|
1616
|
-
kalo ada bug gabung aja ke trus bilang ke ke yang buat lib
|
|
679
|
+
GPL-3.0-or-later. See `LICENSE` for the full license text.
|