@aidulcandra/simple-wa-bot
Version:
A baileys wrapper module
123 lines (112 loc) • 4.66 kB
JavaScript
const {Image} = require('node-webpmux')
const {randomBytes} = require('crypto')
const {fileTypeFromBuffer} = require('file-type')
const {TextEncoder} = require("util")
const {tmpdir} = require("os")
const ffmpeg = require("fluent-ffmpeg")
const sharp = require('sharp')
const {existsSync, read} = require('fs')
const {writeFile, readFile, unlink} = require('fs/promises')
async function createSticker (input, options) {
const buffer = await parseInput(input)
const id = randomBytes(32).toString('hex')
const metadata = {
quality: options?.quality || 100,
type: ['default','crop','full','circle','rounded'].includes(options?.type) ? options.type : 'default',
background: options?.background || 'transparent'
}
const stickerData = JSON.stringify({
'sticker-pack-id': id,
'sticker-pack-name': options?.pack || '',
'sticker-pack-publisher': options?.author || '',
emojis: options?.categories || []
})
const mime = await fileTypeFromBuffer(buffer).then(ft => ft.mime)
const isVideo = mime.startsWith('video')
let image = isVideo
? await videoToGif(buffer)
: buffer
const isWebp = mime.includes('webp')
const isAnimated = isVideo || isWebp || mime.includes('gif')
if (isAnimated && ['crop','circle','rounded'].includes(metadata.type)) {
const filename = `${tmpdir()}/${Math.random().toString(36)}.webp`
await writeFile(filename, image)
image = await crop(filename, isWebp ? 100 : metadata.quality)
await unlink(filename)
}
const img = sharp(image, {animated: isAnimated}).toFormat('webp')
switch (metadata.type) {
case 'crop': img.resize(512, 512, {fit: 'cover', position: sharp.strategy.attention}); break
case 'full': img.resize(512, 512, {fit: 'contain', background: metadata.background}); break
case 'circle': img.resize(512, 512, {fit: 'cover'}).composite([
{
input: Buffer.from(`<svg width="512" height="512"><circle cx="256" cy="256" r="256" fill="black"/></svg>`),
blend: 'dest-in',
gravity: 'northeast',
tile: true
}
]); break
case 'rounded': img.resize(512, 512, {fit: 'cover'}).composite([
{
input: Buffer.from(`<svg width="512" height="512"><rect rx="50" ry="50" width="512" height="512" fill="black"/></svg>`),
blend: 'dest-in',
gravity: 'northeast',
tile: true
}
]); break
}
const converted = await img.webp({quality:metadata.quality,lossless:false}).toBuffer()
const exif = Buffer.concat([
Buffer.from([0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]),
Buffer.from(stickerData, 'utf-8')
])
exif.writeUIntLE(new TextEncoder().encode(stickerData).length, 14, 4)
const media = new Image()
await media.load(converted)
media.exif = exif
const sticker = await media.save(null)
stickerMessage = {sticker}
return stickerMessage
}
async function videoToGif(buffer) {
const filename = `${tmpdir()}/${Math.random().toString(36)}`
const [video, gif] = ['video', 'gif'].map((ext) => `${filename}.${ext}`)
await writeFile(video, buffer)
await new Promise((resolve) => {
ffmpeg(video).save(gif).on('end', resolve)
})
const b = await readFile(gif)
;[video, gif].forEach((file) => unlink(file))
return b
}
async function crop (filename, quality = 100) {
const file = await new Promise(resolve => {
const name = `${tmpdir()}/${Math.random().toString(36)}.webp`
ffmpeg(filename)
.outputOptions([
'-vcodec', 'libwebp',
'-vf', `crop='min(min(iw\,ih)\,256)':'min(min(iw\,ih)\,256)',scale=512:512,setsar=1,fps=15`,
'-loop', '0',
'-preset', 'picture',
'-an',
'-vsync', '0',
'-s', '512:512',
'-qscale', `${quality}`
])
.save(name)
.on('end', () => resolve(name))
})
const b = await readFile(file)
await unlink(file)
return b
}
async function parseInput (input) {
return Buffer.isBuffer(input)
? input
: input.trim().startsWith('<svg')
? Buffer.from(input)
: existsSync(input)
? readFile(input)
: fetch(input).then(r=>r.arrayBuffer()).then(ab => Buffer.from(ab))
}
module.exports = {createSticker}