diff --git a/Dockerfile b/Dockerfile index 4e9828e..b020e18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,4 +7,4 @@ WORKDIR /home/node/bot RUN npm i -CMD [ "node", "index.js" ] +CMD [ "node", "index.js" ] \ No newline at end of file diff --git a/config.js b/config.js index 1b974c9..96c82e8 100644 --- a/config.js +++ b/config.js @@ -1,4 +1,4 @@ module.exports = { - prefix: `trol`, - token: require(`./token`) -} + prefix: `trol`, + token: require(`./token`) +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 0378b91..132d552 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,4 +5,4 @@ services: build: . volumes: - ./token.js:/home/node/bot/token.js - - ./config.js:/home/node/bot/config.js + - ./config.js:/home/node/bot/config.js \ No newline at end of file diff --git a/index.js b/index.js index 4932b72..a462daf 100644 --- a/index.js +++ b/index.js @@ -14,80 +14,80 @@ const client = new Client({ intents: [Intents.FLAGS.GUILD_MESSAGES, Intents.FLAG client.login(config.token); if (!fs.existsSync(tempDir)) - fs.mkdirSync(tempDir); + fs.mkdirSync(tempDir); else - fs.readdir(tempDir, (err, files) => - { - for (const file of files) - { - const filepath = path.join(tempDir, file); - fs.rm(filepath, { recursive: true }, err => { if (err) { throw err; } }) - } - }); + fs.readdir(tempDir, (err, files) => + { + for (const file of files) + { + const filepath = path.join(tempDir, file); + fs.rm(filepath, { recursive: true }, err => { if (err) { throw err; } }) + } + }); client.once("ready", () => { - console.log("loaded trol bot"); + console.log("loaded trol bot"); }); client.on("messageCreate", (msg) => { - if (msg.author.bot) return; + if (msg.author.bot) return; - const args = msg.content.split(/ +/); - const command = args.shift().toLowerCase(); + const args = msg.content.split(/ +/); + const command = args.shift().toLowerCase(); - if (command == config.prefix) - { - if (args[0] == "help") - { - const embed = new MessageEmbed() - .setColor("#ffffff") - .setTitle(config.prefix + " helpy :^)") - .addFields( - { name: "trol", value: "uses attachment (if any)" }, - { name: "trol ", value: "uses image link" }, - { name: "trol me", value: "uses sender's pfp" }, - { name: "trol <@mention>", value: "uses mentioned user's pfp" } - ); - msg.channel.send({ embeds: [embed] }); - } - else - { - var link = args[0]; - var file = null; - if (msg.attachments.size == 1) file = msg.attachments.first().attachment; - if (link != null && file == null) - { - if (link == "me") file = msg.author.avatarURL({ format: "png" }); - if (msg.mentions.users.size > 0) file = msg.mentions.users.values().next().value.avatarURL({ format: "png" }); - if (file == null) file = link; - } + if (command == config.prefix) + { + if (args[0] == "help") + { + const embed = new MessageEmbed() + .setColor("#ffffff") + .setTitle(config.prefix + " helpy :^)") + .addFields( + { name: "trol", value: "uses attachment (if any)" }, + { name: "trol ", value: "uses image link" }, + { name: "trol me", value: "uses sender's pfp" }, + { name: "trol <@mention>", value: "uses mentioned user's pfp" } + ); + msg.channel.send({ embeds: [embed] }); + } + else + { + var link = args[0]; + var file = null; + if (msg.attachments.size == 1) file = msg.attachments.first().attachment; + if (link != null && file == null) + { + if (link == "me") file = msg.author.avatarURL({ format: "png" }); + if (msg.mentions.users.size > 0) file = msg.mentions.users.values().next().value.avatarURL({ format: "png" }); + if (file == null) file = link; + } - if (file != null) - { - const downloadDir = path.join(tempDir, "download"); - if (!fs.existsSync(downloadDir)) - fs.mkdirSync(downloadDir); + if (file != null) + { + const downloadDir = path.join(tempDir, "download"); + if (!fs.existsSync(downloadDir)) + fs.mkdirSync(downloadDir); - var tempFilePath = path.join(downloadDir, `dl_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}`); - request.head(file, (err, res, body) => - { - if (err) - { - console.log(err); - return; - } - request(file).pipe(fs.createWriteStream(tempFilePath)).on('close', () => - { - generateVideo(tempFilePath, 5).then(result => - msg.channel.send({ files: [result] }).then(value => - fs.promises.rm(result) - ) - ); - }); - }); - } - } - } -}); + var tempFilePath = path.join(downloadDir, `dl_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}`); + request.head(file, (err, res, body) => + { + if (err) + { + console.log(err); + return; + } + request(file).pipe(fs.createWriteStream(tempFilePath)).on('close', () => + { + generateVideo(tempFilePath, 5).then(result => + msg.channel.send({ files: [result] }).then(value => + fs.promises.rm(result) + ) + ); + }); + }); + } + } + } +}); \ No newline at end of file diff --git a/shitpost-audio.js b/shitpost-audio.js index 0afef86..5bb20fc 100644 --- a/shitpost-audio.js +++ b/shitpost-audio.js @@ -10,16 +10,16 @@ let loadedSounds = []; let offset = 0; for (let p in soundPaths) { - const soundPath = path.join(soundDir, soundPaths[p]); - if (fs.statSync(soundPath).isDirectory()) - { - offset++; - continue; - } + const soundPath = path.join(soundDir, soundPaths[p]); + if (fs.statSync(soundPath).isDirectory()) + { + offset++; + continue; + } - let buffer = fs.readFileSync(soundPath); - let decoded = wav.decode(buffer); - loadedSounds[p - offset] = decoded; + let buffer = fs.readFileSync(soundPath); + let decoded = wav.decode(buffer); + loadedSounds[p - offset] = decoded; } // somehow this works @@ -28,97 +28,97 @@ for (let p in soundPaths) // so i just made sure the sound files were exported with 48000 bytes per second async function mergeBuffers (buffers, playtime = -1) { - // for the final length, either use the specified playtime, or the length of the first sound - let length = playtime > 0 ? playtime * 48000 : buffers[0].length; - let finalBuffer = []; - for (let f_nr = 0; f_nr < length; f_nr++) - { - // literally add all the sound bytes together - // yeah, this works?? - let value = 0; - for (let buffer of buffers) - if (buffer[f_nr]) value += buffer[f_nr]; - finalBuffer.push(value); - } - return Float32Array.from(finalBuffer); + // for the final length, either use the specified playtime, or the length of the first sound + let length = playtime > 0 ? playtime * 48000 : buffers[0].length; + let finalBuffer = []; + for (let f_nr = 0; f_nr < length; f_nr++) + { + // literally add all the sound bytes together + // yeah, this works?? + let value = 0; + for (let buffer of buffers) + if (buffer[f_nr]) value += buffer[f_nr]; + finalBuffer.push(value); + } + return Float32Array.from(finalBuffer); } class MemeAudio { - constructor() - { - this.tracks = []; - } + constructor() + { + this.tracks = []; + } - playSound (soundId, position = 0, track = -1) - { - // track not specified, go up one from previous - if (track < 0) - track = this.tracks.length; + playSound (soundId, position = 0, track = -1) + { + // track not specified, go up one from previous + if (track < 0) + track = this.tracks.length; - // track doesn't exist, initialize a new one - let stereoTrackBuffer = this.tracks[track]; - if (!this.tracks[track]) - stereoTrackBuffer = [[], []]; + // track doesn't exist, initialize a new one + let stereoTrackBuffer = this.tracks[track]; + if (!this.tracks[track]) + stereoTrackBuffer = [[], []]; - // get the data of the sound to play - let sound = loadedSounds[soundId]; - for (let ch in stereoTrackBuffer) // should just be left/right audio channels - { - let loadedChannel = sound.channelData[ch]; - let byteArray = []; + // get the data of the sound to play + let sound = loadedSounds[soundId]; + for (let ch in stereoTrackBuffer) // should just be left/right audio channels + { + let loadedChannel = sound.channelData[ch]; + let byteArray = []; - // add empty space until the sound should play - for (let i = 0; i < position * 48000; i++) - byteArray[i] = 0; + // add empty space until the sound should play + for (let i = 0; i < position * 48000; i++) + byteArray[i] = 0; - // copy audio data from sound's channel - for (let byte of loadedChannel) - byteArray.push(byte); + // copy audio data from sound's channel + for (let byte of loadedChannel) + byteArray.push(byte); - // convert to Float32array - stereoTrackBuffer[ch] = Float32Array.from(byteArray); - } + // convert to Float32array + stereoTrackBuffer[ch] = Float32Array.from(byteArray); + } - // add to timeline - this.tracks[track] = stereoTrackBuffer; + // add to timeline + this.tracks[track] = stereoTrackBuffer; - return this; - } + return this; + } - async encode (duration) - { - let ac = { sampleRate: 48000, float: true, bitDepth: 32 }; // encoding config + async encode (duration) + { + let ac = { sampleRate: 48000, float: true, bitDepth: 32 }; // encoding config - // make an array of all left channel audio and all right channel audio - let combinedTrack = [[], []]; - for (let track in this.tracks) - { - combinedTrack[0].push(this.tracks[track][0]); - combinedTrack[1].push(this.tracks[track][1]); - } + // make an array of all left channel audio and all right channel audio + let combinedTrack = [[], []]; + for (let track in this.tracks) + { + combinedTrack[0].push(this.tracks[track][0]); + combinedTrack[1].push(this.tracks[track][1]); + } - // make a single stereo track - let mergedTrack = await Promise.all([mergeBuffers(combinedTrack[0], duration), mergeBuffers(combinedTrack[1], duration)]); + // make a single stereo track + let mergedTrack = await Promise.all([mergeBuffers(combinedTrack[0], duration), mergeBuffers(combinedTrack[1], duration)]); - let encoded = wav.encode(mergedTrack, ac); - return encoded; - } + let encoded = wav.encode(mergedTrack, ac); + return encoded; + } } async function generateAudio (seconds) { - let audio = new MemeAudio(); - let pos = 0; - while (pos < seconds) - { - audio.playSound(Math.floor(Math.random() * loadedSounds.length), pos); - pos += (Math.random() * 1) - 0.25; - } - let encoded = await audio.encode(seconds); - resultPath = path.join(tempDir, `snd_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}.wav`); - await fs.promises.writeFile(resultPath, encoded); - return resultPath; + let audio = new MemeAudio(); + let pos = 0; + while (pos < seconds) + { + audio.playSound(Math.floor(Math.random() * loadedSounds.length), pos); + pos += (Math.random() * 1) - 0.25; + } + let encoded = await audio.encode(seconds); + resultPath = path.join(tempDir, `snd_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}.wav`); + await fs.promises.writeFile(resultPath, encoded); + return resultPath; } module.exports = { generateAudio }; \ No newline at end of file diff --git a/shitpost-video.js b/shitpost-video.js index 30923f5..0268209 100644 --- a/shitpost-video.js +++ b/shitpost-video.js @@ -11,180 +11,179 @@ let images = []; let imgPromises = []; fs.promises.readdir(imagesDir).then(filenames => { - for (var filename of filenames) - imgPromises.push(loadImage(path.join(imagesDir, filename)).then(data => images.push(data))); + for (var filename of filenames) + imgPromises.push(loadImage(path.join(imagesDir, filename)).then(data => images.push(data))); - Promise.all(imgPromises).then(() => initialized = true); + Promise.all(imgPromises).then(() => initialized = true); }); async function generateFrames (sourceImagePath, duration) { - if (!initialized) - return null; + if (!initialized) + return null; - let outputPath = path.join(tempDir, "frames_" + Math.floor(Math.random() * 10000).toString().padStart(4, "0")); - let sourceImage; + let outputPath = path.join(tempDir, "frames_" + Math.floor(Math.random() * 10000).toString().padStart(4, "0")); + let sourceImage; - await Promise.all([ - fs.promises.mkdir(outputPath), - loadImage(sourceImagePath).then(data => sourceImage = data) - ]); + await Promise.all([ + fs.promises.mkdir(outputPath), + loadImage(sourceImagePath).then(data => sourceImage = data) + ]); - // settings - const fps = 20; - const res = 256; + // settings + const fps = 20; + const res = 256; - const totalFrames = fps * duration; + const totalFrames = fps * duration; - // init timeline - let objects = []; + // init timeline + let objects = []; - // define classes - class Object - { - constructor(arg = {}) - { - this.imageId = arg.imageId || -1; - this.spawnFrame = arg.spawnFrame || 0; - this.lifetime = fps * duration; - this.pos = arg.pos || [0.5, 0.5]; - this.scale = arg.scale || [1, 1]; - this.pivot = arg.pivot || [0.5, 0.5]; - this.rot = arg.rot || 0; - this.blink = false; - this.sineDur = 0; + // define classes + class Object + { + constructor(arg = {}) + { + this.imageId = arg.imageId || -1; + this.spawnFrame = arg.spawnFrame || 0; + this.lifetime = fps * duration; + this.pos = arg.pos || [0.5, 0.5]; + this.scale = arg.scale || [1, 1]; + this.pivot = arg.pivot || [0.5, 0.5]; + this.rot = arg.rot || 0; + this.blink = false; + this.sineDur = 0; - this.actions = arg.actions || []; + this.actions = arg.actions || []; - objects.push(this); - } - } + objects.push(this); + } + } - class Action - { - constructor(arg = {}) - { - this.type = arg.type; - this.delta = arg.delta; - this.start = arg.start || 0; - } - } + class Action + { + constructor(arg = {}) + { + this.type = arg.type; + this.delta = arg.delta; + this.start = arg.start || 0; + } + } - // generate timeline - const base = new Object(); - if (randomChance(80)) base.actions.push(new Action({ type: "scale", delta: [randomRange(0, 0.1), randomRange(0, 0.05)] })); + // generate timeline + const base = new Object(); + if (randomChance(80)) base.actions.push(new Action({ type: "scale", delta: [randomRange(0, 0.1), randomRange(0, 0.05)] })); - for (i = 0; i < randomRange(2, 12); i++) - { - const obj = new Object(); - obj.pos = [Math.random(), Math.random()]; - const uniformScale = randomRange(0.1, 0.7); - obj.scale = [uniformScale + Math.random() * 0.01, uniformScale + Math.random() * 0.01]; - obj.imageId = Math.floor(Math.random() * images.length); - obj.spawnFrame = Math.floor(Math.random() * totalFrames); - obj.lifetime = randomRange(fps * 0.25, fps * 3); - obj.pivot = [Math.random(), Math.random()]; + for (i = 0; i < randomRange(2, 12); i++) + { + const obj = new Object(); + obj.pos = [Math.random(), Math.random()]; + const uniformScale = randomRange(0.1, 0.7); + obj.scale = [uniformScale + Math.random() * 0.01, uniformScale + Math.random() * 0.01]; + obj.imageId = Math.floor(Math.random() * images.length); + obj.spawnFrame = Math.floor(Math.random() * totalFrames); + obj.lifetime = randomRange(fps * 0.25, fps * 3); + obj.pivot = [Math.random(), Math.random()]; - if (randomChance(25)) - obj.actions.push(new Action({ type: "rotate", delta: randomRange(-8, 8) })) - if (randomChance(25)) - obj.actions.push(new Action({ type: "scale", delta: [randomRange(0, 0.05), 0] })) + if (randomChance(25)) + obj.actions.push(new Action({ type: "rotate", delta: randomRange(-8, 8) })) + if (randomChance(25)) + obj.actions.push(new Action({ type: "scale", delta: [randomRange(0, 0.05), 0] })) - if (randomChance(25)) - obj.blink = true; - else if (randomChance(25)) - obj.actions.push(new Action({ type: "move", delta: [randomRange(-0.05, 0.05), randomRange(-0.05, 0.05)] })) + if (randomChance(25)) + obj.blink = true; + else if (randomChance(25)) + obj.actions.push(new Action({ type: "move", delta: [randomRange(-0.05, 0.05), randomRange(-0.05, 0.05)] })) - if (randomChance(75)) - obj.sineDur = randomRange(1, 5); - } + if (randomChance(75)) + obj.sineDur = randomRange(1, 5); + } - // sort by spawnframe - objects.sort((a, b) => (a.spawnFrame > b.spawnFrame) ? 1 : -1); + // sort by spawnframe + objects.sort((a, b) => (a.spawnFrame > b.spawnFrame) ? 1 : -1); - // render frames - let framePromises = []; - for (var _f = 0; _f < totalFrames; _f++) - { - const f = _f; - framePromises.push(async () => - { - const canvas = createCanvas(res, res); - const ctx = canvas.getContext("2d"); + // render frames + let framePromises = []; + for (var _f = 0; _f < totalFrames; _f++) + { + const f = _f; + framePromises.push(async () => + { + const canvas = createCanvas(res, res); + const ctx = canvas.getContext("2d"); - // draw objects - for (var object of objects) - { - var life = f - object.spawnFrame; // frames passed since init - if (life < 0) continue; - if (life > object.lifetime) continue; - if (object.blink && life % 2 == 1) continue; + // draw objects + for (var object of objects) + { + var life = f - object.spawnFrame; // frames passed since init + if (life < 0) continue; + if (life > object.lifetime) continue; + if (object.blink && life % 2 == 1) continue; - var pos = object.pos; - var rot = object.rot; - var scale = object.scale; - var image = object.imageId == -1 ? sourceImage : images[object.imageId]; + var pos = object.pos; + var rot = object.rot; + var scale = object.scale; + var image = object.imageId == -1 ? sourceImage : images[object.imageId]; - // process actions - for (var action of object.actions) - { - if (f < object.spawnFrame + action.start + 1 || ((action.dur > 0) && (f > object.spawnFrame + action.start + action.dur))) continue; // ignore - const progress = object.sineDur > 0 ? 10 * Math.sin(life * object.sineDur) : life; - switch (action.type) - { - case "move": - pos = [object.pos[0] + action.delta[0] * progress, object.pos[1] + action.delta[1] * progress]; - break; + // process actions + for (var action of object.actions) + { + if (f < object.spawnFrame + action.start + 1 || ((action.dur > 0) && (f > object.spawnFrame + action.start + action.dur))) continue; // ignore + const progress = object.sineDur > 0 ? 10 * Math.sin(life * object.sineDur) : life; + switch (action.type) + { + case "move": + pos = [object.pos[0] + action.delta[0] * progress, object.pos[1] + action.delta[1] * progress]; + break; - case "scale": - scale = [object.scale[0] + action.delta[0] * progress, object.scale[1] + action.delta[1] * progress]; - break; + case "scale": + scale = [object.scale[0] + action.delta[0] * progress, object.scale[1] + action.delta[1] * progress]; + break; - case "rotate": - rot = object.rot + action.delta * life; - break; - } - } + case "rotate": + rot = object.rot + action.delta * life; + break; + } + } - var size = [scale[0] * res, scale[1] * res]; // size in pixels - var cpos = [pos[0] * res, pos[1] * res]; // center pos - var blpos = [cpos[0] - size[0] * object.pivot[0], cpos[1] - size[1] * object.pivot[1]] // bottom left pos + var size = [scale[0] * res, scale[1] * res]; // size in pixels + var cpos = [pos[0] * res, pos[1] * res]; // center pos + var blpos = [cpos[0] - size[0] * object.pivot[0], cpos[1] - size[1] * object.pivot[1]] // bottom left pos - // save base canvas transform - ctx.save(); + // save base canvas transform + ctx.save(); - // rotate canvas to match object rotation - ctx.translate(cpos[0], cpos[1]); - ctx.rotate(rot * Math.PI / 180); - ctx.translate(-cpos[0], -cpos[1]); + // rotate canvas to match object rotation + ctx.translate(cpos[0], cpos[1]); + ctx.rotate(rot * Math.PI / 180); + ctx.translate(-cpos[0], -cpos[1]); - // draw - ctx.drawImage(image, blpos[0], blpos[1], size[0], size[1]) + // draw + ctx.drawImage(image, blpos[0], blpos[1], size[0], size[1]) - // restore transform - ctx.restore(); + // restore transform + ctx.restore(); - // save the frame - const buffer = canvas.toBuffer("image/png"); - await fs.promises.writeFile(path.join(outputPath, `${f}.png"`), buffer); - } - }); - } + // save the frame + const buffer = canvas.toBuffer("image/png"); + await fs.promises.writeFile(path.join(outputPath, `${f}.png"`), buffer); + } + }); + } - await Promise.all(framePromises.map(fn => fn())); + await Promise.all(framePromises.map(fn => fn())); - return outputPath; + return outputPath; } function randomRange (x, y) { - return Math.random() * (y - x) + x; + return Math.random() * (y - x) + x; } function randomChance (percent) { - return (Math.random() * 100) <= percent; + return (Math.random() * 100) <= percent; } -module.exports = { generateFrames }; - +module.exports = { generateFrames }; \ No newline at end of file diff --git a/shitpost.js b/shitpost.js index 6bbe8f7..249f98b 100644 --- a/shitpost.js +++ b/shitpost.js @@ -5,25 +5,25 @@ const fs = require("fs"); async function generateVideo (sourceImg, duration) { - let [framesPath, audioPath] = await Promise.all([ - generateFrames(sourceImg, duration), - generateAudio(duration) - ]); + let [framesPath, audioPath] = await Promise.all([ + generateFrames(sourceImg, duration), + generateAudio(duration) + ]); - const outputPath = `./temp/output_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}.mp4`; + const outputPath = `./temp/output_${Math.floor(Math.random() * 10000).toString().padStart(4, "0")}.mp4`; - const child = spawn(`ffmpeg -i ${audioPath} -r 20 -i ${framesPath}/%d.png -c:v libx264 -vf fps=20 -pix_fmt yuv420p ${outputPath}`, { shell: true }) - child.on("exit", (code) => { finished = true; }); + const child = spawn(`ffmpeg -i ${audioPath} -r 20 -i ${framesPath}/%d.png -c:v libx264 -vf fps=20 -pix_fmt yuv420p ${outputPath}`, { shell: true }) + child.on("exit", (code) => { finished = true; }); - let finished = false; - while (!finished) await new Promise(resolve => setTimeout(resolve, 10)); + let finished = false; + while (!finished) await new Promise(resolve => setTimeout(resolve, 10)); - // delete temp files - fs.promises.rm(framesPath, { recursive: true }); - fs.promises.rm(audioPath); - fs.promises.rm(sourceImg); + // delete temp files + fs.promises.rm(framesPath, { recursive: true }); + fs.promises.rm(audioPath); + fs.promises.rm(sourceImg); - return outputPath; + return outputPath; } module.exports = { generateVideo }; \ No newline at end of file diff --git a/token.js b/token.js index 9543a98..f94be41 100644 --- a/token.js +++ b/token.js @@ -1 +1 @@ -module.exports = `your.bot.token`; +module.exports = `your.bot.token`; \ No newline at end of file