@lmna22/aio-downloader 1.0.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 +416 -0
- package/package.json +51 -0
- package/src/download.js +53 -0
- package/src/index.js +62 -0
- package/src/lib/instagram.js +395 -0
- package/src/lib/pinterest.js +313 -0
- package/src/lib/pixiv.js +425 -0
- package/src/lib/tiktok.js +120 -0
- package/src/lib/twitter.js +239 -0
- package/src/lib/youtube.js +257 -0
- package/src/utils.js +93 -0
package/src/lib/pixiv.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const cheerio = require("cheerio");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { DEFAULT_UA, cleanText, safeJsonParse, uniqBy, isValidUrl, delay } = require("../utils");
|
|
5
|
+
|
|
6
|
+
function isPixivUrl(text) {
|
|
7
|
+
try {
|
|
8
|
+
const u = new URL(text);
|
|
9
|
+
return u.hostname.includes("pixiv.net") || u.hostname.includes("pximg.net");
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildSearchUrl(query) {
|
|
16
|
+
return `https://www.pixiv.net/en/tags/${encodeURIComponent(query)}/artworks`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseArtworkIdFromUrl(url) {
|
|
20
|
+
if (!url) return null;
|
|
21
|
+
const m = String(url).match(/(?:artworks\/|illust_id=)(\d+)/);
|
|
22
|
+
return m ? m[1] : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeArtworkUrl(url, id = null) {
|
|
26
|
+
if (!url && id) return `https://www.pixiv.net/artworks/${id}`;
|
|
27
|
+
if (!url) return null;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(url, "https://www.pixiv.net");
|
|
31
|
+
const illustId = u.searchParams.get("illust_id");
|
|
32
|
+
if (illustId) return `https://www.pixiv.net/artworks/${illustId}`;
|
|
33
|
+
|
|
34
|
+
const m = u.pathname.match(/\/artworks\/(\d+)/);
|
|
35
|
+
if (m) return `https://www.pixiv.net/artworks/${m[1]}`;
|
|
36
|
+
if (id) return `https://www.pixiv.net/artworks/${id}`;
|
|
37
|
+
return u.toString();
|
|
38
|
+
} catch {
|
|
39
|
+
const m = String(url).match(/(?:artworks\/|illust_id=)(\d+)/);
|
|
40
|
+
if (m) return `https://www.pixiv.net/artworks/${m[1]}`;
|
|
41
|
+
if (id) return `https://www.pixiv.net/artworks/${id}`;
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeUserUrl(url, userId = null) {
|
|
47
|
+
if (!url && userId) return `https://www.pixiv.net/users/${userId}`;
|
|
48
|
+
if (!url) return null;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const u = new URL(url, "https://www.pixiv.net");
|
|
52
|
+
const m = u.pathname.match(/\/users\/(\d+)/);
|
|
53
|
+
if (m) return `https://www.pixiv.net/users/${m[1]}`;
|
|
54
|
+
if (userId) return `https://www.pixiv.net/users/${userId}`;
|
|
55
|
+
return u.toString();
|
|
56
|
+
} catch {
|
|
57
|
+
const m = String(url).match(/\/users\/(\d+)/);
|
|
58
|
+
if (m) return `https://www.pixiv.net/users/${m[1]}`;
|
|
59
|
+
if (userId) return `https://www.pixiv.net/users/${userId}`;
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getExtFromUrl(url) {
|
|
65
|
+
try {
|
|
66
|
+
const pathname = new URL(url).pathname;
|
|
67
|
+
const ext = path.extname(pathname);
|
|
68
|
+
return ext || ".jpg";
|
|
69
|
+
} catch {
|
|
70
|
+
return ".jpg";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeHeaders() {
|
|
75
|
+
return {
|
|
76
|
+
"User-Agent": DEFAULT_UA,
|
|
77
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
78
|
+
"Referer": "https://www.pixiv.net/",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizePixivItem(item) {
|
|
83
|
+
if (!item || typeof item !== "object") return null;
|
|
84
|
+
|
|
85
|
+
const id = item.id || item.illustId || item.illust_id ||
|
|
86
|
+
parseArtworkIdFromUrl(item.url) || parseArtworkIdFromUrl(item.link) || null;
|
|
87
|
+
if (!id) return null;
|
|
88
|
+
|
|
89
|
+
const title = cleanText(item.title || item.illust_title || item.alt || item.caption || null);
|
|
90
|
+
|
|
91
|
+
const userId = item.userId || item.user_id || (() => {
|
|
92
|
+
const m = String(item.userUrl || item.www_user_url || "").match(/\/users\/(\d+)/);
|
|
93
|
+
return m ? m[1] : null;
|
|
94
|
+
})();
|
|
95
|
+
|
|
96
|
+
const link = normalizeArtworkUrl(
|
|
97
|
+
item.link || item.url || item.artworkUrl || item.www_member_illust_medium_url || null, id
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const image = item.image || item.src || item.urls?.original || item.url || null;
|
|
101
|
+
const artist = cleanText(item.userName || item.user_name || item.artist || null);
|
|
102
|
+
const artistUrl = normalizeUserUrl(item.userUrl || item.www_user_url || null, userId);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
id: String(id),
|
|
106
|
+
title: title || null,
|
|
107
|
+
link: link || `https://www.pixiv.net/artworks/${id}`,
|
|
108
|
+
displayUrl: link || `https://www.pixiv.net/artworks/${id}`,
|
|
109
|
+
image: isValidUrl(image) ? image : null,
|
|
110
|
+
artist: artist || null,
|
|
111
|
+
artistUrl: artistUrl || null,
|
|
112
|
+
userId: userId ? String(userId) : null,
|
|
113
|
+
images: [],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function deepCollectPixivItems(obj, bucket = []) {
|
|
118
|
+
if (!obj || typeof obj !== "object") return bucket;
|
|
119
|
+
|
|
120
|
+
if (Array.isArray(obj)) {
|
|
121
|
+
for (const item of obj) deepCollectPixivItems(item, bucket);
|
|
122
|
+
return bucket;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const parsed = normalizePixivItem(obj);
|
|
126
|
+
if (parsed) bucket.push(parsed);
|
|
127
|
+
|
|
128
|
+
for (const value of Object.values(obj)) deepCollectPixivItems(value, bucket);
|
|
129
|
+
return bucket;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function parseInitConfigJson(html) {
|
|
133
|
+
const $ = cheerio.load(html);
|
|
134
|
+
const raw = $("#init-config").attr("content") || $("#init-config").text() || $("#init-config.json-data").attr("value");
|
|
135
|
+
if (!raw) return [];
|
|
136
|
+
const json = safeJsonParse(raw);
|
|
137
|
+
if (!json) return [];
|
|
138
|
+
return uniqBy(deepCollectPixivItems(json), (x) => x.id);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseNextData(html) {
|
|
142
|
+
const results = [];
|
|
143
|
+
const matches = [...html.matchAll(/<script[^>]*id=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/g)];
|
|
144
|
+
for (const m of matches) {
|
|
145
|
+
const json = safeJsonParse(m[1]);
|
|
146
|
+
if (!json) continue;
|
|
147
|
+
results.push(...deepCollectPixivItems(json));
|
|
148
|
+
}
|
|
149
|
+
return uniqBy(results, (x) => x.id);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function parsePreloadData(html) {
|
|
153
|
+
const results = [];
|
|
154
|
+
const matches = [...html.matchAll(/<meta[^>]+id=["']meta-preload-data["'][^>]+content=["']([^"]+)["']/g)];
|
|
155
|
+
for (const m of matches) {
|
|
156
|
+
const raw = m[1].replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
157
|
+
const json = safeJsonParse(raw);
|
|
158
|
+
if (!json) continue;
|
|
159
|
+
results.push(...deepCollectPixivItems(json));
|
|
160
|
+
}
|
|
161
|
+
return uniqBy(results, (x) => x.id);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function parseAnchors(html) {
|
|
165
|
+
const $ = cheerio.load(html);
|
|
166
|
+
const results = [];
|
|
167
|
+
|
|
168
|
+
$("a[href*='/artworks/'], a[href*='illust_id=']").each((_, el) => {
|
|
169
|
+
const a = $(el);
|
|
170
|
+
const href = a.attr("href");
|
|
171
|
+
const link = normalizeArtworkUrl(href);
|
|
172
|
+
const id = parseArtworkIdFromUrl(link);
|
|
173
|
+
if (!id) return;
|
|
174
|
+
|
|
175
|
+
const img = a.find("img").first();
|
|
176
|
+
const image = img.attr("src") || img.attr("data-src") || img.attr("data-lazy-src") || null;
|
|
177
|
+
const title = cleanText(a.attr("title") || a.attr("aria-label") || img.attr("alt") || null);
|
|
178
|
+
|
|
179
|
+
let artist = null;
|
|
180
|
+
let artistUrl = null;
|
|
181
|
+
let userId = null;
|
|
182
|
+
|
|
183
|
+
const wrapper = a.closest("li, article, div, figure");
|
|
184
|
+
const userAnchor = wrapper.find("a[href*='/users/']").first();
|
|
185
|
+
|
|
186
|
+
if (userAnchor.length) {
|
|
187
|
+
artist = cleanText(userAnchor.attr("title") || userAnchor.text() || null);
|
|
188
|
+
artistUrl = normalizeUserUrl(userAnchor.attr("href"));
|
|
189
|
+
userId = artistUrl ? (artistUrl.match(/\/users\/(\d+)/) || [])[1] || null : null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
results.push({
|
|
193
|
+
id: String(id),
|
|
194
|
+
title: title || null,
|
|
195
|
+
link,
|
|
196
|
+
displayUrl: link,
|
|
197
|
+
image: isValidUrl(image) ? image : null,
|
|
198
|
+
artist,
|
|
199
|
+
artistUrl,
|
|
200
|
+
userId,
|
|
201
|
+
images: [],
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return uniqBy(results, (x) => x.id);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function extractResultsFromHtml(html) {
|
|
209
|
+
const items = [
|
|
210
|
+
...parseInitConfigJson(html),
|
|
211
|
+
...parseNextData(html),
|
|
212
|
+
...parsePreloadData(html),
|
|
213
|
+
...parseAnchors(html),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
return uniqBy(
|
|
217
|
+
items.filter(Boolean).filter((x) => x.id).map((x) => ({
|
|
218
|
+
id: String(x.id),
|
|
219
|
+
title: cleanText(x.title),
|
|
220
|
+
link: normalizeArtworkUrl(x.link, x.id),
|
|
221
|
+
displayUrl: normalizeArtworkUrl(x.displayUrl || x.link, x.id),
|
|
222
|
+
image: isValidUrl(x.image) ? x.image : null,
|
|
223
|
+
artist: cleanText(x.artist),
|
|
224
|
+
artistUrl: normalizeUserUrl(x.artistUrl, x.userId),
|
|
225
|
+
userId: x.userId ? String(x.userId) : null,
|
|
226
|
+
images: Array.isArray(x.images) ? x.images.filter(isValidUrl) : [],
|
|
227
|
+
})),
|
|
228
|
+
(x) => x.id
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function fetchHtmlWithAxios(url) {
|
|
233
|
+
const res = await axios.get(url, {
|
|
234
|
+
headers: {
|
|
235
|
+
...makeHeaders(),
|
|
236
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
237
|
+
},
|
|
238
|
+
timeout: 30000,
|
|
239
|
+
maxRedirects: 5,
|
|
240
|
+
});
|
|
241
|
+
return res.data;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function fetchHtmlWithPuppeteer(url) {
|
|
245
|
+
let puppeteer;
|
|
246
|
+
try {
|
|
247
|
+
puppeteer = require("puppeteer");
|
|
248
|
+
} catch {
|
|
249
|
+
throw new Error("Puppeteer is required for this Pixiv URL but is not installed. Install it with: npm install puppeteer");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const browser = await puppeteer.launch({
|
|
253
|
+
headless: true,
|
|
254
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const page = await browser.newPage();
|
|
259
|
+
await page.setUserAgent(DEFAULT_UA);
|
|
260
|
+
await page.setExtraHTTPHeaders(makeHeaders());
|
|
261
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout: 60000 });
|
|
262
|
+
await delay(4000);
|
|
263
|
+
|
|
264
|
+
await page.evaluate(async () => {
|
|
265
|
+
await new Promise((resolve) => {
|
|
266
|
+
let totalHeight = 0;
|
|
267
|
+
const distance = 1000;
|
|
268
|
+
const timer = setInterval(() => {
|
|
269
|
+
window.scrollBy(0, distance);
|
|
270
|
+
totalHeight += distance;
|
|
271
|
+
if (totalHeight >= 10000) { clearInterval(timer); resolve(); }
|
|
272
|
+
}, 300);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
await delay(2000);
|
|
277
|
+
return await page.content();
|
|
278
|
+
} finally {
|
|
279
|
+
await browser.close();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function fetchArtworkPages(artworkId) {
|
|
284
|
+
const apiUrl = `https://www.pixiv.net/ajax/illust/${artworkId}/pages?lang=en`;
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const res = await axios.get(apiUrl, {
|
|
288
|
+
headers: {
|
|
289
|
+
...makeHeaders(),
|
|
290
|
+
"Accept": "application/json, text/plain, */*",
|
|
291
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
292
|
+
},
|
|
293
|
+
timeout: 30000,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const body = res.data?.body;
|
|
297
|
+
if (!Array.isArray(body)) return [];
|
|
298
|
+
|
|
299
|
+
const urls = [];
|
|
300
|
+
for (const page of body) {
|
|
301
|
+
if (page?.urls?.original && isValidUrl(page.urls.original)) {
|
|
302
|
+
urls.push(page.urls.original);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return uniqBy(urls, (x) => x);
|
|
306
|
+
} catch {
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function fetchArtworkDetails(artworkId) {
|
|
312
|
+
const apiUrl = `https://www.pixiv.net/ajax/illust/${artworkId}?lang=en`;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const res = await axios.get(apiUrl, {
|
|
316
|
+
headers: {
|
|
317
|
+
...makeHeaders(),
|
|
318
|
+
"Accept": "application/json, text/plain, */*",
|
|
319
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
320
|
+
},
|
|
321
|
+
timeout: 30000,
|
|
322
|
+
});
|
|
323
|
+
return res.data?.body || null;
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function enrichImages(items) {
|
|
330
|
+
const out = [];
|
|
331
|
+
|
|
332
|
+
for (const item of items) {
|
|
333
|
+
const images = await fetchArtworkPages(item.id);
|
|
334
|
+
|
|
335
|
+
let artist = item.artist;
|
|
336
|
+
let userId = item.userId;
|
|
337
|
+
let title = item.title;
|
|
338
|
+
let artistUrl = item.artistUrl;
|
|
339
|
+
|
|
340
|
+
if (!artist || !userId || !title) {
|
|
341
|
+
const details = await fetchArtworkDetails(item.id);
|
|
342
|
+
if (details) {
|
|
343
|
+
artist = details.userName || artist;
|
|
344
|
+
userId = details.userId || userId;
|
|
345
|
+
title = details.illustTitle || details.title || title;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (userId && !artistUrl) {
|
|
350
|
+
artistUrl = `https://www.pixiv.net/users/${userId}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const bestImage = images[0] || item.image || null;
|
|
354
|
+
|
|
355
|
+
out.push({
|
|
356
|
+
...item,
|
|
357
|
+
title: title || item.title,
|
|
358
|
+
artist: artist || item.artist,
|
|
359
|
+
userId: userId || item.userId,
|
|
360
|
+
artistUrl: artistUrl || item.artistUrl,
|
|
361
|
+
image: bestImage,
|
|
362
|
+
images,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
await delay(350);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function pixivDownloader(input, options = {}) {
|
|
372
|
+
const limit = options.limit || 10;
|
|
373
|
+
const enrich = options.enrich !== false;
|
|
374
|
+
|
|
375
|
+
let isUrl = false;
|
|
376
|
+
try { new URL(input); isUrl = true; } catch { }
|
|
377
|
+
|
|
378
|
+
const inputIsPixiv = isUrl && isPixivUrl(input);
|
|
379
|
+
const singleMode = isUrl && inputIsPixiv;
|
|
380
|
+
const url = isUrl ? input : buildSearchUrl(input);
|
|
381
|
+
|
|
382
|
+
let html;
|
|
383
|
+
let results;
|
|
384
|
+
let method = "axios";
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
html = await fetchHtmlWithAxios(url);
|
|
388
|
+
results = extractResultsFromHtml(html);
|
|
389
|
+
if (results.length === 0) throw new Error("No results from axios");
|
|
390
|
+
} catch {
|
|
391
|
+
html = await fetchHtmlWithPuppeteer(url);
|
|
392
|
+
results = extractResultsFromHtml(html);
|
|
393
|
+
method = "puppeteer";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let finalResults = results;
|
|
397
|
+
|
|
398
|
+
if (singleMode) {
|
|
399
|
+
const exactId = parseArtworkIdFromUrl(url);
|
|
400
|
+
if (exactId) {
|
|
401
|
+
finalResults = finalResults.filter((x) => x.id === exactId);
|
|
402
|
+
}
|
|
403
|
+
finalResults = finalResults.slice(0, 1);
|
|
404
|
+
} else {
|
|
405
|
+
finalResults = finalResults.slice(0, limit);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (finalResults.length === 0) {
|
|
409
|
+
throw new Error("No Pixiv artwork data found.");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (enrich) {
|
|
413
|
+
finalResults = await enrichImages(finalResults);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
status: "ok",
|
|
418
|
+
platform: "pixiv",
|
|
419
|
+
method,
|
|
420
|
+
total: finalResults.length,
|
|
421
|
+
results: finalResults,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
module.exports = pixivDownloader;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const cheerio = require('cheerio');
|
|
3
|
+
|
|
4
|
+
let CookieJar, wrapper;
|
|
5
|
+
try {
|
|
6
|
+
CookieJar = require('tough-cookie').CookieJar;
|
|
7
|
+
wrapper = require('axios-cookiejar-support').wrapper;
|
|
8
|
+
} catch (err) { }
|
|
9
|
+
|
|
10
|
+
async function tiktokDownloader(url) {
|
|
11
|
+
if (!CookieJar || !wrapper) {
|
|
12
|
+
return {
|
|
13
|
+
creator: '@fort.kun',
|
|
14
|
+
status: false,
|
|
15
|
+
data: null,
|
|
16
|
+
message: 'tough-cookie and axios-cookiejar-support are required for TikTok downloads. Install them with: npm install tough-cookie axios-cookiejar-support'
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const jar = new CookieJar();
|
|
21
|
+
|
|
22
|
+
const apiClient = axios.create({
|
|
23
|
+
jar: jar,
|
|
24
|
+
withCredentials: true,
|
|
25
|
+
headers: {
|
|
26
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
wrapper(apiClient);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const htmlResponse = await apiClient.get(url);
|
|
33
|
+
const html = htmlResponse.data;
|
|
34
|
+
|
|
35
|
+
const $ = cheerio.load(html);
|
|
36
|
+
const scriptContent = $('#__UNIVERSAL_DATA_FOR_REHYDRATION__').html();
|
|
37
|
+
if (!scriptContent) throw new Error('Script tag #__UNIVERSAL_DATA_FOR_REHYDRATION__ not found.');
|
|
38
|
+
const jsonData = JSON.parse(scriptContent);
|
|
39
|
+
const itemStruct = jsonData?.__DEFAULT_SCOPE__?.["webapp.video-detail"]?.itemInfo?.itemStruct;
|
|
40
|
+
if (!itemStruct) throw new Error('itemStruct not found within the JSON data.');
|
|
41
|
+
|
|
42
|
+
const videoUrlToDownload = itemStruct.video?.downloadAddr || itemStruct.video?.playAddr;
|
|
43
|
+
const videoId = itemStruct.id;
|
|
44
|
+
|
|
45
|
+
const importantData = {
|
|
46
|
+
videoId: videoId,
|
|
47
|
+
description: itemStruct.desc,
|
|
48
|
+
createTime: itemStruct.createTime,
|
|
49
|
+
videoUrl: videoUrlToDownload,
|
|
50
|
+
videoInfo: {
|
|
51
|
+
size: null,
|
|
52
|
+
duration: itemStruct.video?.duration,
|
|
53
|
+
width: itemStruct.video?.width,
|
|
54
|
+
height: itemStruct.video?.height,
|
|
55
|
+
definition: itemStruct.video?.definition,
|
|
56
|
+
coverUrl: itemStruct.video?.cover,
|
|
57
|
+
subtitles: itemStruct.video?.subtitleInfos?.map(sub => ({
|
|
58
|
+
language: sub.LanguageCodeName, url: sub.Url, format: sub.Format, source: sub.Source
|
|
59
|
+
})) || []
|
|
60
|
+
},
|
|
61
|
+
author: {
|
|
62
|
+
id: itemStruct.author?.id,
|
|
63
|
+
uniqueId: itemStruct.author?.uniqueId,
|
|
64
|
+
nickname: itemStruct.author?.nickname,
|
|
65
|
+
avatarThumb: itemStruct.author?.avatarThumb
|
|
66
|
+
},
|
|
67
|
+
music: {
|
|
68
|
+
id: itemStruct.music?.id,
|
|
69
|
+
title: itemStruct.music?.title,
|
|
70
|
+
authorName: itemStruct.music?.authorName,
|
|
71
|
+
playUrl: itemStruct.music?.playUrl,
|
|
72
|
+
isOriginal: itemStruct.music?.original
|
|
73
|
+
},
|
|
74
|
+
stats: {
|
|
75
|
+
likes: itemStruct.statsV2?.diggCount ?? itemStruct.stats?.diggCount,
|
|
76
|
+
shares: itemStruct.statsV2?.shareCount ?? itemStruct.stats?.shareCount,
|
|
77
|
+
comments: itemStruct.statsV2?.commentCount ?? itemStruct.stats?.commentCount,
|
|
78
|
+
plays: itemStruct.statsV2?.playCount ?? itemStruct.stats?.playCount,
|
|
79
|
+
collects: itemStruct.statsV2?.collectCount ?? itemStruct.stats?.collectCount,
|
|
80
|
+
reposts: itemStruct.statsV2?.repostCount
|
|
81
|
+
},
|
|
82
|
+
locationCreated: itemStruct.locationCreated,
|
|
83
|
+
videoBuffer: null
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (videoUrlToDownload) {
|
|
87
|
+
try {
|
|
88
|
+
const videoResponse = await apiClient.get(videoUrlToDownload, {
|
|
89
|
+
responseType: 'arraybuffer',
|
|
90
|
+
headers: {
|
|
91
|
+
'Referer': url,
|
|
92
|
+
'Range': 'bytes=0-'
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (videoResponse.status === 200 || videoResponse.status === 206) {
|
|
97
|
+
importantData.videoBuffer = Buffer.from(videoResponse.data);
|
|
98
|
+
importantData.videoInfo.size = videoResponse.data.length;
|
|
99
|
+
}
|
|
100
|
+
} catch (videoError) {
|
|
101
|
+
console.error(`Error downloading video: ${videoError.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
creator: '@fort.kun',
|
|
107
|
+
status: true,
|
|
108
|
+
data: importantData
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
creator: '@fort.kun',
|
|
114
|
+
status: false,
|
|
115
|
+
data: null
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = tiktokDownloader;
|