UNPKG

@aidulcandra/simple-wa-bot

Version:

A baileys wrapper module

124 lines (113 loc) 4.69 kB
const {Image} = require('node-webpmux') const {randomBytes} = require('crypto') const fileType = require('file-type') const {fileTypeFromBuffer} = fileType 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}