@danonino/moon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ /*
2
+ Base : https://id.pinterest.com/
3
+ Author : ZenzzXD
4
+ Jangan lupa selalu tes code nya
5
+ inspired by wolep : https://pastebin.com/NLpQ1REf
6
+ */
7
+
8
+ const axios = require('axios')
9
+
10
+ const headers = {
11
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36',
12
+ 'Accept-Language': 'id-ID,id;q=0.9,en-AU;q=0.8,en;q=0.7,en-US;q=0.6',
13
+ 'Content-Type': 'application/json',
14
+ 'x-csrftoken': 'daaaed19c58a2787b0d6a23620be18e1',
15
+ 'Cookie': 'csrftoken=daaaed19c58a2787b0d6a23620be18e1; _auth=1'
16
+ }
17
+
18
+ async function parsePinterestId(url) {
19
+ let finalUrl = url
20
+ if (/pin\.it/i.test(url)) {
21
+ const r = await axios.head(url, { maxRedirects: 5 })
22
+ finalUrl = r.request?.res?.responseUrl || r.config?.url
23
+ }
24
+ return finalUrl.match(/\/pin\/(\d+)/)?.[1]
25
+ }
26
+
27
+ function serialize(d1, d2) {
28
+ const a = d1?.data?.v3GetPinQueryv2?.data
29
+ const b = d2?.data?.v3GetPinQueryv2?.data
30
+
31
+ const user = {
32
+ fullName: b?.pinner?.fullName || b?.nativeCreator?.fullName || a?.closeupAttribution?.fullName || a?.nativeCreator?.fullName || '(unknown)',
33
+ username: b?.pinner?.username || a?.nativeCreator?.username || a?.pinner?.username || '(unknown)'
34
+ }
35
+
36
+ const post = {
37
+ title: b?.title?.trim() || b?.closeupUnifiedDescription?.trim() || b?.description?.trim() || '(no title)',
38
+ description: b?.description?.trim() || b?.closeupUnifiedDescription?.trim() || '',
39
+ likesCount: b?.totalReactionCount || 0,
40
+ commentCount: b?.aggregatedPinData?.commentCount || 0,
41
+ createdAt: b?.createdAt || '(unknown)'
42
+ }
43
+
44
+ const v = b?.storyPinData?.pages?.[0]?.blocks?.[0]?.videoDataV2?.videoList720P?.v720P
45
+ || b?.videos?.videoList?.v720P
46
+
47
+ const content = {
48
+ images: Object.keys(a || {}).filter(k => k.startsWith('images_')).map(k => ({ ...a[k], name: k.replace('images_', '') })),
49
+ videos: v ? [v] : []
50
+ }
51
+
52
+ return { user, post, content }
53
+ }
54
+
55
+ async function pinterest(pinurl) {
56
+ const pinId = await parsePinterestId(pinurl)
57
+
58
+ const [r1, r2] = await Promise.all([
59
+ axios.post('https://id.pinterest.com/_/graphql/', {
60
+ queryHash: '5444a9d6e1f023c6785830bbadc6f60fe2bb7a8775b86f77905d400cfb06991b',
61
+ variables: { pinId, isAuth: true, isDesktop: false, isUnauth: false, shouldPrefetchStoryPinFragment: false, shouldSkipImageViewerOnPageQuery: true }
62
+ }, { headers }),
63
+ axios.post('https://id.pinterest.com/_/graphql/', {
64
+ queryHash: 'a03317b3c9329575ec06fe3aeff2a3f194dae93a4eaaf4d16eab671fd2efd198',
65
+ variables: { pinId, isAuth: true, isDesktop: false, isUnauth: false, shouldDefer: false, shouldFetchAIInsight: false, shouldShowSeoDrawerOption: false }
66
+ }, { headers })
67
+ ])
68
+
69
+ return serialize(r1.data, r2.data)
70
+ }
71
+
72
+ module.exports = pinterest
@@ -0,0 +1,32 @@
1
+ const fs = require('fs')
2
+
3
+ async function pixa(img) {
4
+ const form = new FormData()
5
+ form.append('image', new Blob([fs.readFileSync(img)], { type: 'image/jpeg' }), img.split('/').pop())
6
+ form.append('format', 'png')
7
+ form.append('model', 'v1')
8
+
9
+ const res = await fetch('https://api2.pixelcut.app/image/matte/v1', {
10
+ method: 'POST',
11
+ headers: {
12
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36',
13
+ 'Accept': 'application/json, text/plain, */*',
14
+ 'sec-ch-ua': '"Chromium";v="139", "Not;A=Brand";v="99"',
15
+ 'x-locale': 'en',
16
+ 'x-client-version': 'web:pixa.com:4a5b0af2',
17
+ 'sec-ch-ua-mobile': '?1',
18
+ 'sec-ch-ua-platform': '"Android"',
19
+ 'origin': 'https://www.pixa.com',
20
+ 'sec-fetch-site': 'cross-site',
21
+ 'sec-fetch-mode': 'cors',
22
+ 'sec-fetch-dest': 'empty',
23
+ 'referer': 'https://www.pixa.com/',
24
+ 'accept-language': 'id-ID,id;q=0.9,en-AU;q=0.8,en;q=0.7,en-US;q=0.6'
25
+ },
26
+ body: form
27
+ })
28
+
29
+ return Buffer.from(await res.arrayBuffer())
30
+ }
31
+
32
+ module.exports = pixa
@@ -0,0 +1,85 @@
1
+ const crypto = require("crypto")
2
+ const axios = require("axios")
3
+
4
+ class savetube {
5
+ constructor() {
6
+ this.ky = 'C5D58EF67A7584E4A29F6C35BBC4EB12';
7
+ this.fmt = ['144', '240', '360', '480', '720', '1080', 'mp3'];
8
+ this.m = /^((?:https?:)?\/\/)?((?:www|m|music)\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?(?:embed\/)?(?:v\/)?(?:shorts\/)?([a-zA-Z0-9_-]{11})/;
9
+ this.is = axios.create({
10
+ headers: {
11
+ 'content-type': 'application/json',
12
+ 'origin': 'https://yt.savetube.me',
13
+ 'user-agent': 'Mozilla/5.0 (Android 15; Mobile; SM-F958; rv:130.0) Gecko/130.0 Firefox/130.0'
14
+ }
15
+ })
16
+ }
17
+
18
+ async decrypt(enc) {
19
+ try {
20
+ const [sr, ky] = [Buffer.from(enc,'base64'), Buffer.from(this.ky,'hex')]
21
+ const [iv, dt] = [sr.slice(0,16), sr.slice(16)]
22
+ const dc = crypto.createDecipheriv('aes-128-cbc', ky, iv);
23
+ return JSON.parse(Buffer.concat([dc.update(dt), dc.final()]).toString());
24
+ } catch (e) {
25
+ throw new Error(`Error while decrypting data: ${e.message}`);
26
+ }
27
+ }
28
+
29
+ async getCdn() {
30
+ const response = await this.is.get("https://media.savetube.vip/api/random-cdn");
31
+ if (!response.status) return response;
32
+ return {
33
+ status: true,
34
+ data: response.data.cdn
35
+ };
36
+ }
37
+
38
+ async download(url, format = 'mp3') {
39
+ const id = url.match(this.m)?.[3];
40
+ if (!id) {
41
+ return {
42
+ status: false,
43
+ msg: "ID cannot be extracted from url"
44
+ };
45
+ }
46
+ if (!format || !this.fmt.includes(format)) {
47
+ return {
48
+ status: false,
49
+ msg: "Formats not found",
50
+ list: this.fmt
51
+ };
52
+ }
53
+ try {
54
+ const u = await this.getCdn();
55
+ if (!u.status) return u;
56
+ const res = await this.is.post(`https://${u.data}/v2/info`, {
57
+ url: `https://www.youtube.com/watch?v=${id}`
58
+ });
59
+ const dec = await this.decrypt(res.data.data);
60
+ const dl = await this.is.post(`https://${u.data}/download`, {
61
+ id: id,
62
+ downloadType: format === 'mp3' ? 'audio' : 'video',
63
+ quality: format === 'mp3' ? '320' : format,
64
+ key: dec.key
65
+ })
66
+
67
+ return {
68
+ status: true,
69
+ title: dec.title,
70
+ format: format,
71
+ thumb: dec.thumbnail || `https://i.ytimg.com/vi/${id}/hqdefault.jpg`,
72
+ duration: dec.duration,
73
+ cached: dec.fromCache,
74
+ dl: dl.data.data.downloadUrl
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ status: false,
79
+ error: error.message
80
+ };
81
+ }
82
+ }
83
+ }
84
+
85
+ module.exports = savetube;
@@ -0,0 +1,37 @@
1
+ /*
2
+ Base : https://play.google.com/store/apps/details?id=com.raahim2.Sharpify
3
+ By : ZennzXD
4
+ Created : 2 Mei 2026
5
+ */
6
+
7
+ const fs = require('fs')
8
+
9
+ const headers = {
10
+ 'User-Agent': 'okhttp/4.9.2',
11
+ 'Accept-Encoding': 'gzip'
12
+ }
13
+
14
+ const listmodel = {
15
+ enhance: 'https://sharpify-api.vercel.app/api/enhance/auto_enhance',
16
+ upscale: 'https://sharpify-api.vercel.app/api/enhance/upscale',
17
+ removebg: 'https://sharpify-api.vercel.app/api/enhance/bgrem'
18
+ }
19
+
20
+ async function sharpify(img, model) {
21
+ const form = new FormData()
22
+ const fileBuffer = fs.readFileSync(img)
23
+ const blob = new Blob([fileBuffer], { type: 'image/jpeg' })
24
+
25
+ form.append('file', blob, 'source.jpg')
26
+
27
+ const res = await fetch(listmodel[model], {
28
+ method: 'POST',
29
+ headers: headers,
30
+ body: form
31
+ })
32
+
33
+ const data = await res.json()
34
+ return data
35
+ }
36
+
37
+ module.exports = sharpify
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;