Caption now supports more unicode characters, more api work, fixed multiple bugs

This commit is contained in:
TheEssem 2020-11-05 15:40:18 -06:00
parent a03d3a5e79
commit 0600cf230f
11 changed files with 174 additions and 50 deletions

View file

@ -40,6 +40,8 @@ TEMPDIR=
# Set this to true if you're using PM2 to manage the bot # Set this to true if you're using PM2 to manage the bot
PMTWO=false PMTWO=false
# Set this to true if you want to use the image API
API=false
# Enable/disable Twitter bot (true/false) # Enable/disable Twitter bot (true/false)
TWITTER=false TWITTER=false

View file

@ -1,11 +1,14 @@
require("dotenv").config(); require("dotenv").config();
const magick = require("../utils/image.js"); const magick = require("../utils/image.js");
const Job = require("./job.js");
const { version } = require("../package.json"); const { version } = require("../package.json");
const express = require("express"); const express = require("express");
const execPromise = require("util").promisify(require("child_process").exec); const execPromise = require("util").promisify(require("child_process").exec);
const app = express(); const app = express();
const port = 3000; const port = 3000;
const jobs = new Map();
app.get("/", (req, res) => { app.get("/", (req, res) => {
res.send(`esmBot v${version}`); res.send(`esmBot v${version}`);
}); });
@ -22,19 +25,71 @@ app.post("/run", express.json(), async (req, res, next) => {
return res.sendStatus(400); return res.sendStatus(400);
} }
object.type = type.split("/")[1]; object.type = type.split("/")[1];
if (object.type !== "gif" && object.onlyGIF) return res.send("nogif"); if (object.type !== "gif" && object.onlyGIF) return res.send({
status: "nogif"
});
object.delay = object.delay ? object.delay : 0;
}
const id = Math.random().toString(36).substring(2, 15);
if (object.type === "gif" && !object.delay) {
const delay = (await execPromise(`ffprobe -v 0 -of csv=p=0 -select_streams v:0 -show_entries stream=r_frame_rate ${object.path}`)).stdout.replace("\n", ""); const delay = (await execPromise(`ffprobe -v 0 -of csv=p=0 -select_streams v:0 -show_entries stream=r_frame_rate ${object.path}`)).stdout.replace("\n", "");
object.delay = (100 / delay.split("/")[0]) * delay.split("/")[1]; object.delay = (100 / delay.split("/")[0]) * delay.split("/")[1];
} }
const job = new Job(object);
const data = await magick.run(object, true); jobs.set(id, job);
res.contentType(type ? type : "png"); res.send({
res.send(data); id: id,
status: "queued"
});
job.run();
} catch (e) { } catch (e) {
next(e); next(e);
} }
}); });
app.get("/status", (req, res) => {
if (!req.query.id) return res.sendStatus(400);
const job = jobs.get(req.query.id);
if (!job) return res.sendStatus(400);
const timeout = setTimeout(function() {
job.removeAllListeners();
return res.send({
id: req.query.id,
status: job.status
});
}, 10000);
job.once("data", function() {
clearTimeout(timeout);
res.send({
id: req.query.id,
status: job.status
});
//jobs.delete(req.query.id);
});
job.on("error", function(e) {
clearTimeout(timeout);
res.status(500);
res.send({
id: req.query.id,
status: job.status,
error: e
});
jobs.delete(req.query.id);
});
});
app.get("/image", (req, res) => {
if (!req.query.id) return res.sendStatus(400);
const job = jobs.get(req.query.id);
if (!job) return res.sendStatus(400);
if (!job.data) return res.sendStatus(400);
if (job.error) return;
jobs.delete(req.query.id);
res.contentType(job.options.type ? job.options.type : "png");
return res.send(job.data);
});
app.listen(port, () => { app.listen(port, () => {
console.log(`Started image API on port ${port}.`); console.log(`Started image API on port ${port}.`);
}); });

28
api/job.js Normal file
View file

@ -0,0 +1,28 @@
const { EventEmitter } = require("events");
const magick = require("../utils/image.js");
class Job extends EventEmitter {
constructor(options) {
super();
this.options = options;
this.status = "queued";
this.data = null;
this.error = null;
}
run() {
this.status = "processing";
magick.run(this.options, true).then(data => {
this.status = "success";
this.data = data;
return this.emit("data", data, this.options.type);
}).catch(e => {
this.status = "error";
this.error = e;
return this.emit("error", e);
});
return;
}
}
module.exports = Job;

View file

@ -1,11 +1,10 @@
const db = require("../utils/database.js"); const db = require("../utils/database.js");
const logger = require("../utils/logger.js"); const logger = require("../utils/logger.js");
const misc = require("../utils/misc.js"); const misc = require("../utils/misc.js");
const client = require("../utils/client.js");
// run when the bot is added to a guild // run when the bot is added to a guild
module.exports = async (guild) => { module.exports = async (guild) => {
logger.log("info", `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot. Owner: ${client.users.get(guild.ownerID).username}#${client.users.get(guild.ownerID).discriminator} (${guild.ownerID})`); logger.log("info", `[GUILD JOIN] ${guild.name} (${guild.id}) added the bot.`);
const guildDB = new db.guilds({ const guildDB = new db.guilds({
id: guild.id, id: guild.id,
tags: misc.tagDefaults, tags: misc.tagDefaults,
@ -15,4 +14,5 @@ module.exports = async (guild) => {
tagsDisabled: false tagsDisabled: false
}); });
await guildDB.save(); await guildDB.save();
return guildDB;
}; };

View file

@ -3,6 +3,7 @@ const client = require("../utils/client.js");
const database = require("../utils/database.js"); const database = require("../utils/database.js");
const logger = require("../utils/logger.js"); const logger = require("../utils/logger.js");
const collections = require("../utils/collections.js"); const collections = require("../utils/collections.js");
const guildCreate = require("./guildCreate.js");
const commands = [...collections.aliases.keys(), ...collections.commands.keys()]; const commands = [...collections.aliases.keys(), ...collections.commands.keys()];
// run when someone sends a message // run when someone sends a message
@ -24,7 +25,10 @@ module.exports = async (message) => {
if (!valid) return; if (!valid) return;
// prefix can be a mention or a set of special characters // prefix can be a mention or a set of special characters
const guildDB = message.channel.guild ? await database.guilds.findOne({ id: message.channel.guild.id }).lean().exec() : null; let guildDB = message.channel.guild ? await database.guilds.findOne({ id: message.channel.guild.id }).lean().exec() : null;
if (message.channel.guild && !guildDB) {
guildDB = await guildCreate(message.channel.guild);
}
// there's a bit of a workaround here due to member.mention not accounting for both mention types // there's a bit of a workaround here due to member.mention not accounting for both mention types
const prefix = message.channel.guild ? (message.content.startsWith(message.channel.guild.members.get(client.user.id).mention) ? `${message.channel.guild.members.get(client.user.id).mention} ` : (message.content.startsWith(`<@${client.user.id}>`) ? `<@${client.user.id}> ` : guildDB.prefix)) : ""; const prefix = message.channel.guild ? (message.content.startsWith(message.channel.guild.members.get(client.user.id).mention) ? `${message.channel.guild.members.get(client.user.id).mention} ` : (message.content.startsWith(`<@${client.user.id}>`) ? `<@${client.user.id}> ` : guildDB.prefix)) : "";

View file

@ -30,7 +30,7 @@ module.exports = async () => {
tagsDisabled: false tagsDisabled: false
}); });
await newGuild.save(); await newGuild.save();
} else if (guildDB) { } else {
if (!guildDB.warns) { if (!guildDB.warns) {
logger.log(`Creating warn object for guild ${id}...`); logger.log(`Creating warn object for guild ${id}...`);
guildDB.set("warns", {}); guildDB.set("warns", {});

View file

@ -23,10 +23,10 @@ class CaptionWorker : public Napi::AsyncWorker {
Image caption_image(Geometry(query), Color("white")); Image caption_image(Geometry(query), Color("white"));
caption_image.fillColor("black"); caption_image.fillColor("black");
caption_image.alpha(true); caption_image.alpha(true);
caption_image.font("./assets/caption.otf"); caption_image.font("Futura");
caption_image.fontPointsize(width / 10); caption_image.fontPointsize(width / 10);
caption_image.textGravity(Magick::CenterGravity); caption_image.textGravity(Magick::CenterGravity);
caption_image.read("caption:" + caption); caption_image.read("pango:" + caption);
caption_image.extent(Geometry(width, caption_image.rows() + (width / 10)), Magick::CenterGravity); caption_image.extent(Geometry(width, caption_image.rows() + (width / 10)), Magick::CenterGravity);
coalesceImages(&coalesced, frames.begin(), frames.end()); coalesceImages(&coalesced, frames.begin(), frames.end());

8
servers.json Normal file
View file

@ -0,0 +1,8 @@
{
"lava": [
{ "id": "1", "host": "localhost", "port": 2333, "password": "youshallnotpass" }
],
"image": [
"http://localhost:3000"
]
}

View file

@ -4,32 +4,45 @@ const { promisify } = require("util");
const AbortController = require("abort-controller"); const AbortController = require("abort-controller");
const fileType = require("file-type"); const fileType = require("file-type");
const execPromise = promisify(require("child_process").exec); const execPromise = promisify(require("child_process").exec);
const servers = require("../servers.json").image;
const formats = ["image/jpeg", "image/png", "image/webp", "image/gif"]; const formats = ["image/jpeg", "image/png", "image/webp", "image/gif"];
exports.run = async (object, fromAPI = false) => { exports.run = async (object, fromAPI = false) => {
if (process.env.API === "true" && !fromAPI) { if (process.env.API === "true" && !fromAPI) {
const req = await fetch(`${process.env.API_URL}/run`, { const currentServer = servers[Math.floor(Math.random() * servers.length)];
const req = await fetch(`${currentServer}/run`, {
method: "POST", method: "POST",
body: JSON.stringify(object), body: JSON.stringify(object),
headers: { headers: {
"Content-Type": "application/json" "Content-Type": "application/json"
} }
}); });
const buffer = await req.buffer(); const json = await req.json();
console.log(buffer.toString()); if (json.status === "nogif") return {
if (buffer.toString() === "nogif") return {
buffer: "nogif", buffer: "nogif",
type: null type: null
}; };
return { let data;
buffer: buffer, while (!data) {
type: req.headers.get("content-type").split("/")[1] const statusReq = await fetch(`${currentServer}/status?id=${json.id}`);
}; const statusJSON = await statusReq.json();
if (statusJSON.status === "success") {
const imageReq = await fetch(`${currentServer}/image?id=${json.id}`);
data = {
buffer: await imageReq.buffer(),
type: imageReq.headers.get("content-type").split("/")[1]
};
} else if (statusJSON.status === "error") {
throw new Error(statusJSON.error);
}
}
return data;
} else { } else {
let type; let type;
if (!fromAPI && object.path) { if (!fromAPI && object.path) {
type = (object.type ? object.type : await this.getType(object.path)).split("/")[1]; const newType = (object.type ? object.type : await this.getType(object.path));
type = newType ? newType.split("/")[1] : "png";
if (type !== "gif" && object.onlyGIF) return { if (type !== "gif" && object.onlyGIF) return {
buffer: "nogif", buffer: "nogif",
type: null type: null

View file

@ -1,4 +1,5 @@
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const execPromise = require("util").promisify(require("child_process").exec);
// gets the proper image paths // gets the proper image paths
const getImage = async (image, image2, gifv = false) => { const getImage = async (image, image2, gifv = false) => {
@ -8,10 +9,15 @@ const getImage = async (image, image2, gifv = false) => {
path: image path: image
}; };
if (gifv) { if (gifv) {
if (image2.includes("tenor.com") && process.env.TENOR !== "") { if (image2.includes("tenor.com")) {
const data = await fetch(`https://api.tenor.com/v1/gifs?ids=${image2.split("-").pop()}&key=${process.env.TENOR}`); if (process.env.TENOR !== "") {
const json = await data.json(); const data = await fetch(`https://api.tenor.com/v1/gifs?ids=${image2.split("-").pop()}&key=${process.env.TENOR}`);
payload.path = json.results[0].media[0].gif.url; const json = await data.json();
payload.path = json.results[0].media[0].gif.url;
} else {
const delay = (await execPromise(`ffprobe -v 0 -of csv=p=0 -select_streams v:0 -show_entries stream=r_frame_rate ${image}`)).stdout.replace("\n", "");
payload.delay = (100 / delay.split("/")[0]) * delay.split("/")[1];
}
} else if (image2.includes("giphy.com")) { } else if (image2.includes("giphy.com")) {
payload.path = `https://media0.giphy.com/media/${image2.split("-").pop()}/giphy.gif`; payload.path = `https://media0.giphy.com/media/${image2.split("-").pop()}/giphy.gif`;
} else if (image2.includes("imgur.com")) { } else if (image2.includes("imgur.com")) {

View file

@ -6,7 +6,7 @@ const moment = require("moment");
require("moment-duration-format"); require("moment-duration-format");
const { Manager } = require("@lavacord/eris"); const { Manager } = require("@lavacord/eris");
const nodes = require("../lavanodes.json"); const nodes = require("../servers.json").lava;
exports.players = new Map(); exports.players = new Map();
@ -51,7 +51,8 @@ exports.play = async (sound, message, music = false) => {
if (!message.channel.guild.members.get(client.user.id).permission.has("voiceConnect") || !message.channel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I can't join this voice channel!`); if (!message.channel.guild.members.get(client.user.id).permission.has("voiceConnect") || !message.channel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I can't join this voice channel!`);
const voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID); const voiceChannel = message.channel.guild.channels.get(message.member.voiceState.channelID);
if (!voiceChannel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I don't have permission to join this voice channel!`); if (!voiceChannel.permissionsOf(client.user.id).has("voiceConnect")) return client.createMessage(message.channel.id, `${message.author.mention}, I don't have permission to join this voice channel!`);
if (!music && this.manager.voiceStates.has(message.channel.guild.id) && this.players.get(message.channel.guild.id).type === "music") return client.createMessage(message.channel.id, `${message.author.mention}, I can't play a sound effect while playing music!`); const player = this.players.get(message.channel.guild.id);
if (!music && this.manager.voiceStates.has(message.channel.guild.id) && (player && player.type === "music")) return client.createMessage(message.channel.id, `${message.author.mention}, I can't play a sound effect while playing music!`);
const node = this.manager.idealNodes[0]; const node = this.manager.idealNodes[0];
const { tracks } = await fetch(`http://${node.host}:${node.port}/loadtracks?identifier=${sound}`, { headers: { Authorization: node.password } }).then(res => res.json()); const { tracks } = await fetch(`http://${node.host}:${node.port}/loadtracks?identifier=${sound}`, { headers: { Authorization: node.password } }).then(res => res.json());
const oldQueue = this.queues.get(voiceChannel.guild.id); const oldQueue = this.queues.get(voiceChannel.guild.id);
@ -59,11 +60,16 @@ exports.play = async (sound, message, music = false) => {
if (music) { if (music) {
this.queues.set(voiceChannel.guild.id, oldQueue ? [...oldQueue, tracks[0].track] : [tracks[0].track]); this.queues.set(voiceChannel.guild.id, oldQueue ? [...oldQueue, tracks[0].track] : [tracks[0].track]);
} }
const connection = await this.manager.join({ let connection;
guild: voiceChannel.guild.id, if (player) {
channel: voiceChannel.id, connection = player;
node: node.id } else {
}); connection = await this.manager.join({
guild: voiceChannel.guild.id,
channel: voiceChannel.id,
node: node.id
});
}
if (oldQueue && music) { if (oldQueue && music) {
client.createMessage(message.channel.id, `${message.author.mention}, your tune has been added to the queue!`); client.createMessage(message.channel.id, `${message.author.mention}, your tune has been added to the queue!`);
@ -101,7 +107,7 @@ exports.nextSong = async (message, connection, track, info, music, voiceChannel,
}); });
await connection.play(track); await connection.play(track);
this.players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: message.author.id, voiceChannel: voiceChannel, originalChannel: message.channel }); this.players.set(voiceChannel.guild.id, { player: connection, type: music ? "music" : "sound", host: message.author.id, voiceChannel: voiceChannel, originalChannel: message.channel });
if (inQueue) { if (inQueue && connection.listeners("error").length === 0) {
connection.on("error", (error) => { connection.on("error", (error) => {
if (playingMessage.channel.messages.get(playingMessage.id)) playingMessage.delete(); if (playingMessage.channel.messages.get(playingMessage.id)) playingMessage.delete();
this.manager.leave(voiceChannel.guild.id); this.manager.leave(voiceChannel.guild.id);
@ -111,24 +117,26 @@ exports.nextSong = async (message, connection, track, info, music, voiceChannel,
logger.error(error); logger.error(error);
}); });
} }
connection.on("end", async (data) => { if (connection.listeners("end").length === 0) {
if (data.reason === "REPLACED") return; connection.on("end", async (data) => {
const queue = this.queues.get(voiceChannel.guild.id); if (data.reason === "REPLACED") return;
const newQueue = queue ? queue.slice(1) : []; const queue = this.queues.get(voiceChannel.guild.id);
this.queues.set(voiceChannel.guild.id, newQueue); const newQueue = queue ? queue.slice(1) : [];
if (newQueue.length === 0) { this.queues.set(voiceChannel.guild.id, newQueue);
this.manager.leave(voiceChannel.guild.id); if (newQueue.length === 0) {
connection.destroy(); this.manager.leave(voiceChannel.guild.id);
this.players.delete(voiceChannel.guild.id); connection.destroy();
this.queues.delete(voiceChannel.guild.id); this.players.delete(voiceChannel.guild.id);
if (music) await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended."); this.queues.delete(voiceChannel.guild.id);
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete(); if (music) await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended.");
} else { if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
const track = await fetch(`http://${connection.node.host}:${connection.node.port}/decodetrack?track=${encodeURIComponent(newQueue[0])}`, { headers: { Authorization: connection.node.password } }).then(res => res.json()); } else {
this.nextSong(message, connection, newQueue[0], track, music, voiceChannel, true); const track = await fetch(`http://${connection.node.host}:${connection.node.port}/decodetrack?track=${encodeURIComponent(newQueue[0])}`, { headers: { Authorization: connection.node.password } }).then(res => res.json());
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete(); this.nextSong(message, connection, newQueue[0], track, music, voiceChannel, true);
} if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
}); }
});
}
}; };
exports.stop = async (message) => { exports.stop = async (message) => {