@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.
package/README.md ADDED
@@ -0,0 +1 @@
1
+
package/index.js ADDED
@@ -0,0 +1,25 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+
4
+ const srcPath = path.join(__dirname, 'src')
5
+ const modules = {}
6
+ const listScraper = []
7
+
8
+ fs.readdirSync(srcPath).forEach(file => {
9
+ if (file.endsWith('.js')) {
10
+ const name = file.replace('.js', '')
11
+ modules[name] = require(`./src/${file}`)
12
+ listScraper.push(name)
13
+ }
14
+ })
15
+
16
+ modules.docs = async () => {
17
+ return {
18
+ success: true,
19
+ total: listScraper.length,
20
+ scrapers: listScraper,
21
+ message: "Gunakan nama scraper sebagai function. Contoh pemakaian: await nama_scraper('url')"
22
+ }
23
+ }
24
+
25
+ module.exports = modules
package/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import scraper from './index.js';
2
+
3
+ export const applemusicdl = scraper.applemusicdl;
4
+ export const geniusdetail = scraper.geniusdetail;
5
+ export const geniussearch = scraper.geniussearch;
6
+ export const igdl = scraper.igdl;
7
+ export const igstalk = scraper.igstalk;
8
+ export const novaai = scraper.novaai;
9
+ export const pinterestdownload = scraper.pinterestdownload;
10
+ export const pixaremovebg = scraper.pixaremovebg;
11
+ export const sharpify = scraper.sharpify;
12
+ export const threadsdownload = scraper.threadsdownload;
13
+ export const twetterdownload = scraper.twetterdownload;
14
+ export const docs = scraper.docs;
15
+
16
+ export default scraper;
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@danonino/moon",
3
+ "version": "1.0.0",
4
+ "description": "femboys",
5
+ "keywords": [
6
+ "test",
7
+ "moon",
8
+ "scraping"
9
+ ],
10
+ "main": "index.js",
11
+ "type": "commonjs",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./index.mjs",
15
+ "require": "./index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "test": "echo \"Error: no test specified\" && exit 1"
20
+ },
21
+ "author": "moon",
22
+ "license": "ISC",
23
+ "dependencies": {
24
+ "@fontsource/plus-jakarta-sans": "^5.2.8",
25
+ "@napi-rs/canvas": "^1.0.2",
26
+ "@zenaveline/scraper": "^1.2.2",
27
+ "axios": "^1.6.0",
28
+ "cheerio": "^1.0.0",
29
+ "crypto": "^1.0.1",
30
+ "form-data": "^4.0.0",
31
+ "sharp": "^0.33.2"
32
+ }
33
+ }
@@ -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,63 @@
1
+ /*
2
+ Base : https://aaplmusicdownloader.com/
3
+ By : ZennzXD
4
+ Created : Kamis 2 April 2026
5
+ */
6
+
7
+ const axios = require('axios')
8
+
9
+ const headers = {
10
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36',
11
+ 'Accept': 'application/json, text/javascript, */*; q=0.01',
12
+ 'origin': 'https://aaplmusicdownloader.com',
13
+ 'referer': 'https://aaplmusicdownloader.com/',
14
+ 'sec-fetch-site': 'same-origin',
15
+ 'sec-fetch-mode': 'cors',
16
+ 'accept-language': 'id-ID,id;q=0.9,en-AU;q=0.8,en;q=0.7,en-US;q=0.6'
17
+ }
18
+
19
+ async function aapldown(url) {
20
+ const init = await axios.get('https://aaplmusicdownloader.com/', { headers })
21
+ const cookies = init.headers['set-cookie']
22
+ const cookieStr = cookies ? cookies.map(c => c.split(';')[0]).join('; ') : ''
23
+
24
+ const reqheaders = { ...headers, 'Cookie': cookieStr, 'x-requested-with': 'XMLHttpRequest' }
25
+
26
+ const metadata = await axios.get(`https://aaplmusicdownloader.com/api/song_url.php?url=${encodeURIComponent(url)}`, { headers: reqheaders })
27
+ const meta = metadata.data
28
+
29
+ const swdData = new URLSearchParams()
30
+ swdData.append('song_name', meta.name.replace(/['"]/g, ''))
31
+ swdData.append('artist_name', meta.artist.replace(/['"]/g, ''))
32
+ swdData.append('url', meta.url)
33
+ swdData.append('token', 'none')
34
+ swdData.append('zip_download', 'false')
35
+ swdData.append('quality', '320')
36
+
37
+ const swdRes = await axios.post('https://aaplmusicdownloader.com/api/composer/swd.php', swdData, {
38
+ headers: { ...reqheaders, 'Content-Type': 'application/x-www-form-urlencoded' }
39
+ })
40
+
41
+ const id3Data = new URLSearchParams()
42
+ id3Data.append('url', swdRes.data.dlink)
43
+ id3Data.append('name', meta.name)
44
+ id3Data.append('artist', meta.artist)
45
+ id3Data.append('album', meta.albumname)
46
+ id3Data.append('thumb', meta.thumb)
47
+
48
+ const id3Res = await axios.post('https://aaplmusicdownloader.com/api/composer/ffmpeg/saveid3.php', id3Data, {
49
+ headers: { ...reqheaders, 'Content-Type': 'application/x-www-form-urlencoded' },
50
+ responseType: 'text'
51
+ })
52
+
53
+ return {
54
+ title: meta.name,
55
+ album: meta.albumname,
56
+ artist: meta.artist,
57
+ thumb: meta.thumb,
58
+ duration: meta.duration,
59
+ url_dl: `https://aaplmusicdownloader.com/api/composer/ffmpeg/saved/${id3Res.data}`
60
+ }
61
+ }
62
+
63
+ module.exports = aapldown
package/src/brat.js ADDED
@@ -0,0 +1,192 @@
1
+ /*
2
+ Source : https://whatsapp.com/channel/0029VbCHRSDAzNboLatr0W0o/471
3
+ Creator : Ditzzx
4
+ */
5
+
6
+ const { createCanvas, loadImage, GlobalFonts } = require('@napi-rs/canvas')
7
+ const { writeFileSync, existsSync, readFileSync } = require('fs')
8
+ const path = require('path')
9
+
10
+ const THEMES = {
11
+ black: { bg: '#000000', text: '#ffffff' },
12
+ white: { bg: '#ffffff', text: '#000000' },
13
+ green: { bg: '#8ace00', text: '#000000' }
14
+ }
15
+
16
+ const FONT_URL = 'https://raw.githubusercontent.com/Ditzzx-vibecoder/Assets/main/Font/ARIALN.ttf'
17
+ const EMOJI_JSON_URL = 'https://media.githubusercontent.com/media/Ditzzx-vibecoder/entahlah/main/emoji-apple.json'
18
+ const FONT_PATH = path.join(__dirname, 'ARIALN.ttf')
19
+ const EMOJI_JSON_PATH = path.join(__dirname, 'emoji-apple.json')
20
+
21
+ async function downloadFile(url, dest) {
22
+ const res = await fetch(url)
23
+ const buf = Buffer.from(await res.arrayBuffer())
24
+ writeFileSync(dest, buf)
25
+ return buf
26
+ }
27
+
28
+ async function ensureFont() {
29
+ if (!existsSync(FONT_PATH)) await downloadFile(FONT_URL, FONT_PATH)
30
+ GlobalFonts.registerFromPath(FONT_PATH, 'ArialNarrow')
31
+ }
32
+
33
+ let emojiMap = null
34
+ const emojiImageCache = new Map()
35
+
36
+ function emojiToUnicode(emoji) {
37
+ return [...emoji].map(c => c.codePointAt(0).toString(16).padStart(4, '0')).join('-')
38
+ }
39
+
40
+ async function loadEmojiMap() {
41
+ if (emojiMap) return emojiMap
42
+ if (!existsSync(EMOJI_JSON_PATH)) await downloadFile(EMOJI_JSON_URL, EMOJI_JSON_PATH)
43
+ emojiMap = JSON.parse(readFileSync(EMOJI_JSON_PATH, 'utf-8'))
44
+ return emojiMap
45
+ }
46
+
47
+ async function getEmojiImage(emoji) {
48
+ if (emojiImageCache.has(emoji)) return emojiImageCache.get(emoji)
49
+ const map = await loadEmojiMap()
50
+ const base = emojiToUnicode(emoji)
51
+ const variants = [
52
+ base,
53
+ base.replace(/-fe0f/gi, ''),
54
+ `${base.replace(/-fe0f/gi, '')}-fe0f`,
55
+ base.toUpperCase(),
56
+ base.replace(/-fe0f/gi, '').toUpperCase(),
57
+ base.replace(/-fe0f/gi, '').toUpperCase() + '-FE0F'
58
+ ]
59
+ let b64 = null
60
+ for (const v of variants) {
61
+ if (map[v]) { b64 = map[v]; break }
62
+ }
63
+ if (!b64) return null
64
+ const img = await loadImage(Buffer.from(b64, 'base64'))
65
+ emojiImageCache.set(emoji, img)
66
+ return img
67
+ }
68
+
69
+ async function drawAppleEmoji(ctx, emoji, x, y, size) {
70
+ const img = await getEmojiImage(emoji)
71
+ if (!img) { ctx.fillText(emoji, x, y); return }
72
+ ctx.drawImage(img, x, y, size, size)
73
+ }
74
+
75
+ const EMOJI_REGEX = /(\p{Emoji_Modifier_Base}\p{Emoji_Modifier}|\p{Emoji_Presentation}\uFE0F?|\p{Emoji}\uFE0F|[\u{1F1E0}-\u{1F1FF}]{2}|\p{Extended_Pictographic}\uFE0F?)/gu
76
+
77
+ function measureTextCustom(ctx, text, fontSize) {
78
+ const parts = text.split(EMOJI_REGEX)
79
+ let w = 0
80
+ for (const part of parts) {
81
+ if (!part) continue
82
+ EMOJI_REGEX.lastIndex = 0
83
+ if (EMOJI_REGEX.test(part)) w += fontSize
84
+ else w += ctx.measureText(part).width
85
+ EMOJI_REGEX.lastIndex = 0
86
+ }
87
+ return w
88
+ }
89
+
90
+ async function drawTextWithEmojis(ctx, text, x, y, fontSize) {
91
+ const parts = text.split(EMOJI_REGEX)
92
+ let curX = x
93
+ for (const part of parts) {
94
+ if (!part) continue
95
+ EMOJI_REGEX.lastIndex = 0
96
+ if (EMOJI_REGEX.test(part)) {
97
+ await drawAppleEmoji(ctx, part, curX, y, fontSize)
98
+ curX += fontSize
99
+ } else {
100
+ ctx.fillText(part, curX, y)
101
+ curX += ctx.measureText(part).width
102
+ }
103
+ EMOJI_REGEX.lastIndex = 0
104
+ }
105
+ }
106
+
107
+ function wrapText(ctx, text, maxWidth, fontSize) {
108
+ ctx.font = `${fontSize}px ArialNarrow`
109
+ const words = text.split(' ')
110
+ const lines = []
111
+ let cur = ''
112
+ for (const word of words) {
113
+ const test = cur ? cur + ' ' + word : word
114
+ if (measureTextCustom(ctx, test, fontSize) > maxWidth && cur) {
115
+ lines.push(cur)
116
+ cur = word
117
+ } else {
118
+ cur = test
119
+ }
120
+ }
121
+ if (cur) lines.push(cur)
122
+ return lines
123
+ }
124
+
125
+ function fitsAt(ctx, text, fontSize, maxWidth, maxHeight, lineGap) {
126
+ const lines = wrapText(ctx, text, maxWidth, fontSize)
127
+ const longestWord = Math.max(...text.split(' ').map(w => measureTextCustom(ctx, w, fontSize)))
128
+ const totalHeight = lines.length * (fontSize + lineGap) - lineGap
129
+ return longestWord <= maxWidth && totalHeight <= maxHeight
130
+ }
131
+
132
+ function findBestFontSize(ctx, text, maxWidth, maxHeight, lineGap) {
133
+ let lo = 10
134
+ let hi = 700
135
+ let best = lo
136
+
137
+ while (lo <= hi) {
138
+ const mid = Math.floor((lo + hi) / 2)
139
+ if (fitsAt(ctx, text, mid, maxWidth, maxHeight, lineGap)) {
140
+ best = mid
141
+ lo = mid + 1
142
+ } else {
143
+ hi = mid - 1
144
+ }
145
+ }
146
+ return best
147
+ }
148
+
149
+ async function generateBrat({ text = 'Halo Guys Nama Saya', theme = 'white', blur = 0 } = {}) {
150
+ const selectedTheme = THEMES[theme] || THEMES.white
151
+ const blurAmount = [0, 1, 2, 3].includes(blur) ? blur : 0
152
+
153
+ const size = 1000
154
+ const padding = 80
155
+ const lineGap = 20
156
+ const maxWidth = size - padding * 2
157
+ const maxHeight = size - padding * 2
158
+
159
+ await ensureFont()
160
+ await loadEmojiMap()
161
+
162
+ const canvas = createCanvas(size, size)
163
+ const ctx = canvas.getContext('2d')
164
+
165
+ const fontSize = findBestFontSize(ctx, text, maxWidth, maxHeight, lineGap)
166
+ const lines = wrapText(ctx, text, maxWidth, fontSize)
167
+
168
+ ctx.fillStyle = selectedTheme.bg
169
+ ctx.fillRect(0, 0, size, size)
170
+
171
+ ctx.fillStyle = selectedTheme.text
172
+ ctx.font = `${fontSize}px ArialNarrow`
173
+ ctx.textAlign = 'left'
174
+ ctx.textBaseline = 'top'
175
+
176
+ ctx.save()
177
+ if (blurAmount > 0) ctx.filter = `blur(${blurAmount}px)`
178
+
179
+ const totalTextHeight = lines.length * (fontSize + lineGap) - lineGap
180
+ let y = (size - totalTextHeight) / 2
181
+ for (const line of lines) {
182
+ await drawTextWithEmojis(ctx, line, padding, y, fontSize)
183
+ y += fontSize + lineGap
184
+ }
185
+
186
+ ctx.restore()
187
+
188
+ const buffer = await canvas.encode('png')
189
+ return buffer
190
+ }
191
+
192
+ module.exports = generateBrat
@@ -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;
@@ -0,0 +1,106 @@
1
+ /*
2
+ Source : https://whatsapp.com/channel/0029VbCHRSDAzNboLatr0W0o/471
3
+ */
4
+
5
+ const { createCanvas, loadImage, GlobalFonts } = require("@napi-rs/canvas");
6
+ const fs = require("fs");
7
+ const fsp = require("fs/promises");
8
+ const path = require("path");
9
+
10
+ const ROOT = process.cwd();
11
+ const OUTPUT_DIR = path.join(ROOT, "tmp");
12
+
13
+ const IMAGE_URL =
14
+ "https://raw.githubusercontent.com/Ditzzx-vibecoder/Assets/main/Image/file_0000000078bc71fa87da5cf26dc6c008.jpeg";
15
+
16
+ const WIDTH = 841;
17
+ const HEIGHT = 1870;
18
+
19
+ const FONT_PATHS = [
20
+ path.join(
21
+ ROOT,
22
+ "node_modules/@fontsource/plus-jakarta-sans/files/plus-jakarta-sans-latin-600-normal.woff2"
23
+ ),
24
+ path.join(
25
+ ROOT,
26
+ "node_modules/@fontsource/plus-jakarta-sans/files/plus-jakarta-sans-latin-ext-600-normal.woff2"
27
+ ),
28
+ ];
29
+
30
+ const FIXED_RP = Object.freeze({
31
+ text: "Rp",
32
+ x: 61,
33
+ y: 368,
34
+ size: 20,
35
+ weight: 800,
36
+ });
37
+
38
+ const AMOUNT_STYLE = {
39
+ x: 94,
40
+ y: 371,
41
+ size: 28,
42
+ weight: 800,
43
+ color: "#FFFFFF",
44
+ };
45
+
46
+ function formatAmount(input) {
47
+ const digits = String(input).replace(/[^\d]/g, "") || "0";
48
+ const normalized = digits.replace(/^0+(?=\d)/, "");
49
+
50
+ return normalized.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
51
+ }
52
+
53
+ function registerFont() {
54
+ for (const fontPath of FONT_PATHS) {
55
+ if (fs.existsSync(fontPath)) {
56
+ GlobalFonts.registerFromPath(fontPath, "Plus Jakarta Sans");
57
+ return;
58
+ }
59
+ }
60
+
61
+ throw new Error("Font Plus Jakarta Sans tidak ketemu. Pastikan package @fontsource/plus-jakarta-sans terinstal.");
62
+ }
63
+
64
+ async function loadImageFromUrl(url) {
65
+ const res = await fetch(url);
66
+
67
+ if (!res.ok) {
68
+ throw new Error(`Gagal download image: HTTP ${res.status}`);
69
+ }
70
+
71
+ const arrayBuffer = await res.arrayBuffer();
72
+ return loadImage(Buffer.from(arrayBuffer));
73
+ }
74
+
75
+ async function fakeOvo(amountInput = "5000002828") {
76
+ fs.mkdirSync(OUTPUT_DIR, {
77
+ recursive: true,
78
+ });
79
+
80
+ registerFont();
81
+
82
+ const image = await loadImageFromUrl(IMAGE_URL);
83
+
84
+ const canvas = createCanvas(WIDTH, HEIGHT);
85
+ const ctx = canvas.getContext("2d");
86
+
87
+ ctx.imageSmoothingEnabled = true;
88
+ ctx.drawImage(image, 0, 0, WIDTH, HEIGHT);
89
+
90
+ ctx.fillStyle = AMOUNT_STYLE.color;
91
+ ctx.textAlign = "left";
92
+ ctx.textBaseline = "alphabetic";
93
+
94
+ ctx.font = `${FIXED_RP.weight} ${FIXED_RP.size}px "Plus Jakarta Sans"`;
95
+ ctx.fillText(FIXED_RP.text, FIXED_RP.x, FIXED_RP.y);
96
+
97
+ const AMOUNT_TEXT = formatAmount(amountInput);
98
+
99
+ ctx.font = `${AMOUNT_STYLE.weight} ${AMOUNT_STYLE.size}px "Plus Jakarta Sans"`;
100
+ ctx.fillText(AMOUNT_TEXT, AMOUNT_STYLE.x, AMOUNT_STYLE.y);
101
+
102
+ const buffer = await canvas.encode("png");
103
+ return buffer;
104
+ }
105
+
106
+ module.exports = fakeOvo;
@@ -0,0 +1,44 @@
1
+ /*
2
+ Genius
3
+ Base : https://play.google.com/store/apps/details?id=com.genius.android
4
+ Author : ZennzXD
5
+ Created : Sabtu 2 mei 2026
6
+ */
7
+
8
+ const headers = {
9
+ 'Accept-Encoding': 'gzip',
10
+ 'x-genius-app-background-request': '0',
11
+ 'x-genius-logged-out': 'true',
12
+ 'x-genius-android-version': '8.1.1',
13
+ 'user-agent': 'Genius/8.1.1 (Android; Android 13; ZN/Android)'
14
+ }
15
+
16
+ function parselirik(node) {
17
+ if (typeof node === 'string') return node
18
+ if (!node || !node.children) return ''
19
+ if (node.tag === 'br') return '\n'
20
+ return node.children.map(parselirik).join('')
21
+ }
22
+
23
+ async function detail(id) {
24
+ const res = await fetch(`https://api.genius.com/songs/${id}`, { headers })
25
+ const data = await res.json()
26
+ const song = data.response.song
27
+
28
+ return {
29
+ id: song.id,
30
+ title: song.title,
31
+ artist: song.artist_names,
32
+ header_image_url: song.header_image_url,
33
+ song_art_image_url: song.song_art_image_url,
34
+ instrumental: song.instrumental,
35
+ is_music: song.is_music,
36
+ hidden: song.hidden,
37
+ explicit: song.explicit,
38
+ release_date: song.release_date_for_display,
39
+ url: song.url,
40
+ lyrics: song.lyrics ? parselirik(song.lyrics.dom).trim() : null
41
+ }
42
+ }
43
+
44
+ module.exports = detail