@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 +1 -0
- package/index.js +25 -0
- package/index.mjs +16 -0
- package/package.json +33 -0
- package/src/allinonedownloader.js +66 -0
- package/src/applemusicdl.js +63 -0
- package/src/brat.js +192 -0
- package/src/bypasstools.js +35 -0
- package/src/douyindl.js +69 -0
- package/src/fake-ovo.js +106 -0
- package/src/geniusdetail.js +44 -0
- package/src/geniussearch.js +42 -0
- package/src/igdl.js +53 -0
- package/src/igstalk.js +59 -0
- package/src/iqc-darkmode.js +382 -0
- package/src/iqc-pinkmode.js +390 -0
- package/src/mediafiredl.js +144 -0
- package/src/nanobanana.js +115 -0
- package/src/novaai.js +34 -0
- package/src/pinterestdownload.js +72 -0
- package/src/pixaremovebg.js +32 -0
- package/src/savetube.js +85 -0
- package/src/sharpify.js +37 -0
- package/src/spotify.js +374 -0
- package/src/spotifycard.js +142 -0
- package/src/spotifydl.js +25 -0
- package/src/threadsdownload.js +65 -0
- package/src/tiktokdm-qc.js +225 -0
- package/src/tiktokdownload.js +56 -0
- package/src/twetterdownload.js +39 -0
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
async function search(query) {
|
|
17
|
+
const res = await fetch(`https://api.genius.com/search/multi?q=${encodeURIComponent(query)}`, { headers })
|
|
18
|
+
const data = await res.json()
|
|
19
|
+
|
|
20
|
+
const songs = []
|
|
21
|
+
|
|
22
|
+
for (const section of data.response.sections) {
|
|
23
|
+
if (section.type === 'song' || section.type === 'top_hit') {
|
|
24
|
+
for (const hit of section.hits) {
|
|
25
|
+
if (hit.type === 'song') {
|
|
26
|
+
const song = hit.result
|
|
27
|
+
songs.push({
|
|
28
|
+
id: song.id,
|
|
29
|
+
title: song.title,
|
|
30
|
+
artist: song.artist_names,
|
|
31
|
+
header_image_url: song.header_image_url,
|
|
32
|
+
url: song.url
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return songs
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = search
|
package/src/igdl.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Base : https://fastdl.app
|
|
3
|
+
Author : ZenzzXD
|
|
4
|
+
Thanks to NX
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const axios = require('axios')
|
|
8
|
+
const crypto = require('crypto')
|
|
9
|
+
|
|
10
|
+
const headers = {
|
|
11
|
+
'accept': 'application/json, text/plain, */*',
|
|
12
|
+
'accept-language': 'id-ID,id;q=0.9,en-AU;q=0.8,en;q=0.7,en-US;q=0.6',
|
|
13
|
+
'origin': 'https://fastdl.app',
|
|
14
|
+
'referer': 'https://fastdl.app/',
|
|
15
|
+
'user-agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36',
|
|
16
|
+
'sec-ch-ua': '"Chromium";v="139", "Not;A=Brand";v="99"',
|
|
17
|
+
'sec-ch-ua-mobile': '?1',
|
|
18
|
+
'sec-ch-ua-platform': '"Android"',
|
|
19
|
+
'sec-fetch-dest': 'empty',
|
|
20
|
+
'sec-fetch-mode': 'cors',
|
|
21
|
+
'sec-fetch-site': 'same-site'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function igdl(url) {
|
|
25
|
+
const { data: msc } = await axios.get('https://fastdl.app/msec', { headers })
|
|
26
|
+
|
|
27
|
+
const ts = Date.now() - (Math.abs(Date.now() - Math.floor(msc.msec * 1000)) >= 60000 ? Date.now() - Math.floor(msc.msec * 1000) : 0)
|
|
28
|
+
|
|
29
|
+
const sg = crypto
|
|
30
|
+
.createHmac('sha256', Buffer.from('82314e32a384d00f055de496b4737acde3cbb2f851b90e1a70625f6d3bb56401', 'hex'))
|
|
31
|
+
.update(url + ts)
|
|
32
|
+
.digest('hex')
|
|
33
|
+
|
|
34
|
+
const { data: result } = await axios.post('https://cors.siputzx.my.id/https://api-wh.fastdl.app/api/convert',
|
|
35
|
+
new URLSearchParams({
|
|
36
|
+
sf_url : url,
|
|
37
|
+
ts : ts.toString(),
|
|
38
|
+
_ts : '1778140969163',
|
|
39
|
+
_tsc : (Math.abs(Date.now() - Math.floor(msc.msec * 1000)) >= 60000 ? Date.now() - Math.floor(msc.msec * 1000) : 0).toString(),
|
|
40
|
+
_sv : '2',
|
|
41
|
+
_s : sg
|
|
42
|
+
}).toString(),
|
|
43
|
+
{
|
|
44
|
+
headers: {
|
|
45
|
+
...headers
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return { success: true, ...result }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = igdl
|
package/src/igstalk.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
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
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = igStalk;
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Source : https://whatsapp.com/channel/0029VbCHRSDAzNboLatr0W0o/471
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const axios = require('axios')
|
|
6
|
+
const { createCanvas, loadImage, GlobalFonts } = require('@napi-rs/canvas')
|
|
7
|
+
const { writeFile, mkdir, readFile } = require('node:fs/promises')
|
|
8
|
+
const { existsSync } = require('node:fs')
|
|
9
|
+
const { join } = require('node:path')
|
|
10
|
+
|
|
11
|
+
const APPLE_EMOJI_JSON_URL = 'https://media.githubusercontent.com/media/Ditzzx-vibecoder/entahlah/main/emoji-apple.json';
|
|
12
|
+
const APPLE_EMOJI_JSON_LOCAL = join(__dirname, 'fonts', 'emoji-apple-image.json');
|
|
13
|
+
|
|
14
|
+
let appleEmojiMap = null;
|
|
15
|
+
const emojiImageCache = new Map();
|
|
16
|
+
|
|
17
|
+
async function downloadFile(url) {
|
|
18
|
+
const res = await axios.get(url, { responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0' }, maxRedirects: 5 });
|
|
19
|
+
return Buffer.from(res.data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function emojiToUnicode(emoji) {
|
|
23
|
+
return [...emoji].map(c => c.codePointAt(0).toString(16).padStart(4, '0')).join('-');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function loadAppleEmojiMap() {
|
|
27
|
+
if (appleEmojiMap) return appleEmojiMap;
|
|
28
|
+
await mkdir(join(__dirname, 'fonts'), { recursive: true });
|
|
29
|
+
if (!existsSync(APPLE_EMOJI_JSON_LOCAL)) {
|
|
30
|
+
const buf = await downloadFile(APPLE_EMOJI_JSON_URL);
|
|
31
|
+
await writeFile(APPLE_EMOJI_JSON_LOCAL, buf);
|
|
32
|
+
}
|
|
33
|
+
const raw = await readFile(APPLE_EMOJI_JSON_LOCAL, 'utf-8');
|
|
34
|
+
appleEmojiMap = JSON.parse(raw);
|
|
35
|
+
return appleEmojiMap;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function getEmojiImage(emoji) {
|
|
39
|
+
if (emojiImageCache.has(emoji)) return emojiImageCache.get(emoji);
|
|
40
|
+
const map = await loadAppleEmojiMap();
|
|
41
|
+
const base = emojiToUnicode(emoji);
|
|
42
|
+
const variants = [
|
|
43
|
+
base,
|
|
44
|
+
base.replace(/-fe0f/gi, ''),
|
|
45
|
+
`${base.replace(/-fe0f/gi, '')}-fe0f`,
|
|
46
|
+
base.toUpperCase(),
|
|
47
|
+
base.replace(/-fe0f/gi, '').toUpperCase(),
|
|
48
|
+
base.replace(/-fe0f/gi, '').toUpperCase() + '-FE0F',
|
|
49
|
+
];
|
|
50
|
+
let b64 = null;
|
|
51
|
+
for (const v of variants) {
|
|
52
|
+
if (map[v]) { b64 = map[v]; break; }
|
|
53
|
+
}
|
|
54
|
+
if (!b64) return null;
|
|
55
|
+
const buf = Buffer.from(b64, 'base64');
|
|
56
|
+
const img = await loadImage(buf);
|
|
57
|
+
emojiImageCache.set(emoji, img);
|
|
58
|
+
return img;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function drawAppleEmoji(ctx, emoji, x, y, size) {
|
|
62
|
+
const img = await getEmojiImage(emoji);
|
|
63
|
+
if (!img) {
|
|
64
|
+
ctx.fillText(emoji, x, y);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
ctx.drawImage(img, x - size / 2, y - size / 2, size, size);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
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;
|
|
71
|
+
|
|
72
|
+
function measureTextCustom(ctx, text, fontSize) {
|
|
73
|
+
const parts = text.split(EMOJI_REGEX);
|
|
74
|
+
let totalWidth = 0;
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (!part) continue;
|
|
77
|
+
EMOJI_REGEX.lastIndex = 0;
|
|
78
|
+
if (EMOJI_REGEX.test(part)) {
|
|
79
|
+
totalWidth += fontSize * 1.05;
|
|
80
|
+
} else {
|
|
81
|
+
totalWidth += ctx.measureText(part).width;
|
|
82
|
+
}
|
|
83
|
+
EMOJI_REGEX.lastIndex = 0;
|
|
84
|
+
}
|
|
85
|
+
return totalWidth;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function drawTextWithEmojis(ctx, text, x, y, fontSize) {
|
|
89
|
+
const parts = text.split(EMOJI_REGEX);
|
|
90
|
+
let currentX = x;
|
|
91
|
+
for (const part of parts) {
|
|
92
|
+
if (!part) continue;
|
|
93
|
+
EMOJI_REGEX.lastIndex = 0;
|
|
94
|
+
if (EMOJI_REGEX.test(part)) {
|
|
95
|
+
const emojiSize = fontSize * 1.05;
|
|
96
|
+
const emojiCX = currentX + emojiSize / 2;
|
|
97
|
+
const emojiCY = y;
|
|
98
|
+
await drawAppleEmoji(ctx, part, emojiCX, emojiCY, emojiSize);
|
|
99
|
+
currentX += emojiSize;
|
|
100
|
+
} else {
|
|
101
|
+
ctx.fillText(part, currentX, y);
|
|
102
|
+
currentX += ctx.measureText(part).width;
|
|
103
|
+
}
|
|
104
|
+
EMOJI_REGEX.lastIndex = 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function wrapText(ctx, text, maxWidth, fontSize) {
|
|
109
|
+
ctx.font = `${fontSize}px InterRegular`;
|
|
110
|
+
const words = text.split(" ");
|
|
111
|
+
const lines = [];
|
|
112
|
+
let cur = "";
|
|
113
|
+
for (let i = 0; i < words.length; i++) {
|
|
114
|
+
const word = words[i];
|
|
115
|
+
if (word.includes('\n')) {
|
|
116
|
+
const parts = word.split('\n');
|
|
117
|
+
for (let j = 0; j < parts.length; j++) {
|
|
118
|
+
const test = cur + (cur ? " " : "") + parts[j];
|
|
119
|
+
if (measureTextCustom(ctx, test, fontSize) > maxWidth && cur) {
|
|
120
|
+
lines.push(cur); cur = parts[j];
|
|
121
|
+
} else { cur = test; }
|
|
122
|
+
if (j < parts.length - 1) { lines.push(cur); cur = ""; }
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const test = cur + (cur ? " " : "") + word;
|
|
127
|
+
if (measureTextCustom(ctx, test, fontSize) > maxWidth && i > 0) {
|
|
128
|
+
lines.push(cur); cur = word;
|
|
129
|
+
} else { cur = test; }
|
|
130
|
+
}
|
|
131
|
+
if (cur) lines.push(cur);
|
|
132
|
+
return lines;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function iqcDarkmode({ txt = `Earth without art is just "eh" 🌍🎨✨`, timeStr = "16.34", imgUrl = null } = {}) {
|
|
136
|
+
try {
|
|
137
|
+
const caption = imgUrl ? txt : ""
|
|
138
|
+
const RIN_BG_URL = 'https://raw.githubusercontent.com/ryyntwx/allimagerin/refs/heads/main/iqc-hytam.png'
|
|
139
|
+
const RIN_DIR = join(process.cwd(), 'assets', 'rinchat')
|
|
140
|
+
const RIN_BG_LOCAL = join(RIN_DIR, 'iqc-hytam.png')
|
|
141
|
+
const RIN_FONTS_DIR = join(RIN_DIR, 'fonts')
|
|
142
|
+
const RIN_TMP = join(process.cwd(), 'tmp')
|
|
143
|
+
|
|
144
|
+
const RIN_FONTS = [
|
|
145
|
+
{ url: 'https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hiJ-Ek-_EeA.woff2', file: 'Inter-Regular.ttf' }
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
const BG_W = 941
|
|
149
|
+
const BG_H = 1671
|
|
150
|
+
|
|
151
|
+
await mkdir(RIN_FONTS_DIR, { recursive: true })
|
|
152
|
+
await mkdir(RIN_TMP, { recursive: true })
|
|
153
|
+
|
|
154
|
+
async function rinDownload(url, isJson = false) {
|
|
155
|
+
const res = await axios.get(url, { responseType: isJson ? 'json' : 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0' }, maxRedirects: 5 })
|
|
156
|
+
return isJson ? res.data : Buffer.from(res.data)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let emojis = ["👍", "❤️", "😂", "😮", "😢", "🙏"]
|
|
160
|
+
|
|
161
|
+
for (const f of RIN_FONTS) {
|
|
162
|
+
const dest = join(RIN_FONTS_DIR, f.file)
|
|
163
|
+
if (!existsSync(dest)) await writeFile(dest, await rinDownload(f.url))
|
|
164
|
+
GlobalFonts.registerFromPath(dest, 'InterRegular')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!existsSync(RIN_BG_LOCAL)) {
|
|
168
|
+
await writeFile(RIN_BG_LOCAL, await rinDownload(RIN_BG_URL))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await loadAppleEmojiMap();
|
|
172
|
+
|
|
173
|
+
const canvas = createCanvas(BG_W, BG_H)
|
|
174
|
+
const ctx = canvas.getContext('2d')
|
|
175
|
+
const bgImg = await loadImage(RIN_BG_LOCAL)
|
|
176
|
+
ctx.drawImage(bgImg, 0, 0, BG_W, BG_H)
|
|
177
|
+
|
|
178
|
+
const PERMANENT_TIME_X = 463;
|
|
179
|
+
const PERMANENT_TIME_Y = 8;
|
|
180
|
+
const PERMANENT_TIME_SIZE = 27;
|
|
181
|
+
|
|
182
|
+
ctx.fillStyle = "#ffffff";
|
|
183
|
+
ctx.font = `${PERMANENT_TIME_SIZE}px InterRegular`;
|
|
184
|
+
ctx.textAlign = "center";
|
|
185
|
+
ctx.textBaseline = "top";
|
|
186
|
+
ctx.fillText(timeStr, PERMANENT_TIME_X, PERMANENT_TIME_Y);
|
|
187
|
+
|
|
188
|
+
const chatFontSize = 30;
|
|
189
|
+
const maxWidthLimit = 530;
|
|
190
|
+
const minBubbleWidth = 280;
|
|
191
|
+
const lineHeight = chatFontSize + 14;
|
|
192
|
+
const paddingX = 30;
|
|
193
|
+
const paddingY = 20;
|
|
194
|
+
const rad = 28;
|
|
195
|
+
const fixedX = 35;
|
|
196
|
+
const fixedBaseY = 946;
|
|
197
|
+
|
|
198
|
+
ctx.font = `22px InterRegular`;
|
|
199
|
+
const timeWidth = ctx.measureText(timeStr).width;
|
|
200
|
+
|
|
201
|
+
let finalY, finalBubbleHeight, bubbleW;
|
|
202
|
+
|
|
203
|
+
if (!imgUrl) {
|
|
204
|
+
ctx.font = `${chatFontSize}px InterRegular`;
|
|
205
|
+
const chatLines = wrapText(ctx, txt, maxWidthLimit, chatFontSize);
|
|
206
|
+
|
|
207
|
+
let longestW = 0;
|
|
208
|
+
chatLines.forEach(l => {
|
|
209
|
+
const w = measureTextCustom(ctx, l.trim(), chatFontSize);
|
|
210
|
+
if (w > longestW) longestW = w;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
bubbleW = longestW + (paddingX * 2);
|
|
214
|
+
bubbleW = Math.max(bubbleW, timeWidth + 75);
|
|
215
|
+
bubbleW = Math.max(bubbleW, 180);
|
|
216
|
+
|
|
217
|
+
const spaceTimeY = 12;
|
|
218
|
+
finalBubbleHeight = (chatLines.length * lineHeight) + paddingY + spaceTimeY + 22;
|
|
219
|
+
finalY = fixedBaseY - finalBubbleHeight;
|
|
220
|
+
|
|
221
|
+
ctx.fillStyle = "#1c1c1e";
|
|
222
|
+
ctx.beginPath();
|
|
223
|
+
ctx.moveTo(fixedX + rad, finalY);
|
|
224
|
+
ctx.lineTo(fixedX + bubbleW - rad, finalY);
|
|
225
|
+
ctx.quadraticCurveTo(fixedX + bubbleW, finalY, fixedX + bubbleW, finalY + rad);
|
|
226
|
+
ctx.lineTo(fixedX + bubbleW, finalY + finalBubbleHeight - rad);
|
|
227
|
+
ctx.quadraticCurveTo(fixedX + bubbleW, finalY + finalBubbleHeight, fixedX + bubbleW - rad, finalY + finalBubbleHeight);
|
|
228
|
+
ctx.lineTo(fixedX + rad, finalY + finalBubbleHeight);
|
|
229
|
+
ctx.quadraticCurveTo(fixedX + 8, finalY + finalBubbleHeight, fixedX + 8, finalY + finalBubbleHeight - 8);
|
|
230
|
+
ctx.lineTo(fixedX + 8, finalY + rad);
|
|
231
|
+
ctx.quadraticCurveTo(fixedX + 8, finalY, fixedX + rad, finalY);
|
|
232
|
+
ctx.closePath();
|
|
233
|
+
ctx.fill();
|
|
234
|
+
|
|
235
|
+
ctx.beginPath();
|
|
236
|
+
ctx.moveTo(fixedX + 12, finalY + finalBubbleHeight - 20);
|
|
237
|
+
ctx.quadraticCurveTo(fixedX - 2, finalY + finalBubbleHeight - 4, fixedX - 8, finalY + finalBubbleHeight);
|
|
238
|
+
ctx.quadraticCurveTo(fixedX + 6, finalY + finalBubbleHeight, fixedX + 22, finalY + finalBubbleHeight - 2);
|
|
239
|
+
ctx.closePath();
|
|
240
|
+
ctx.fill();
|
|
241
|
+
|
|
242
|
+
ctx.save();
|
|
243
|
+
ctx.fillStyle = "#ffffff";
|
|
244
|
+
ctx.font = `${chatFontSize}px InterRegular`;
|
|
245
|
+
ctx.textAlign = "left";
|
|
246
|
+
ctx.textBaseline = "middle";
|
|
247
|
+
for (let i = 0; i < chatLines.length; i++) {
|
|
248
|
+
const lineY = finalY + paddingY + (i * lineHeight) + (chatFontSize / 2);
|
|
249
|
+
await drawTextWithEmojis(ctx, chatLines[i].trim(), fixedX + paddingX, lineY, chatFontSize);
|
|
250
|
+
}
|
|
251
|
+
ctx.restore();
|
|
252
|
+
|
|
253
|
+
ctx.fillStyle = "#727278";
|
|
254
|
+
ctx.font = `22px InterRegular`;
|
|
255
|
+
ctx.textAlign = "right";
|
|
256
|
+
ctx.textBaseline = "top";
|
|
257
|
+
ctx.fillText(timeStr, fixedX + bubbleW - 22, finalY + finalBubbleHeight - 38);
|
|
258
|
+
|
|
259
|
+
} else {
|
|
260
|
+
const imgBuf = imgUrl.startsWith('http')
|
|
261
|
+
? await rinDownload(imgUrl)
|
|
262
|
+
: await readFile(imgUrl)
|
|
263
|
+
const imgObj = await loadImage(imgBuf)
|
|
264
|
+
|
|
265
|
+
const imgAspect = imgObj.width / imgObj.height
|
|
266
|
+
bubbleW = Math.min(Math.max(imgObj.width, minBubbleWidth), maxWidthLimit)
|
|
267
|
+
let imgDrawH = Math.round(bubbleW / imgAspect)
|
|
268
|
+
bubbleW = Math.max(bubbleW, timeWidth + 75);
|
|
269
|
+
|
|
270
|
+
let captionLines = [];
|
|
271
|
+
if (caption) {
|
|
272
|
+
ctx.font = `${chatFontSize}px InterRegular`;
|
|
273
|
+
captionLines = wrapText(ctx, caption, bubbleW - paddingX * 2, chatFontSize);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const captionH = captionLines.length > 0
|
|
277
|
+
? paddingY + (captionLines.length * lineHeight)
|
|
278
|
+
: 0;
|
|
279
|
+
const timeRowH = 28;
|
|
280
|
+
finalBubbleHeight = imgDrawH + captionH + timeRowH + (captionLines.length > 0 ? 4 : 0);
|
|
281
|
+
finalY = fixedBaseY - finalBubbleHeight;
|
|
282
|
+
|
|
283
|
+
ctx.fillStyle = "#1c1c1e";
|
|
284
|
+
ctx.beginPath();
|
|
285
|
+
ctx.moveTo(fixedX + rad, finalY);
|
|
286
|
+
ctx.lineTo(fixedX + bubbleW - rad, finalY);
|
|
287
|
+
ctx.quadraticCurveTo(fixedX + bubbleW, finalY, fixedX + bubbleW, finalY + rad);
|
|
288
|
+
ctx.lineTo(fixedX + bubbleW, finalY + finalBubbleHeight - rad);
|
|
289
|
+
ctx.quadraticCurveTo(fixedX + bubbleW, finalY + finalBubbleHeight, fixedX + bubbleW - rad, finalY + finalBubbleHeight);
|
|
290
|
+
ctx.lineTo(fixedX + rad, finalY + finalBubbleHeight);
|
|
291
|
+
ctx.quadraticCurveTo(fixedX + 8, finalY + finalBubbleHeight, fixedX + 8, finalY + finalBubbleHeight - 8);
|
|
292
|
+
ctx.lineTo(fixedX + 8, finalY + rad);
|
|
293
|
+
ctx.quadraticCurveTo(fixedX + 8, finalY, fixedX + rad, finalY);
|
|
294
|
+
ctx.closePath();
|
|
295
|
+
ctx.fill();
|
|
296
|
+
|
|
297
|
+
ctx.beginPath();
|
|
298
|
+
ctx.moveTo(fixedX + 12, finalY + finalBubbleHeight - 20);
|
|
299
|
+
ctx.quadraticCurveTo(fixedX - 2, finalY + finalBubbleHeight - 4, fixedX - 8, finalY + finalBubbleHeight);
|
|
300
|
+
ctx.quadraticCurveTo(fixedX + 6, finalY + finalBubbleHeight, fixedX + 22, finalY + finalBubbleHeight - 2);
|
|
301
|
+
ctx.closePath();
|
|
302
|
+
ctx.fill();
|
|
303
|
+
|
|
304
|
+
ctx.save();
|
|
305
|
+
ctx.beginPath();
|
|
306
|
+
ctx.moveTo(fixedX + rad, finalY);
|
|
307
|
+
ctx.lineTo(fixedX + bubbleW - rad, finalY);
|
|
308
|
+
ctx.quadraticCurveTo(fixedX + bubbleW, finalY, fixedX + bubbleW, finalY + rad);
|
|
309
|
+
ctx.lineTo(fixedX + bubbleW, finalY + imgDrawH);
|
|
310
|
+
ctx.lineTo(fixedX + 8, finalY + imgDrawH);
|
|
311
|
+
ctx.lineTo(fixedX + 8, finalY + rad);
|
|
312
|
+
ctx.quadraticCurveTo(fixedX + 8, finalY, fixedX + rad, finalY);
|
|
313
|
+
ctx.closePath();
|
|
314
|
+
ctx.clip();
|
|
315
|
+
ctx.drawImage(imgObj, fixedX, finalY, bubbleW, imgDrawH);
|
|
316
|
+
ctx.beginPath();
|
|
317
|
+
ctx.moveTo(fixedX + 8, finalY + imgDrawH);
|
|
318
|
+
ctx.lineTo(fixedX + 8, finalY + rad);
|
|
319
|
+
ctx.quadraticCurveTo(fixedX + 8, finalY, fixedX + rad, finalY);
|
|
320
|
+
ctx.lineTo(fixedX + bubbleW - rad, finalY);
|
|
321
|
+
ctx.quadraticCurveTo(fixedX + bubbleW, finalY, fixedX + bubbleW, finalY + rad);
|
|
322
|
+
ctx.lineTo(fixedX + bubbleW, finalY + imgDrawH);
|
|
323
|
+
ctx.strokeStyle = "#1c1c1e";
|
|
324
|
+
ctx.lineWidth = 18;
|
|
325
|
+
ctx.stroke();
|
|
326
|
+
ctx.restore();
|
|
327
|
+
|
|
328
|
+
if (captionLines.length > 0) {
|
|
329
|
+
ctx.save();
|
|
330
|
+
ctx.fillStyle = "#ffffff";
|
|
331
|
+
ctx.font = `${chatFontSize}px InterRegular`;
|
|
332
|
+
ctx.textAlign = "left";
|
|
333
|
+
ctx.textBaseline = "middle";
|
|
334
|
+
for (let i = 0; i < captionLines.length; i++) {
|
|
335
|
+
const lineY = finalY + imgDrawH + paddingY + (i * lineHeight) + (chatFontSize / 2);
|
|
336
|
+
await drawTextWithEmojis(ctx, captionLines[i].trim(), fixedX + paddingX, lineY, chatFontSize);
|
|
337
|
+
}
|
|
338
|
+
ctx.restore();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
ctx.fillStyle = "#727278";
|
|
342
|
+
ctx.font = `22px InterRegular`;
|
|
343
|
+
ctx.textAlign = "right";
|
|
344
|
+
ctx.textBaseline = "middle";
|
|
345
|
+
ctx.fillText(timeStr, fixedX + bubbleW - 22, finalY + finalBubbleHeight - timeRowH);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const emojiSize = Math.round(54 * 1.03);
|
|
349
|
+
const emCardH = emojiSize + Math.round(44 * 1.03);
|
|
350
|
+
const emCardW = Math.round(530 * 1.03);
|
|
351
|
+
const emCardX = fixedX + 8;
|
|
352
|
+
const emCardY = finalY - emCardH - 18;
|
|
353
|
+
|
|
354
|
+
ctx.fillStyle = "#1c1c1e";
|
|
355
|
+
ctx.beginPath();
|
|
356
|
+
ctx.roundRect(emCardX, emCardY, emCardW, emCardH, [emCardH / 2]);
|
|
357
|
+
ctx.fill();
|
|
358
|
+
|
|
359
|
+
const startX = emCardX + 55;
|
|
360
|
+
const spacingX = 76;
|
|
361
|
+
const emojiCY = emCardY + (emCardH / 2) + 2;
|
|
362
|
+
|
|
363
|
+
for (let i = 0; i < Math.min(emojis.length, 6); i++) {
|
|
364
|
+
await drawAppleEmoji(ctx, emojis[i], startX + (i * spacingX), emojiCY, emojiSize);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
ctx.fillStyle = "#8e8e93";
|
|
368
|
+
ctx.font = `${Math.round(36 * 1.03)}px InterRegular`;
|
|
369
|
+
ctx.textAlign = "center";
|
|
370
|
+
ctx.textBaseline = "middle";
|
|
371
|
+
ctx.fillText("+", startX + (6 * spacingX) - 8, emCardY + (emCardH / 2) - 2);
|
|
372
|
+
|
|
373
|
+
const buffer = await canvas.encode('png');
|
|
374
|
+
return buffer;
|
|
375
|
+
|
|
376
|
+
} catch (e) {
|
|
377
|
+
console.error(e.message || e)
|
|
378
|
+
throw e;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = iqcDarkmode;
|