@zenaveline/scraper 1.1.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  ## 🚀 Fitur Unggulan
19
19
  - **⚡ Super Ringan & Cepat:** Dibuat menggunakan library andalan.
20
20
  - **đŸ“Ļ Support Dual Module:** Bisa pakai `import` (ESM) atau `require` (CommonJS).
21
- - **💡 Developer Friendly:** Sintaks yang sederhana dan to-the-point menggunakan Promise (Async/Await).
21
+ - **💡 Developer Friendly:** Sintaks yang sederhana dan mudah dipahami.
22
22
  - **🌐 Beragam Layanan:** Mulai dari Instagram, Threads, Twitter (X), Pinterest, hingga Apple Music & AI dan lain lain.
23
23
 
24
24
  ## đŸ“Ļ Instalasi
@@ -33,26 +33,44 @@ yarn add @zenaveline/scraper
33
33
 
34
34
  ## 📖 Quick Start
35
35
 
36
- Cara panggilnya sangat praktis! Gunakan _destructuring_ untuk mengimpor fungsi yang spesifik kamu butuhkan.
36
+ Cara panggilnya sangat praktis! Kamu bebas menamai *import*-nya sesuai selera (contoh: `scraper` atau nama lain).
37
37
 
38
38
  ### 🌟 ESM (ECMAScript Modules)
39
39
  Untuk project berformat module (atau menggunakan TypeScript):
40
40
 
41
41
  ```javascript
42
- import { igdl } from '@zenaveline/scraper';
42
+ import scraper from '@zenaveline/scraper'
43
43
 
44
- const data = await igdl('https://www.instagram.com/p/url-postingan');
45
- console.log(data); // JSON
44
+ const data = await scraper.igdl('https://www.instagram.com/p/url-postingan')
45
+ console.log(data) // output
46
46
  ```
47
47
 
48
48
  ### đŸ’ģ CommonJS (CJS)
49
49
  Untuk project standar Node.js (seperti bot WhatsApp pada umumnya):
50
50
 
51
51
  ```javascript
52
- const { igdl } = require('@zenaveline/scraper');
52
+ const scraper = require('@zenaveline/scraper')
53
53
 
54
- const data = await igdl('https://www.instagram.com/p/url-postingan');
55
- console.log(data); // JSON
54
+ const data = await scraper.igstalk('prabowo')
55
+ console.log(data); // output
56
+ ```
57
+
58
+ ### đŸŽļ Khusus Penggunaan Spotify API
59
+
60
+ ```javascript
61
+ const scraper = require('@zenaveline/scraper')
62
+
63
+ const spotify = new scraper.spotify()
64
+
65
+ // Contoh pencarian lagu
66
+ const searchResult = await spotify.search('a thousand years jvke')
67
+ console.log(searchResult)
68
+
69
+ // Contoh Mengambil data spesifik berdasarkan ID
70
+ const trackData = await spotify.track('23WxsmliKISYaCfam8iPPG')
71
+ const artistData = await spotify.artist('164Uj4eKjl6zTBKfJLFKKK')
72
+ const albumData = await spotify.album('7z2Nuz41dkU9ruY5Qkva2L')
73
+ const playlistData = await spotify.playlist('4IfUIdWEonPys9mYs7zXna')
56
74
  ```
57
75
 
58
76
  ---
@@ -69,6 +87,9 @@ Panggil nama fungsinya sesuai daftar di bawah ini untuk menikmati semua fiturnya
69
87
  | `threadsdownload(url)` | Ambil media foto/video dari postingan Threads secara instan. |
70
88
  | `twetterdownload(url)` | Unduh postingan video/gambar dari platform X (Twitter). |
71
89
  | `pinterestdownload(url)`| Ekstrak media gambar dan video dengan kualitas jernih dari Pinterest. |
90
+ | `douyindl(url)` | Download video Douyin tanpa watermark. |
91
+ | `allinonedownloader(url, tool)` | Downloader all-in-one yang mendukung banyak platform (Tiktok, YouTube, IG, dll). |
92
+ | `tiktokdownload(url)` | Download video/foto Tiktok tanpa watermark. |
72
93
 
73
94
  ### đŸŽĩ Musik & Audio
74
95
  | Fungsi | Deskripsi Singkat |
@@ -76,6 +97,9 @@ Panggil nama fungsinya sesuai daftar di bawah ini untuk menikmati semua fiturnya
76
97
  | `applemusicdl(url)` | Unduh audio lagu favoritmu langsung dari Apple Music (hingga 320kbps). |
77
98
  | `geniussearch(query)` | Temukan lagu dan metadata sang artis lewat API Genius. |
78
99
  | `geniusdetail(id)` | Dapatkan detail lagu beserta lirik utuhnya via Genius ID. |
100
+ | `spotify` | Class API untuk fitur Spotify (search, track, artist, album, playlist). Gunakan dengan `new scraper.spotify()`. |
101
+ | `spotifydl(url)` | Unduh track dari Spotify (mendukung URL Track). |
102
+ | `spotifycard(options)` | Buat gambar kartu keren lagu Spotify menggunakan metadata musik. |
79
103
 
80
104
  ### 🤖 AI & Image Tools
81
105
  | Fungsi | Deskripsi Singkat |
@@ -87,6 +111,7 @@ Panggil nama fungsinya sesuai daftar di bawah ini untuk menikmati semua fiturnya
87
111
  ### â„šī¸ Utility
88
112
  | Fungsi | Deskripsi Singkat |
89
113
  | :--- | :--- |
114
+ | `bypasstools(url)` | Bypass shortlink URL (support sf.gl, sub2unlock, tinyurl, linkvertise, dll). |
90
115
  | `docs()` | Gunakan fungsi ini untuk melihat *list* lengkap seluruh fungsi scraper yang didukung secara terprogram. |
91
116
 
92
117
  > **Catatan Penting:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenaveline/scraper",
3
- "version": "1.1.1",
3
+ "version": "1.2.2",
4
4
  "description": "Kumpulan scraper yang mudah digunakan!",
5
5
  "keywords": [
6
6
  "@zenaveline/scraper",
@@ -23,6 +23,7 @@
23
23
  "dependencies": {
24
24
  "axios": "^1.6.0",
25
25
  "cheerio": "^1.0.0",
26
- "crypto": "^1.0.1"
26
+ "crypto": "^1.0.1",
27
+ "sharp": "^0.33.2"
27
28
  }
28
29
  }
@@ -0,0 +1,66 @@
1
+ /*
2
+ Base : https://wowdownloader.com
3
+ Author : Neo
4
+ WhatsApp Channel: https://whatsapp.com/channel/0029Vb6hVYK8V0tkiz4bKs0N
5
+ Support:
6
+ Tiktok
7
+ Twitter
8
+ Facebook
9
+ Instagram
10
+ Pinterest
11
+ LinkedIn
12
+ Douyin
13
+ Likee
14
+ SoundCloud
15
+ Spotify
16
+ Apple Music
17
+ YouTube
18
+ */
19
+ const axios = require('axios');
20
+
21
+ const tools = [
22
+ 'apple-music-downloader', 'douyin-downloader', 'facebook-video-downloader',
23
+ 'instagram-reels-downloader', 'instagram-story-downloader', 'instagram-video-downloader',
24
+ 'likee-downloader', 'linkedin-video-downloader', 'pinterest-video-downloader',
25
+ 'soundcloud-downloader', 'spotify-downloader', 'tiktok-photo-downloader',
26
+ 'tiktok-story-downloader', 'tiktok-video-downloader', 'twitter-gif-downloader',
27
+ 'twitter-video-downloader', 'youtube-monetization-checker', 'youtube-money-calculator',
28
+ 'youtube-tags-extractor', 'youtube-thumbnail-downloader', 'youtube-transcript',
29
+ 'youtube-video-downloader'
30
+ ];
31
+
32
+ async function wowdownloader(url, tool) {
33
+ try {
34
+ if (!url.includes('https://')) throw new Error('Invalid url.');
35
+ if (!tools.includes(tool)) throw new Error('Invalid tool.');
36
+
37
+ const { data: html, headers } = await axios.get(`https://wowdownloader.com/tool/${tool}`, {
38
+ headers: {
39
+ 'user-agent': 'Neo/1.0'
40
+ }
41
+ });
42
+
43
+ const csrfToken = html.match(/<meta name="csrf-token" content="([^"]+)">/)?.[1];
44
+ if (!csrfToken) throw new Error('Failed to get token.');
45
+
46
+ const { data } = await axios.post('https://wowdownloader.com/api/download', {
47
+ url: url,
48
+ tool: tool
49
+ }, {
50
+ headers: {
51
+ origin: 'https://wowdownloader.com',
52
+ referer: `https://wowdownloader.com/tool/${tool}`,
53
+ 'x-csrf-token': csrfToken,
54
+ cookie: headers['set-cookie']?.map(c => c.split(';')[0]).join('; '),
55
+ 'content-type': 'application/json',
56
+ 'user-agent': 'Neo/1.0'
57
+ }
58
+ });
59
+
60
+ return data;
61
+ } catch (error) {
62
+ throw new Error(error.response?.data?.error || error.message);
63
+ }
64
+ }
65
+
66
+ module.exports = wowdownloader;
@@ -0,0 +1,35 @@
1
+ //credit by nazir
2
+ //support sf.gl, sub2unlock, tinyurl, linkvertise dll
3
+
4
+ const crypto = require("crypto");
5
+
6
+ async function bypass(url, androidId = crypto.randomBytes(16).toString("hex")) {
7
+ const deviceId = crypto.createHash("sha256").update(`bypasstools:${androidId}`).digest("hex");
8
+ const { sessionToken } = await fetch("https://bypass.tools/api/mobile/init", {
9
+ method: "POST",
10
+ headers: { "Content-Type": "application/json" },
11
+ body: JSON.stringify({
12
+ deviceId,
13
+ platform: "android",
14
+ appVersion: "1.0.0"
15
+ })
16
+ }).then(r => r.json());
17
+ return fetch("https://bypass.tools/api/mobile/bypass", {
18
+ method: "POST",
19
+ headers: {
20
+ "Content-Type": "application/json",
21
+ Authorization: `Bearer ${sessionToken}`,
22
+ "X-Device-ID": deviceId
23
+ },
24
+ body: JSON.stringify({
25
+ url,
26
+ forceRefresh: false
27
+ })
28
+ }).then(async r => {
29
+ const d = await r.json();
30
+ if (!r.ok) throw new Error(d.message || "Bypass failed");
31
+ return d.result;
32
+ });
33
+ }
34
+
35
+ module.exports = bypass;
@@ -0,0 +1,69 @@
1
+ //Š Nazir
2
+
3
+ async function getAwemeId(input) {
4
+ if (/^\d+$/.test(input)) return input;
5
+ if (input.includes('v.douyin.com')) {
6
+ const res = await fetch(input, { redirect: 'follow' });
7
+ input = res.url;
8
+ }
9
+
10
+ const match = input.match(/\/video\/(\d+)/);
11
+ if (match) return match[1];
12
+
13
+ throw new Error("Gagal mengekstrak ID dari URL.");
14
+ }
15
+
16
+ async function douyin(input) {
17
+ try {
18
+ const awemeId = await getAwemeId(input);
19
+ const url = "https://so.douyin.com/aweme/v1/web/aweme/detail/";
20
+
21
+ const headers = {
22
+ "content-type": "application/x-www-form-urlencoded",
23
+ "accept": "application/json",
24
+ "user-agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36"
25
+ };
26
+ const body = `aweme_id=${awemeId}&request_source=280`;
27
+
28
+ const response = await fetch(url, {
29
+ method: "POST",
30
+ headers: headers,
31
+ body: body
32
+ });
33
+
34
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
35
+
36
+ const data = await response.json();
37
+
38
+ if (!data.aweme_detail) {
39
+ throw new Error("Metadata tidak ditemukan.");
40
+ }
41
+
42
+ const videoData = data.aweme_detail.video;
43
+ const desc = data.aweme_detail.desc || "no_description";
44
+ const author = data.aweme_detail.author ? data.aweme_detail.author.nickname : "unknown";
45
+ const videoUrl = videoData.play_addr.url_list[0];
46
+
47
+ return {
48
+ status: "success",
49
+ video_id: awemeId,
50
+ metadata: {
51
+ description: desc,
52
+ author: author,
53
+ author_id: data.aweme_detail.author ? data.aweme_detail.author.unique_id : null,
54
+ statistics: data.aweme_detail.statistics,
55
+ cover: videoData.cover.url_list[0],
56
+ duration: Math.round(videoData.duration / 1000) + "s",
57
+ video_url: videoUrl
58
+ }
59
+ };
60
+
61
+ } catch (error) {
62
+ return {
63
+ status: "error",
64
+ message: error.message
65
+ };
66
+ }
67
+ }
68
+
69
+ module.exports = douyin;
package/src/igstalk.js CHANGED
@@ -1,67 +1,59 @@
1
- /*
2
- Base : https://instagram.com
3
- Author : ZenzzXD
4
- Test code nya di masing" ya jangan di website
5
- */
6
-
7
- const axios = require('axios')
8
- const cheerio = require('cheerio')
9
-
10
- const headers = {
11
- 'host': 'www.instagram.com',
12
- 'dpr': '1.962499976158142',
13
- 'viewport-width': '980',
14
- 'sec-ch-ua': '"Chromium";v="139", "Not;A=Brand";v="99"',
15
- 'sec-ch-ua-mobile': '?1',
16
- 'sec-ch-ua-platform': '"Android"',
17
- 'sec-ch-ua-platform-version': '"13.0.0"',
18
- 'sec-ch-ua-model': '"RMX3085"',
19
- 'sec-ch-ua-full-version-list': '"Chromium";v="139.0.7339.0", "Not;A=Brand";v="99.0.0.0"',
20
- 'sec-ch-prefers-color-scheme': 'dark',
21
- 'upgrade-insecure-requests': '1',
22
- 'user-agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36',
23
- 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
24
- 'sec-fetch-site': 'same-origin',
25
- 'sec-fetch-mode': 'navigate',
26
- 'sec-fetch-user': '?1',
27
- 'sec-fetch-dest': 'document',
28
- 'accept-encoding': 'gzip, deflate, br',
29
- 'accept-language': 'id-ID,id;q=0.9,en-AU;q=0.8,en;q=0.7,en-US;q=0.6',
30
- 'cookie': 'csrftoken=UhNYfmpbN4zFIUATif4lcT; datr=aisGabstXUMaPhnsc3rGDr3V; ig_did=8961CBBD-2C6B-486E-97C0-61557817C61A; ps_l=1; ps_n=1; mid=aZVbfgABAAHGlIdJc5uThx4NQ4Qv; wd=551x1096; dpr=1.962499976158142',
31
- 'pragma': 'akamai-x-cache-on, akamai-x-cache-remote-on, akamai-x-check-cacheable, akamai-x-get-cache-key, akamai-x-get-extracted-values, status-id, akamai-x-get-true-cache-key, akamai-x-serial-no, akamai-x-get-request-id,akamai-x-get-nonces,akamai-x-get-client-ip,akamai-x-feo-trace'
32
- }
33
-
34
- async function instagram(username) {
35
- const r = await axios.get(`https://www.instagram.com/${username}`, { headers })
36
- const $ = cheerio.load(r.data)
37
-
38
- const title = $('meta[property="og:title"]').attr('content') || ''
39
- const description = $('meta[property="og:description"]').attr('content') || ''
40
- const image = $('meta[property="og:image"]').attr('content') || ''
41
- const url = $('meta[property="og:url"]').attr('content') || ''
42
- const nameDescription = $('meta[name="description"]').attr('content') || ''
43
-
44
- const followers = description.match(/([\d.,KMB]+)\s+(Pengikut|Followers)/i)?.[1] || '0'
45
- const following = description.match(/([\d.,KMB]+)\s+(Mengikuti|Following)/i)?.[1] || '0'
46
- const posts = description.match(/([\d.,KMB]+)\s+(Postingan|Posts)/i)?.[1] || '0'
47
-
48
- const nameUsernameMatch = title.match(/^([^(]+)\s+\(([^)]+)\)/)
49
- const name = nameUsernameMatch?.[1]?.trim() || ''
50
- const parsedUsername = nameUsernameMatch?.[2]?.replace('@', '').trim() || username
51
-
52
- const bioMatch = nameDescription.match(/(?:di Instagram|on Instagram):\s*["']?([^"']+)["']?$/i)
53
- const bio = bioMatch?.[1] || ''
54
-
55
- return {
56
- name,
57
- username: parsedUsername,
58
- followers,
59
- following,
60
- posts,
61
- bio,
62
- profilePic: image,
63
- url: url || `https://www.instagram.com/${username}`
1
+ const axios = require('axios');
2
+ const crypto = require('crypto');
3
+
4
+ async function igStalk(username) {
5
+ const [ky, tx] = ['2180a186d666f77b2c2af70e0098166d2b43d1e0a7b199776b07b98bf8351f37', 1781788719883]
6
+
7
+ try {
8
+ const client = axios.create({
9
+ baseURL: 'https://cors.caliph.my.id/https://api-wh.storiesig.info/api/v1/instagram',
10
+ headers: {
11
+ 'accept': 'application/json, text/plain, */*',
12
+ 'accept-language': 'id,id-ID;q=0.9,en-US;q=0.8,en;q=0.7',
13
+ 'origin': 'https://storiesig.info',
14
+ 'referer': 'https://storiesig.info/',
15
+ 'user-agent': 'Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36',
16
+ 'sec-ch-ua': '"Chromium";v="149", "Google Chrome";v="149", "Not/A)Brand";v="99"',
17
+ 'sec-ch-ua-mobile': '?0',
18
+ 'sec-ch-ua-platform': '"Linux"',
19
+ 'sec-fetch-dest': 'empty',
20
+ 'sec-fetch-mode': 'cors',
21
+ 'sec-fetch-site': 'same-site'
22
+ },
23
+ });
24
+
25
+ client.interceptors.request.use((config) => {
26
+ const bd = config.data ?? {};
27
+ const ts = Date.now();
28
+ const mg = JSON.stringify(Object.keys(bd).sort().reduce((a, k) => { a[k] = bd[k]; return a; }, {})) + ts;
29
+ const sg = crypto.createHmac('sha256', Buffer.from(ky, 'hex')).update(mg).digest('hex');
30
+ config.data = { ...bd, ts, _ts: tx, _tsc: 0, _sv: 2, _s: sg };
31
+ return config;
32
+ });
33
+
34
+ const { data: usrinf } = await client.post('userInfo', { username });
35
+
36
+ if ((!usrinf.sucess && usrinf?.result <= 0) || !usrinf.result?.find(p => p?.user?.username === username)) {
37
+ return {
38
+ status: false,
39
+ msg: 'User not found'
40
+ }
41
+ }
42
+
43
+ const [{ data: stories }, { data: posts }] = await Promise.all([
44
+ client.post('stories', { username }),
45
+ client.post('postsV2', { username, maxId: '' })
46
+ ]);
47
+
48
+ return {
49
+ status: true,
50
+ ...usrinf.result[0].user,
51
+ stories: stories.result,
52
+ posts: posts.result
53
+ };
54
+ } catch (error) {
55
+ throw error
64
56
  }
65
57
  }
66
58
 
67
- module.exports = instagram
59
+ module.exports = igStalk;
package/src/spotify.js ADDED
@@ -0,0 +1,374 @@
1
+ const axios = require("axios");
2
+ const crypto = require("crypto");
3
+
4
+ class Parser {
5
+ _getImg(o) {
6
+ return (o?.sources || []).map(s => ({
7
+ url: s.url,
8
+ width: s.width || s.maxWidth || null,
9
+ height: s.height || s.maxHeight || null
10
+ }));
11
+ }
12
+
13
+ _getCol(o) {
14
+ return o?.extractedColors?.colorRaw?.hex || o?.extractedColors?.colorDark?.hex || null;
15
+ }
16
+
17
+ _getVI(v) {
18
+ return v?.squareCoverImage?.extractedColorSet ? {
19
+ text_color: v.squareCoverImage.extractedColorSet.encoreBaseSetTextColor || null,
20
+ high_contrast: v.squareCoverImage.extractedColorSet.highContrast || null,
21
+ higher_contrast: v.squareCoverImage.extractedColorSet.higherContrast || null,
22
+ min_contrast: v.squareCoverImage.extractedColorSet.minContrast || null
23
+ } : null;
24
+ }
25
+
26
+ _getLink(uri) {
27
+ if (!uri) return { id: null, url: null };
28
+ const p = uri.split(':');
29
+ return {
30
+ uri,
31
+ id: p[2] || null,
32
+ url: p[2] ? `https://open.spotify.com/${p[1]}/${p[2]}` : null
33
+ };
34
+ }
35
+
36
+ parseSearch(res) {
37
+ if (!res) return null;
38
+
39
+ const parse = (arr, mapFn, isTrack = false) => (arr || []).reduce((acc, node) => {
40
+ const d = isTrack ? node.item?.data : node.data;
41
+ if (d) acc.push({ ...mapFn(d), ...(node.matchedFields && { matched_fields: node.matchedFields }) });
42
+ return acc;
43
+ }, []);
44
+
45
+ const trackItems = res.tracksV2?.items?.length ? res.tracksV2.items : res.topResultsV2?.itemsV2?.filter(i => i.item?.__typename === "TrackResponseWrapper");
46
+
47
+ return {
48
+ top_results: (res.topResultsV2?.itemsV2 || []).reduce((acc, node) => {
49
+ const wrap = node.item;
50
+ const d = wrap?.data;
51
+ if (!d) return acc;
52
+ const type = wrap.__typename?.replace('ResponseWrapper', '') || 'Unknown';
53
+ acc.push({
54
+ type: type, ...this._getLink(d.uri),
55
+ name: d.name || d.profile?.name || d.displayName || null,
56
+ images: this._getImg(d.coverArt || d.visuals?.avatarImage || d.images?.items?.[0] || d.avatar),
57
+ matched_fields: node.matchedFields || []
58
+ });
59
+ return acc;
60
+ }, []),
61
+ tracks: parse(trackItems, t => ({
62
+ ...this._getLink(t.uri), name: t.name || null, duration_ms: t.duration?.totalMilliseconds || 0,
63
+ explicit: t.contentRating?.label === "EXPLICIT", media_type: t.trackMediaType || null,
64
+ playability: { playable: !!t.playability?.playable, reason: t.playability?.reason || null },
65
+ associations: { audio_count: t.associationsV3?.audioAssociations?.totalCount || 0, video_count: t.associationsV3?.videoAssociations?.totalCount || 0 },
66
+ artists: (t.artists?.items || []).map(a => ({ ...this._getLink(a.uri), uri: a.uri, name: a.profile?.name })),
67
+ album: {
68
+ ...this._getLink(t.albumOfTrack?.uri), name: t.albumOfTrack?.name || null,
69
+ images: this._getImg(t.albumOfTrack?.coverArt), color_dark: this._getCol(t.albumOfTrack?.coverArt), visual_identity: this._getVI(t.albumOfTrack?.visualIdentity)
70
+ },
71
+ sixteen_by_nine_cover: t.visualIdentity?.sixteenByNineCoverImage?.image?.data?.sources || []
72
+ }), true),
73
+ albums: parse(res.albumsV2?.items, a => ({
74
+ ...this._getLink(a.uri), name: a.name || null, type: a.type || null, release_year: a.date?.year || null,
75
+ playability: { playable: !!a.playability?.playable, reason: a.playability?.reason || null },
76
+ artists: (a.artists?.items || []).map(art => ({ ...this._getLink(art.uri), uri: art.uri, name: art.profile?.name })),
77
+ images: this._getImg(a.coverArt), color_dark: this._getCol(a.coverArt), visual_identity: this._getVI(a.visualIdentity)
78
+ })),
79
+ artists: parse(res.artists?.items, art => ({
80
+ ...this._getLink(art.uri), name: art.profile?.name || null, images: this._getImg(art.visuals?.avatarImage), color_dark: this._getCol(art.visuals?.avatarImage), visual_identity: this._getVI(art.visualIdentity)
81
+ })),
82
+ episodes: parse(res.episodes?.items, ep => ({
83
+ ...this._getLink(ep.uri), name: ep.name || null, description: ep.description || null, duration_ms: ep.duration?.totalMilliseconds || 0, explicit: ep.contentRating?.label === "EXPLICIT", media_types: ep.mediaTypes || [], release_date: ep.releaseDate?.isoString || null,
84
+ playability: { playable: ep.playability?.reason === "PLAYABLE", reason: ep.playability?.reason || null }, played_state: ep.playedState?.state || null, is_paywall: !!ep.restrictions?.paywallContent,
85
+ images: this._getImg(ep.coverArt), color_dark: this._getCol(ep.coverArt), visual_identity: this._getVI(ep.visualIdentity), video_preview_thumbnail: this._getImg(ep.videoPreviewThumbnail?.imagePreview?.data),
86
+ podcast: { ...this._getLink(ep.podcastV2?.data?.uri), name: ep.podcastV2?.data?.name || null, publisher: ep.podcastV2?.data?.publisher?.name || null, media_type: ep.podcastV2?.data?.mediaType || null }
87
+ })),
88
+ podcasts: parse(res.podcasts?.items, pod => ({
89
+ ...this._getLink(pod.uri), name: pod.name || null, publisher: pod.publisher?.name || null, media_type: pod.mediaType || null,
90
+ topics: (pod.topics?.items || []).map(t => ({ ...this._getLink(t.uri), uri: t.uri, title: t.title })),
91
+ images: this._getImg(pod.coverArt), color_dark: this._getCol(pod.coverArt), visual_identity: this._getVI(pod.visualIdentity)
92
+ })),
93
+ playlists: parse(res.playlists?.items, pl => ({
94
+ ...this._getLink(pl.uri), name: pl.name || null, description: pl.description || null, format: pl.format || null, attributes: pl.attributes || [],
95
+ images: this._getImg(pl.images?.items?.[0]), color_dark: this._getCol(pl.images?.items?.[0]), visual_identity: this._getVI(pl.visualIdentity),
96
+ owner: { ...this._getLink(pl.ownerV2?.data?.uri), display_name: pl.ownerV2?.data?.name || null, username: pl.ownerV2?.data?.username || null, images: this._getImg(pl.ownerV2?.data?.avatar) }
97
+ })),
98
+ genres: parse(res.genres?.items, g => ({
99
+ ...this._getLink(g.uri), name: g.name || null, images: this._getImg(g.image), color_dark: this._getCol(g.image)
100
+ })),
101
+ users: parse(res.users?.items, u => ({
102
+ ...this._getLink(u.uri), display_name: u.displayName || null, username: u.username || null, images: this._getImg(u.avatar), color_dark: this._getCol(u.avatar)
103
+ }))
104
+ };
105
+ }
106
+
107
+ parseTrack(data) {
108
+ const t = data?.track || data;
109
+ if (!t || t.__typename !== 'Track') return null;
110
+ const allArtists = [...(t.firstArtist?.items || []), ...(t.otherArtists?.items || [])];
111
+
112
+ return {
113
+ ...this._getLink(t.uri), name: t.name || null, duration_ms: t.duration?.totalMilliseconds || 0,
114
+ playcount: parseInt(t.playcount) || 0, explicit: t.contentRating?.label === "EXPLICIT", track_number: t.trackNumber || null,
115
+ album: {
116
+ ...this._getLink(t.albumOfTrack?.uri), name: t.albumOfTrack?.name || null, type: t.albumOfTrack?.type || null, release_year: t.albumOfTrack?.date?.year || null,
117
+ images: this._getImg(t.albumOfTrack?.coverArt), color: this._getCol(t.albumOfTrack?.coverArt), visual_identity: this._getVI(t.albumOfTrack?.visualIdentity)
118
+ },
119
+ artists: allArtists.map(node => ({ ...this._getLink(node.uri), name: node.profile?.name || null, images: this._getImg(node.visuals?.avatarImage) }))
120
+ };
121
+ }
122
+
123
+ parseArtist(data) {
124
+ const a = data?.artist || data;
125
+ if (!a || a.__typename !== 'Artist') return null;
126
+
127
+ return {
128
+ ...this._getLink(a.uri || `spotify:artist:${a.id}`), uri: a.uri || `spotify:artist:${a.id}`, name: a.profile?.name || null, verified: !!a.profile?.verified,
129
+ images: this._getImg(a.visuals?.avatarImage), header_images: this._getImg(a.visuals?.headerImage?.data || a.headerImage?.data), color: this._getCol(a.visuals?.avatarImage),
130
+ statistics: { followers: a.stats?.followers || 0, monthly_listeners: a.stats?.monthlyListeners || 0 },
131
+ top_tracks: (a.discography?.topTracks?.items || []).map(node => ({
132
+ ...this._getLink(node.track?.uri), name: node.track?.name || null, playcount: parseInt(node.track?.playcount) || 0, duration_ms: node.track?.duration?.totalMilliseconds || 0,
133
+ album: { ...this._getLink(node.track?.albumOfTrack?.uri), name: node.track?.albumOfTrack?.name || null, images: this._getImg(node.track?.albumOfTrack?.coverArt) }
134
+ }))
135
+ };
136
+ }
137
+
138
+ parseAlbum(data) {
139
+ const al = data?.albumUnion || data?.album || data;
140
+ if (!al || (al.__typename !== 'Album' && al.__typename !== 'AlbumRelease')) return null;
141
+
142
+ return {
143
+ ...this._getLink(al.uri), name: al.name || null, type: al.type || null,
144
+ release_date: al.date?.isoString || al.date?.year || null, label: al.label || null,
145
+ playability: { playable: !!al.playability?.playable, reason: al.playability?.reason || null },
146
+ images: this._getImg(al.coverArt), color: this._getCol(al.coverArt), visual_identity: this._getVI(al.visualIdentity),
147
+ artists: (al.artists?.items || []).map(art => ({ ...this._getLink(art.uri), name: art.profile?.name || null })),
148
+ copyrights: al.copyrights?.items || [],
149
+ tracks: (al.tracks?.items || al.tracksV2?.items || []).map(node => {
150
+ const t = node.track || node;
151
+ return {
152
+ ...this._getLink(t.uri), name: t.name || null, duration_ms: t.duration?.totalMilliseconds || 0,
153
+ playcount: parseInt(t.playcount) || 0, explicit: t.contentRating?.label === "EXPLICIT", track_number: t.trackNumber || null,
154
+ artists: (t.artists?.items || []).map(a => ({ ...this._getLink(a.uri), uri: a.uri, name: a.profile?.name }))
155
+ };
156
+ })
157
+ };
158
+ }
159
+
160
+ parsePlaylist(data) {
161
+ const pl = data?.playlistV2 || data?.playlist || data;
162
+ if (!pl || (pl.__typename !== 'Playlist' && pl.__typename !== 'PlaylistResponseWrapper')) return null;
163
+
164
+ return {
165
+ ...this._getLink(pl.uri), name: pl.name || null, description: pl.description || null, format: pl.format || null,
166
+ followers: pl.followers || pl.ownerV2?.data?.followers || 0,
167
+ images: this._getImg(pl.images?.items?.[0] || pl.image), color: this._getCol(pl.images?.items?.[0] || pl.image), visual_identity: this._getVI(pl.visualIdentity),
168
+ owner: {
169
+ ...this._getLink(pl.ownerV2?.data?.uri), display_name: pl.ownerV2?.data?.name || null, username: pl.ownerV2?.data?.username || null,
170
+ images: this._getImg(pl.ownerV2?.data?.avatar)
171
+ },
172
+ tracks: (pl.content?.items || pl.tracks?.items || []).map(node => {
173
+ const t = node.item?.data || node.track || node;
174
+ if (!t || t.__typename !== 'Track') return null;
175
+ return {
176
+ ...this._getLink(t.uri), name: t.name || null, duration_ms: t.duration?.totalMilliseconds || 0, explicit: t.contentRating?.label === "EXPLICIT",
177
+ album: { ...this._getLink(t.albumOfTrack?.uri), name: t.albumOfTrack?.name || null, images: this._getImg(t.albumOfTrack?.coverArt) },
178
+ artists: (t.artists?.items || []).map(a => ({ ...this._getLink(a.uri), uri: a.uri, name: a.profile?.name }))
179
+ };
180
+ }).filter(item => item !== null)
181
+ };
182
+ }
183
+ }
184
+
185
+
186
+ class Spotify {
187
+ constructor() {
188
+ this.cfg = {
189
+ secret: '376136387538459893883312310911992847112448894410210511297108',
190
+ version: 61,
191
+ client_version: '1.2.88.61.ge172202b',
192
+ query: {
193
+ search: {
194
+ opt: "searchDesktop",
195
+ sha: "21b3fe49546912ba782db5c47e9ef5a7dbd20329520ba0c7d0fcfadee671d24e"
196
+ },
197
+ track: {
198
+ opt: "getTrack",
199
+ sha: "612585ae06ba435ad26369870deaae23b5c8800a256cd8a57e08eddc25a37294",
200
+ },
201
+ artist: {
202
+ opt: "queryArtistOverview",
203
+ sha: "5b9e64f43843fa3a9b6a98543600299b0a2cbbbccfdcdcef2402eb9c1017ca4c"
204
+ },
205
+ album: {
206
+ opt: "getAlbum",
207
+ sha: "b9bfabef66ed756e5e13f68a942deb60bd4125ec1f1be8cc42769dc0259b4b10"
208
+ },
209
+ playlist: {
210
+ opt: "fetchPlaylist",
211
+ sha: "32b05e92e438438408674f95d0fdad8082865dc32acd55bd97f5113b8579092b"
212
+ }
213
+ }
214
+ };
215
+ this.is = axios.create({
216
+ headers: {
217
+ 'referer': 'https://open.spotify.com/',
218
+ 'origin': 'https://open.spotify.com',
219
+ 'content-type': 'application/json',
220
+ 'accept': 'application/json',
221
+ 'user-agent': 'Mozilla/5.0 (Linux; Android 16; NX729J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.34 Mobile Safari/537.36',
222
+ }
223
+ });
224
+ this.parser = new Parser();
225
+ }
226
+
227
+ generateTOTP(tsms) {
228
+ const counter = Math.floor((tsms / 1000) / 30);
229
+ const buffer = Buffer.alloc(8);
230
+ buffer.writeBigInt64BE(BigInt(counter));
231
+ const hmac = crypto.createHmac('sha1', Buffer.from(this.cfg.secret, "utf8")).update(buffer);
232
+ const digest = hmac.digest();
233
+ const offset = digest[digest.length - 1] & 0xf;
234
+ const code = (digest.readUInt32BE(offset) & 0x7fffffff) % 1000000;
235
+ return code.toString().padStart(6, '0');
236
+ }
237
+
238
+ async getToken() {
239
+ try {
240
+ if (this.is.defaults.headers.authorization) return true
241
+ const sts = Math.floor(Date.now() / 1000);
242
+ const { data: token } = await this.is.get("https://open.spotify.com/api/token", {
243
+ params: {
244
+ reason: "init",
245
+ productType: "web-player",
246
+ totp: this.generateTOTP(Date.now()),
247
+ totpServer: this.generateTOTP(sts * 1000),
248
+ totpVer: String(this.cfg.version)
249
+ }
250
+ });
251
+ const { data: client } = await this.is.post('https://clienttoken.spotify.com/v1/clienttoken', {
252
+ client_data: {
253
+ client_version: this.cfg.client_version,
254
+ client_id: token.clientId,
255
+ js_sdk_data: {
256
+ device_brand: "unknown",
257
+ device_model: "unknown",
258
+ os: "linux",
259
+ os_version: "24.04",
260
+ device_id: crypto.randomUUID(),
261
+ device_type: "computer"
262
+ }
263
+ }
264
+ });
265
+
266
+ Object.assign(this.is.defaults.headers, {
267
+ 'accept-language': 'en',
268
+ 'app-platform': 'WebPlayer',
269
+ 'authorization': `Bearer ${token.accessToken}`,
270
+ 'client-token': client.granted_token.token,
271
+ 'spotify-app-version': this.cfg.client_version
272
+ })
273
+ return true
274
+ } catch (error) {
275
+ return false;
276
+ }
277
+ }
278
+
279
+ async query(name, vars) {
280
+ try {
281
+ if (!(await this.getToken())) return;
282
+ const sel = this.cfg.query[name]
283
+
284
+ const { data: res } = await this.is.post('https://api-partner.spotify.com/pathfinder/v2/query', {
285
+ variables: vars,
286
+ operationName: sel.opt,
287
+ extensions: {
288
+ persistedQuery: {
289
+ version: 1,
290
+ sha256Hash: sel.sha
291
+ }
292
+ }
293
+ });
294
+
295
+ return res
296
+ } catch (error) {
297
+ throw error
298
+ }
299
+ }
300
+
301
+ async search(query) {
302
+ try {
303
+ const res = await this.query("search", {
304
+ searchTerm: query,
305
+ offset: 0,
306
+ limit: 10,
307
+ numberOfTopResults: 5,
308
+ includeAudiobooks: true,
309
+ includeArtistHasConcertsField: false,
310
+ includePreReleases: true,
311
+ includeAuthors: false,
312
+ includeEpisodeContentRatingsV2: false
313
+ });
314
+ return this.parser.parseSearch(res.data.searchV2)
315
+ } catch (error) {
316
+ throw error
317
+ }
318
+ }
319
+
320
+ async track(ids) {
321
+ try {
322
+ const res = await this.query("track", {
323
+ uri: `spotify:track:${ids}`
324
+ });
325
+ return this.parser.parseTrack(res.data.trackUnion)
326
+ } catch (error) {
327
+ throw error
328
+ }
329
+ }
330
+
331
+ async artist(ids) {
332
+ try {
333
+ const res = await this.query("artist", {
334
+ uri: `spotify:artist:${ids}`,
335
+ locale: "",
336
+ preReleaseV2: false
337
+ });
338
+ return this.parser.parseArtist(res.data.artistUnion)
339
+ } catch (error) {
340
+ throw error
341
+ }
342
+ }
343
+
344
+ async album(ids) {
345
+ try {
346
+ const res = await this.query("album", {
347
+ uri: `spotify:album:${ids}`,
348
+ locale: "",
349
+ offset: 0,
350
+ limit: 50
351
+ });
352
+ return this.parser.parseAlbum(res.data.albumUnion)
353
+ } catch (error) {
354
+ throw error
355
+ }
356
+ }
357
+
358
+ async playlist(ids) {
359
+ try {
360
+ const res = await this.query("playlist", {
361
+ uri: `spotify:playlist:${ids}`,
362
+ offset: 0,
363
+ limit: 25,
364
+ enableWatchFeedEntrypoint: false,
365
+ includeEpisodeContentRatingsV2: false
366
+ });
367
+ return this.parser.parsePlaylist(res.data.playlistV2)
368
+ } catch (error) {
369
+ throw error
370
+ }
371
+ }
372
+ }
373
+
374
+ module.exports = Spotify;
@@ -0,0 +1,142 @@
1
+ const sharp = require('sharp')
2
+ const axios = require('axios')
3
+
4
+ async function loadImageFromURL(url) {
5
+ if (!url) return null;
6
+ if (Buffer.isBuffer(url)) return url;
7
+ try {
8
+ const response = await axios.get(url, { responseType: 'arraybuffer', timeout: 4000 });
9
+ return Buffer.from(response.data, 'binary');
10
+ } catch (err) {
11
+ return null;
12
+ }
13
+ }
14
+
15
+ function truncateText(text, maxChars = 20) {
16
+ if (text.length <= maxChars) return text;
17
+ return text.slice(0, maxChars - 3).trim() + '...';
18
+ }
19
+
20
+ function wrapText(text, maxCharsPerLine = 22) {
21
+ if (!text) return [];
22
+ const words = text.split(' ');
23
+ const lines = [];
24
+ let currentLine = '';
25
+
26
+ for (const word of words) {
27
+ if ((currentLine + word).length > maxCharsPerLine) {
28
+ if (currentLine) lines.push(currentLine.trim());
29
+ currentLine = word + ' ';
30
+ } else {
31
+ currentLine += word + ' ';
32
+ }
33
+ }
34
+ if (currentLine) lines.push(currentLine.trim());
35
+ return lines;
36
+ }
37
+
38
+ const escapeXml = (unsafe) => (unsafe || "").replace(/[<>&'"]/g, c => {
39
+ switch (c) {
40
+ case '<': return '&lt;';
41
+ case '>': return '&gt;';
42
+ case '&': return '&amp;';
43
+ case '\'': return '&apos;';
44
+ case '"': return '&quot;';
45
+ }
46
+ });
47
+
48
+
49
+ async function drawCardSpotify({ bg, cover, title, artist }) {
50
+ const width = 320;
51
+ const height = 420;
52
+ const cardX = 20;
53
+ const cardY = 20;
54
+ const cardWidth = 280;
55
+ const cardHeight = 380;
56
+ const radius = 20;
57
+
58
+ let baseImageBuffer;
59
+ let bgColor = '#222222';
60
+
61
+ const coverBuffer = await loadImageFromURL(cover);
62
+ let dominantColor = bgColor;
63
+ if (coverBuffer) {
64
+ try {
65
+ const stats = await sharp(coverBuffer).stats();
66
+ const { r, g, b } = stats.dominant;
67
+ dominantColor = `rgb(${r}, ${g}, ${b})`;
68
+ } catch (e) { }
69
+ }
70
+
71
+ if (bg) {
72
+ const bgBuffer = await loadImageFromURL(bg);
73
+ if (bgBuffer) {
74
+ baseImageBuffer = await sharp(bgBuffer).resize(width, height, { fit: 'cover' }).toBuffer();
75
+ }
76
+ }
77
+
78
+ if (!baseImageBuffer && coverBuffer) {
79
+ baseImageBuffer = await sharp({ create: { width, height, channels: 4, background: dominantColor } }).png().toBuffer();
80
+ }
81
+
82
+ if (!baseImageBuffer) {
83
+ baseImageBuffer = await sharp({ create: { width, height, channels: 4, background: bgColor } }).png().toBuffer();
84
+ }
85
+
86
+ const composites = [];
87
+
88
+ const cardSvg = `<svg width="${width}" height="${height}">
89
+ <rect x="${cardX}" y="${cardY}" width="${cardWidth}" height="${cardHeight}" rx="${radius}" ry="${radius}" fill="rgba(0, 0, 0, 0.7)" />
90
+ </svg>`;
91
+ composites.push({ input: Buffer.from(cardSvg), top: 0, left: 0 });
92
+
93
+ if (coverBuffer) {
94
+ const resizedCover = await sharp(coverBuffer).resize(240, 240, { fit: 'cover' }).toBuffer();
95
+ composites.push({
96
+ input: resizedCover,
97
+ left: cardX + 20,
98
+ top: cardY + 20
99
+ });
100
+ }
101
+
102
+ let titleLines = wrapText(truncateText(title || "", 26), 20);
103
+ let artistLines = wrapText(truncateText(artist || ""), 28);
104
+
105
+ let currentY = cardY + 282;
106
+ let textSvgStr = `<svg width="${width}" height="${height}">
107
+ <style>
108
+ .t { font-family: sans-serif; font-weight: bold; font-size: 22px; fill: white; }
109
+ .a { font-family: sans-serif; font-size: 16px; fill: rgba(255, 255, 255, 0.8); }
110
+ </style>
111
+ `;
112
+
113
+ for (const line of titleLines) {
114
+ textSvgStr += `<text x="${cardX + 20}" y="${currentY}" class="t">${escapeXml(line)}</text>`;
115
+ currentY += 26;
116
+ }
117
+
118
+ currentY += 2;
119
+
120
+ for (const line of artistLines) {
121
+ textSvgStr += `<text x="${cardX + 20}" y="${currentY}" class="a">${escapeXml(line)}</text>`;
122
+ currentY += 20;
123
+ }
124
+
125
+ textSvgStr += `<text x="${cardX + 40}" y="${cardY + 370}" font-family="sans-serif" font-weight="bold" font-size="14px" fill="white">Spotify</text>
126
+ </svg>`;
127
+
128
+ composites.push({ input: Buffer.from(textSvgStr), top: 0, left: 0 });
129
+
130
+ const logoUrl = "https://storage.googleapis.com/pr-newsroom-wp/1/2023/05/Spotify_Primary_Logo_RGB_White-300x300.png";
131
+ const logoBuffer = await loadImageFromURL(logoUrl);
132
+ if (logoBuffer) {
133
+ const logoResized = await sharp(logoBuffer).resize(20, 20).toBuffer();
134
+ composites.push({ input: logoResized, top: cardY + 354, left: cardX + 14 });
135
+ }
136
+
137
+ const finalBuffer = await sharp(baseImageBuffer).composite(composites).png().toBuffer();
138
+
139
+ return finalBuffer;
140
+ }
141
+
142
+ module.exports = drawCardSpotify;
@@ -0,0 +1,25 @@
1
+ const axios = require("axios");
2
+
3
+ async function SpotifyDl(url) {
4
+ try {
5
+ const { data: pp } = await axios.post('https://gamepvz.com/api/download/get-url', {
6
+ url: url
7
+ }, {
8
+ 'headers': {
9
+ 'content-type': 'application/json',
10
+ 'user-agent': 'Mozilla/5.0 (Linux; Android 16; NX729J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.34 Mobile Safari/537.36',
11
+ }
12
+ })
13
+ return {
14
+ status: true,
15
+ title: pp.title,
16
+ author: pp.authorName,
17
+ cover: pp.coverUrl,
18
+ dl: atob(pp.originalVideoUrl.split('url=')[1])
19
+ }
20
+ } catch(e) {
21
+ return { status: false }
22
+ }
23
+ }
24
+
25
+ module.exports = SpotifyDl;
@@ -0,0 +1,56 @@
1
+ //credit: by Nazir
2
+
3
+ async function tt(url) {
4
+ const html = await fetch(url, {
5
+ headers: {
6
+ authority: "www.tiktok.com",
7
+ "sec-ch-ua-mobile": "?1",
8
+ "sec-ch-ua-platform": `"Android"`,
9
+ "user-agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36"
10
+ }
11
+ }).then(a => a.text());
12
+ const match = html.match(/<script id="__UNIVERSAL_DATA_FOR_REHYDRATION__"[^>]*>([\s\S]*?)<\/script>/);
13
+ if (!match) return null;
14
+ const json = JSON.parse(match[1]);
15
+ const data = json.__DEFAULT_SCOPE__["webapp.reflow.video.detail"].itemInfo.itemStruct;
16
+ let download = data.imagePost
17
+ ? data.imagePost.images.reduce((acc, img) => {
18
+ return acc.concat(img.imageURL.urlList);
19
+ }, [])
20
+ : await fetch(`https://www.tiktok.com/player/api/v1/items?item_ids=${data.id}`)
21
+ .then(a => a.json())
22
+ .then(b => b.items[0].video_info.url_list[0]);
23
+ return {
24
+ id: data.id || data.aweme_id || null,
25
+ like: data.stats.diggCount || 0,
26
+ views: data.stats.playCount || data.play || 0,
27
+ share: data.stats.shareCount || 0,
28
+ comment: data.stats.commentCount || 0,
29
+ isVideo: data.imagePost ? false : true,
30
+ title: data.desc || data.suggestedWords?.[0] || "",
31
+ region: data.locationCreated || null,
32
+ duration: `${data.duration || data.music?.duration || 0} second`,
33
+ download: download,
34
+ author: {
35
+ id: data.author.id || "",
36
+ avatar: data.author?.avatarThumb,
37
+ nickname: data.author?.nickname || "",
38
+ username: data.author.uniqueId || "",
39
+ followers: data.author.followerCount || 0,
40
+ following: data.author.followingCount || 0,
41
+ like: data.author.heartCount || 0,
42
+ verified: data.author.verified,
43
+ videoCount: data.author.videoCount || 0
44
+ },
45
+ music: {
46
+ id: data.music?.id || null,
47
+ title: data.music?.title || "",
48
+ author: data.music?.authorName || "",
49
+ thumbnail: data.music?.coverLarge || data.music?.coverMedium || data.music?.coverThumb || null,
50
+ duration: data.music?.duration + " second" || "",
51
+ url: data.music?.playUrl || null
52
+ }
53
+ };
54
+ }
55
+
56
+ module.exports = tt;