@k4la/library 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/data/asahotak.json +1142 -0
- package/data/family100.json +73589 -0
- package/data/susunkata.json +3008 -0
- package/data/tebakbendera.json +346 -0
- package/data/tebakgambar.json +6002 -0
- package/data/tebakkata.json +1512 -0
- package/data/tebakkimia.json +502 -0
- package/index.js +29 -0
- package/lib/blackjack.js +219 -0
- package/lib/converter.js +105 -0
- package/lib/exif.js +205 -0
- package/lib/function.js +13 -0
- package/lib/game.js +58 -0
- package/lib/tictactoe.js +164 -0
- package/lib/ulartangga.js +181 -0
- package/lib/uno.js +250 -0
- package/lib/uploader.js +136 -0
- package/lib/werewolf.js +1208 -0
- package/package.json +23 -0
package/lib/blackjack.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib/utils/blackjack.js
|
|
3
|
+
* @description Blackjack game utilities
|
|
4
|
+
* @author K4lameety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const jimp = require('jimp')
|
|
8
|
+
|
|
9
|
+
const CARDS_URL = "https://raw.githubusercontent.com/hayeah/playing-cards-assets/master/png/"
|
|
10
|
+
|
|
11
|
+
const gameData = {}
|
|
12
|
+
|
|
13
|
+
const createSession = (id) => {
|
|
14
|
+
gameData[id] = {
|
|
15
|
+
id: id,
|
|
16
|
+
status: 'waiting',
|
|
17
|
+
deck: [],
|
|
18
|
+
dealer: [],
|
|
19
|
+
player: [],
|
|
20
|
+
winner: null
|
|
21
|
+
}
|
|
22
|
+
return gameData[id]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const getSession = (id) => gameData[id] || false
|
|
26
|
+
const deleteSession = (id) => delete gameData[id]
|
|
27
|
+
|
|
28
|
+
const generateDeck = () => {
|
|
29
|
+
const suits = ['clubs', 'diamonds', 'hearts', 'spades']
|
|
30
|
+
const values = ['ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'jack', 'queen', 'king']
|
|
31
|
+
let deck = []
|
|
32
|
+
|
|
33
|
+
for (let suit of suits) {
|
|
34
|
+
for (let val of values) {
|
|
35
|
+
let score = parseInt(val)
|
|
36
|
+
if (val === 'ace') score = 11
|
|
37
|
+
if (['jack', 'queen', 'king'].includes(val)) score = 10
|
|
38
|
+
|
|
39
|
+
let filename = `${val}_of_${suit}.png`
|
|
40
|
+
|
|
41
|
+
deck.push({
|
|
42
|
+
id: filename,
|
|
43
|
+
value: val,
|
|
44
|
+
suit: suit,
|
|
45
|
+
score: score
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (let i = deck.length - 1; i > 0; i--) {
|
|
50
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
51
|
+
[deck[i], deck[j]] = [deck[j], deck[i]]
|
|
52
|
+
}
|
|
53
|
+
return deck
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const calculateScore = (hand) => {
|
|
57
|
+
let score = 0
|
|
58
|
+
let aceCount = 0
|
|
59
|
+
|
|
60
|
+
for (let card of hand) {
|
|
61
|
+
score += card.score
|
|
62
|
+
if (card.value === 'ace') aceCount++
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
while (score > 21 && aceCount > 0) {
|
|
66
|
+
score -= 10
|
|
67
|
+
aceCount--
|
|
68
|
+
}
|
|
69
|
+
return score
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const startGame = (id) => {
|
|
73
|
+
let session = getSession(id)
|
|
74
|
+
if (!session) session = createSession(id)
|
|
75
|
+
|
|
76
|
+
session.status = 'playing'
|
|
77
|
+
session.deck = generateDeck()
|
|
78
|
+
session.player = []
|
|
79
|
+
session.dealer = []
|
|
80
|
+
|
|
81
|
+
session.player.push(session.deck.pop())
|
|
82
|
+
session.dealer.push(session.deck.pop())
|
|
83
|
+
session.player.push(session.deck.pop())
|
|
84
|
+
session.dealer.push(session.deck.pop())
|
|
85
|
+
|
|
86
|
+
let pScore = calculateScore(session.player)
|
|
87
|
+
if (pScore === 21) {
|
|
88
|
+
session.status = 'end'
|
|
89
|
+
session.winner = 'player_blackjack'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return session
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hit = (id) => {
|
|
96
|
+
let session = getSession(id)
|
|
97
|
+
if (!session || session.status !== 'playing') return { status: 'error' }
|
|
98
|
+
|
|
99
|
+
let card = session.deck.pop()
|
|
100
|
+
session.player.push(card)
|
|
101
|
+
|
|
102
|
+
let score = calculateScore(session.player)
|
|
103
|
+
if (score > 21) {
|
|
104
|
+
session.status = 'end'
|
|
105
|
+
return { status: 'bust', card, score }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { status: 'ok', card, score }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const stand = (id) => {
|
|
112
|
+
let session = getSession(id)
|
|
113
|
+
if (!session || session.status !== 'playing') return { status: 'error' }
|
|
114
|
+
|
|
115
|
+
let dealerScore = calculateScore(session.dealer)
|
|
116
|
+
|
|
117
|
+
while (dealerScore < 17) {
|
|
118
|
+
session.dealer.push(session.deck.pop())
|
|
119
|
+
dealerScore = calculateScore(session.dealer)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let playerScore = calculateScore(session.player)
|
|
123
|
+
session.status = 'end'
|
|
124
|
+
|
|
125
|
+
let result = ''
|
|
126
|
+
if (dealerScore > 21) result = 'dealer_bust'
|
|
127
|
+
else if (playerScore > dealerScore) result = 'win'
|
|
128
|
+
else if (playerScore < dealerScore) result = 'lose'
|
|
129
|
+
else result = 'push'
|
|
130
|
+
|
|
131
|
+
return { status: result, dealerScore, playerScore }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const canvas = async (id) => {
|
|
135
|
+
let session = getSession(id)
|
|
136
|
+
if (!session) return null
|
|
137
|
+
|
|
138
|
+
let bg = new jimp(800, 600, 0x35654dFF)
|
|
139
|
+
let font = await jimp.loadFont(jimp.FONT_SANS_32_WHITE)
|
|
140
|
+
let fontBig = await jimp.loadFont(jimp.FONT_SANS_64_WHITE)
|
|
141
|
+
|
|
142
|
+
let cardBack = new jimp(120, 175, 0x800000FF)
|
|
143
|
+
let border = new jimp(110, 165, 0xFFFFFF22)
|
|
144
|
+
cardBack.composite(border, 5, 5)
|
|
145
|
+
|
|
146
|
+
let dx = 50
|
|
147
|
+
let dy = 50
|
|
148
|
+
let dealerScore = 0
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < session.dealer.length; i++) {
|
|
151
|
+
if (session.status === 'playing' && i === 1) {
|
|
152
|
+
bg.composite(cardBack, dx, dy)
|
|
153
|
+
} else {
|
|
154
|
+
try {
|
|
155
|
+
let url = CARDS_URL + session.dealer[i].id
|
|
156
|
+
let img = await jimp.read(url)
|
|
157
|
+
img.resize(120, 175)
|
|
158
|
+
bg.composite(img, dx, dy)
|
|
159
|
+
} catch (e) {
|
|
160
|
+
bg.print(font, dx, dy, session.dealer[i].value)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
dx += 130
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (session.status === 'playing') {
|
|
167
|
+
bg.print(font, 50, 240, `Dealer: ${session.dealer[0].score} + ?`)
|
|
168
|
+
} else {
|
|
169
|
+
dealerScore = calculateScore(session.dealer)
|
|
170
|
+
bg.print(font, 50, 240, `Dealer: ${dealerScore}`)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let px = 50
|
|
174
|
+
let py = 350
|
|
175
|
+
for (let card of session.player) {
|
|
176
|
+
try {
|
|
177
|
+
let url = CARDS_URL + card.id
|
|
178
|
+
let img = await jimp.read(url)
|
|
179
|
+
img.resize(120, 175)
|
|
180
|
+
bg.composite(img, px, py)
|
|
181
|
+
} catch (e) {
|
|
182
|
+
bg.print(font, px, py, card.value)
|
|
183
|
+
}
|
|
184
|
+
px += 130
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let playerScore = calculateScore(session.player)
|
|
188
|
+
bg.print(font, 50, 540, `Player: ${playerScore}`)
|
|
189
|
+
|
|
190
|
+
if (session.status === 'end') {
|
|
191
|
+
let text = ""
|
|
192
|
+
let color = 0
|
|
193
|
+
|
|
194
|
+
if (session.winner === 'player_blackjack') text = "BLACKJACK!"
|
|
195
|
+
else if (playerScore > 21) text = "BUSTED!"
|
|
196
|
+
else if (dealerScore > 21) text = "DEALER BUST!"
|
|
197
|
+
else if (playerScore > dealerScore) text = "YOU WIN!"
|
|
198
|
+
else if (playerScore < dealerScore) text = "YOU LOSE!"
|
|
199
|
+
else text = "PUSH"
|
|
200
|
+
|
|
201
|
+
let overlay = new jimp(800, 150, 0x000000AA)
|
|
202
|
+
bg.composite(overlay, 0, 225)
|
|
203
|
+
|
|
204
|
+
let textWidth = jimp.measureText(fontBig, text)
|
|
205
|
+
bg.print(fontBig, 400 - (textWidth / 2), 265, text)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return await bg.getBufferAsync(jimp.MIME_JPEG)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
createSession,
|
|
213
|
+
startGame,
|
|
214
|
+
hit,
|
|
215
|
+
stand,
|
|
216
|
+
getSession,
|
|
217
|
+
deleteSession,
|
|
218
|
+
canvas
|
|
219
|
+
}
|
package/lib/converter.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib/converter.js
|
|
3
|
+
* @description Media conversion utilities using ffmpeg
|
|
4
|
+
* @author K4lameety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const { spawn } = require('child_process')
|
|
10
|
+
const os = require('os')
|
|
11
|
+
|
|
12
|
+
let ffmpegPath;
|
|
13
|
+
try {
|
|
14
|
+
ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
ffmpegPath = 'ffmpeg';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ffmpeg(buffer, args = [], ext = '', ext2 = '') {
|
|
20
|
+
return new Promise(async (resolve, reject) => {
|
|
21
|
+
try {
|
|
22
|
+
let tmp = path.join(os.tmpdir(), Date.now() + '.' + ext)
|
|
23
|
+
let out = tmp + '.' + ext2
|
|
24
|
+
|
|
25
|
+
await fs.promises.writeFile(tmp, buffer)
|
|
26
|
+
|
|
27
|
+
spawn(ffmpegPath, [
|
|
28
|
+
'-y',
|
|
29
|
+
'-i', tmp,
|
|
30
|
+
...args,
|
|
31
|
+
out
|
|
32
|
+
])
|
|
33
|
+
.on('error', reject)
|
|
34
|
+
.on('close', async (code) => {
|
|
35
|
+
try {
|
|
36
|
+
await fs.promises.unlink(tmp)
|
|
37
|
+
if (code !== 0) return reject(code)
|
|
38
|
+
|
|
39
|
+
const result = await fs.promises.readFile(out)
|
|
40
|
+
|
|
41
|
+
await fs.promises.unlink(out)
|
|
42
|
+
|
|
43
|
+
resolve(result)
|
|
44
|
+
} catch (e) {
|
|
45
|
+
reject(e)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
} catch (e) {
|
|
49
|
+
reject(e)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert Audio/Video to MP3
|
|
56
|
+
* @param {Buffer} buffer
|
|
57
|
+
* @param {String} ext
|
|
58
|
+
*/
|
|
59
|
+
function toAudio(buffer, ext) {
|
|
60
|
+
return ffmpeg(buffer, [
|
|
61
|
+
'-vn',
|
|
62
|
+
'-ac', '2',
|
|
63
|
+
'-b:a', '128k',
|
|
64
|
+
'-ar', '44100',
|
|
65
|
+
'-f', 'mp3'
|
|
66
|
+
], ext, 'mp3')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert Audio to Voice Note (Opus/PTT)
|
|
71
|
+
* @param {Buffer} buffer
|
|
72
|
+
* @param {String} ext
|
|
73
|
+
*/
|
|
74
|
+
function toPTT(buffer, ext) {
|
|
75
|
+
return ffmpeg(buffer, [
|
|
76
|
+
'-vn',
|
|
77
|
+
'-c:a', 'libopus',
|
|
78
|
+
'-b:a', '128k',
|
|
79
|
+
'-vbr', 'on',
|
|
80
|
+
'-compression_level', '10'
|
|
81
|
+
], ext, 'opus')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Convert Video to MP4
|
|
86
|
+
* @param {Buffer} buffer
|
|
87
|
+
* @param {String} ext
|
|
88
|
+
*/
|
|
89
|
+
function toVideo(buffer, ext) {
|
|
90
|
+
return ffmpeg(buffer, [
|
|
91
|
+
'-c:v', 'libx264',
|
|
92
|
+
'-c:a', 'aac',
|
|
93
|
+
'-ab', '128k',
|
|
94
|
+
'-ar', '44100',
|
|
95
|
+
'-crf', '32',
|
|
96
|
+
'-preset', 'slow'
|
|
97
|
+
], ext, 'mp4')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
toAudio,
|
|
102
|
+
toPTT,
|
|
103
|
+
toVideo,
|
|
104
|
+
ffmpeg,
|
|
105
|
+
}
|
package/lib/exif.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib/exif.js
|
|
3
|
+
* @description Functions to handle EXIF metadata for WebP images and stickers
|
|
4
|
+
* @author K4lameety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const { tmpdir } = require("os")
|
|
9
|
+
const Crypto = require("crypto")
|
|
10
|
+
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path
|
|
11
|
+
const ff = require('fluent-ffmpeg')
|
|
12
|
+
const webp = require("node-webpmux")
|
|
13
|
+
const path = require("path")
|
|
14
|
+
|
|
15
|
+
ff.setFfmpegPath(ffmpegPath)
|
|
16
|
+
|
|
17
|
+
async function imageToWebp(media) {
|
|
18
|
+
const tmpFileOut = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
19
|
+
const tmpFileIn = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.jpg`)
|
|
20
|
+
|
|
21
|
+
fs.writeFileSync(tmpFileIn, media)
|
|
22
|
+
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
ff(tmpFileIn)
|
|
25
|
+
.on("error", reject)
|
|
26
|
+
.on("end", () => resolve(true))
|
|
27
|
+
.addOutputOptions([
|
|
28
|
+
"-vcodec",
|
|
29
|
+
"libwebp",
|
|
30
|
+
"-vf",
|
|
31
|
+
"scale='min(320,iw)':min'(320,ih)':force_original_aspect_ratio=decrease,fps=15, pad=320:320:-1:-1:color=white@0.0, split [a][b]; [a] palettegen=reserve_transparent=on:transparency_color=ffffff [p]; [b][p] paletteuse"
|
|
32
|
+
])
|
|
33
|
+
.toFormat("webp")
|
|
34
|
+
.save(tmpFileOut)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const buff = fs.readFileSync(tmpFileOut)
|
|
38
|
+
fs.unlinkSync(tmpFileOut)
|
|
39
|
+
fs.unlinkSync(tmpFileIn)
|
|
40
|
+
return buff
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function videoToWebp(media) {
|
|
44
|
+
const tmpFileOut = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
45
|
+
const tmpFileIn = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.mp4`)
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(tmpFileIn, media)
|
|
48
|
+
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
ff(tmpFileIn)
|
|
51
|
+
.on("error", reject)
|
|
52
|
+
.on("end", () => resolve(true))
|
|
53
|
+
.addOutputOptions([
|
|
54
|
+
"-vcodec",
|
|
55
|
+
"libwebp",
|
|
56
|
+
"-vf",
|
|
57
|
+
"scale='min(320,iw)':min'(320,ih)':force_original_aspect_ratio=decrease,fps=15, pad=320:320:-1:-1:color=white@0.0, split [a][b]; [a] palettegen=reserve_transparent=on:transparency_color=ffffff [p]; [b][p] paletteuse",
|
|
58
|
+
"-loop",
|
|
59
|
+
"0",
|
|
60
|
+
"-ss",
|
|
61
|
+
"00:00:00",
|
|
62
|
+
"-t",
|
|
63
|
+
"00:00:05",
|
|
64
|
+
"-preset",
|
|
65
|
+
"default",
|
|
66
|
+
"-an",
|
|
67
|
+
"-vsync",
|
|
68
|
+
"0"
|
|
69
|
+
])
|
|
70
|
+
.toFormat("webp")
|
|
71
|
+
.save(tmpFileOut)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const buff = fs.readFileSync(tmpFileOut)
|
|
75
|
+
fs.unlinkSync(tmpFileOut)
|
|
76
|
+
fs.unlinkSync(tmpFileIn)
|
|
77
|
+
return buff
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function writeExifImg(media, metadata) {
|
|
81
|
+
let wMedia = await imageToWebp(media)
|
|
82
|
+
const tmpFileIn = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
83
|
+
const tmpFileOut = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
84
|
+
fs.writeFileSync(tmpFileIn, wMedia)
|
|
85
|
+
|
|
86
|
+
if (metadata.packname || metadata.author) {
|
|
87
|
+
const img = new webp.Image()
|
|
88
|
+
const json = {
|
|
89
|
+
"sticker-pack-id": metadata.packId || `com.k4lameety.sticker`, // ID Default
|
|
90
|
+
"sticker-pack-name": metadata.packname,
|
|
91
|
+
"sticker-pack-publisher": metadata.author,
|
|
92
|
+
"emojis": metadata.categories ? metadata.categories : [""]
|
|
93
|
+
}
|
|
94
|
+
const exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00])
|
|
95
|
+
const jsonBuff = Buffer.from(JSON.stringify(json), "utf-8")
|
|
96
|
+
const exif = Buffer.concat([exifAttr, jsonBuff])
|
|
97
|
+
exif.writeUIntLE(jsonBuff.length, 14, 4)
|
|
98
|
+
await img.load(tmpFileIn)
|
|
99
|
+
fs.unlinkSync(tmpFileIn)
|
|
100
|
+
img.exif = exif
|
|
101
|
+
await img.save(tmpFileOut)
|
|
102
|
+
return tmpFileOut
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function writeExifVid(media, metadata) {
|
|
107
|
+
let wMedia = await videoToWebp(media)
|
|
108
|
+
const tmpFileIn = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
109
|
+
const tmpFileOut = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
110
|
+
fs.writeFileSync(tmpFileIn, wMedia)
|
|
111
|
+
|
|
112
|
+
if (metadata.packname || metadata.author) {
|
|
113
|
+
const img = new webp.Image()
|
|
114
|
+
const json = {
|
|
115
|
+
"sticker-pack-id": metadata.packId || `com.k4lameety.sticker`,
|
|
116
|
+
"sticker-pack-name": metadata.packname,
|
|
117
|
+
"sticker-pack-publisher": metadata.author,
|
|
118
|
+
"emojis": metadata.categories ? metadata.categories : [""]
|
|
119
|
+
}
|
|
120
|
+
const exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00])
|
|
121
|
+
const jsonBuff = Buffer.from(JSON.stringify(json), "utf-8")
|
|
122
|
+
const exif = Buffer.concat([exifAttr, jsonBuff])
|
|
123
|
+
exif.writeUIntLE(jsonBuff.length, 14, 4)
|
|
124
|
+
await img.load(tmpFileIn)
|
|
125
|
+
fs.unlinkSync(tmpFileIn)
|
|
126
|
+
img.exif = exif
|
|
127
|
+
await img.save(tmpFileOut)
|
|
128
|
+
return tmpFileOut
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function writeExif(media, metadata) {
|
|
133
|
+
let wMedia = /webp/.test(media.mimetype) ? media.data : /image/.test(media.mimetype) ? await imageToWebp(media.data) : /video/.test(media.mimetype) ? await videoToWebp(media.data) : ""
|
|
134
|
+
const tmpFileIn = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
135
|
+
const tmpFileOut = path.join(tmpdir(), `${Crypto.randomBytes(6).readUIntLE(0, 6).toString(36)}.webp`)
|
|
136
|
+
fs.writeFileSync(tmpFileIn, wMedia)
|
|
137
|
+
|
|
138
|
+
if (metadata.packname || metadata.author) {
|
|
139
|
+
const img = new webp.Image()
|
|
140
|
+
const json = {
|
|
141
|
+
"sticker-pack-id": metadata.packId || `com.k4lameety.sticker`,
|
|
142
|
+
"sticker-pack-name": metadata.packname,
|
|
143
|
+
"sticker-pack-publisher": metadata.author,
|
|
144
|
+
"emojis": metadata.categories ? metadata.categories : [""]
|
|
145
|
+
}
|
|
146
|
+
const exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00])
|
|
147
|
+
const jsonBuff = Buffer.from(JSON.stringify(json), "utf-8")
|
|
148
|
+
const exif = Buffer.concat([exifAttr, jsonBuff])
|
|
149
|
+
exif.writeUIntLE(jsonBuff.length, 14, 4)
|
|
150
|
+
await img.load(tmpFileIn)
|
|
151
|
+
fs.unlinkSync(tmpFileIn)
|
|
152
|
+
img.exif = exif
|
|
153
|
+
await img.save(tmpFileOut)
|
|
154
|
+
return tmpFileOut
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function exifAvatar(buffer, packname, author, categories = [''], extra = {}) {
|
|
159
|
+
const img = new webp.Image() // Menggunakan require di atas, bukan import dinamis
|
|
160
|
+
const stickerPackId = extra.packId || Crypto.randomBytes(32).toString('hex')
|
|
161
|
+
const json = {
|
|
162
|
+
'sticker-pack-id': stickerPackId,
|
|
163
|
+
'sticker-pack-name': packname,
|
|
164
|
+
'sticker-pack-publisher': author,
|
|
165
|
+
'emojis': categories,
|
|
166
|
+
'is-avatar-sticker': 1,
|
|
167
|
+
...extra
|
|
168
|
+
}
|
|
169
|
+
let exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00])
|
|
170
|
+
let jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8')
|
|
171
|
+
let exif = Buffer.concat([exifAttr, jsonBuffer])
|
|
172
|
+
exif.writeUIntLE(jsonBuffer.length, 14, 4)
|
|
173
|
+
await img.load(buffer)
|
|
174
|
+
img.exif = exif
|
|
175
|
+
return await img.save(null)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function addExif(webpSticker, packname, author, categories = [''], extra = {}) {
|
|
179
|
+
const img = new webp.Image()
|
|
180
|
+
const stickerPackId = extra.packId || Crypto.randomBytes(32).toString('hex')
|
|
181
|
+
const json = {
|
|
182
|
+
'sticker-pack-id': stickerPackId,
|
|
183
|
+
'sticker-pack-name': packname,
|
|
184
|
+
'sticker-pack-publisher': author,
|
|
185
|
+
'emojis': categories,
|
|
186
|
+
...extra
|
|
187
|
+
}
|
|
188
|
+
let exifAttr = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00])
|
|
189
|
+
let jsonBuffer = Buffer.from(JSON.stringify(json), 'utf8')
|
|
190
|
+
let exif = Buffer.concat([exifAttr, jsonBuffer])
|
|
191
|
+
exif.writeUIntLE(jsonBuffer.length, 14, 4)
|
|
192
|
+
await img.load(webpSticker)
|
|
193
|
+
img.exif = exif
|
|
194
|
+
return await img.save(null)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
imageToWebp,
|
|
199
|
+
videoToWebp,
|
|
200
|
+
writeExifImg,
|
|
201
|
+
writeExifVid,
|
|
202
|
+
writeExif,
|
|
203
|
+
exifAvatar,
|
|
204
|
+
addExif
|
|
205
|
+
}
|
package/lib/function.js
ADDED
package/lib/game.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib/game.js
|
|
3
|
+
* @description Game database handler
|
|
4
|
+
* @author K4lameety
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const pickRandom = (arr) => {
|
|
11
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const asahOtakDb = require('../data/asahotak.json');
|
|
15
|
+
const susunKataDb = require('../data/susunkata.json');
|
|
16
|
+
const tebakBenderaDb = require('../data/tebakbendera.json');
|
|
17
|
+
const tebakKimiaDb = require('../data/tebakkimia.json');
|
|
18
|
+
const tebakKataDb = require('../data/tebakkata.json');
|
|
19
|
+
const tebakGambarDb = require('../data/tebakgambar.json');
|
|
20
|
+
const family100Db = require('../data/family100.json');
|
|
21
|
+
|
|
22
|
+
function asahOtak() {
|
|
23
|
+
return pickRandom(asahOtakDb);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function susunKata() {
|
|
27
|
+
return pickRandom(susunKataDb);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function tebakBendera() {
|
|
31
|
+
return pickRandom(tebakBenderaDb);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function tebakKimia() {
|
|
35
|
+
return pickRandom(tebakKimiaDb);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function tebakKata() {
|
|
39
|
+
return pickRandom(tebakKataDb);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function tebakGambar() {
|
|
43
|
+
return pickRandom(tebakGambarDb);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function family100() {
|
|
47
|
+
return pickRandom(family100Db);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
asahOtak,
|
|
52
|
+
susunKata,
|
|
53
|
+
tebakBendera,
|
|
54
|
+
tebakKimia,
|
|
55
|
+
tebakKata,
|
|
56
|
+
tebakGambar,
|
|
57
|
+
family100
|
|
58
|
+
};
|