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
PMTWO=false
# Set this to true if you want to use the image API
API=false
# Enable/disable Twitter bot (true/false)
TWITTER=false

View file

@ -1,11 +1,14 @@
require("dotenv").config();
const magick = require("../utils/image.js");
const Job = require("./job.js");
const { version } = require("../package.json");
const express = require("express");
const execPromise = require("util").promisify(require("child_process").exec);
const app = express();
const port = 3000;
const jobs = new Map();
app.get("/", (req, res) => {
res.send(`esmBot v${version}`);
});
@ -22,19 +25,71 @@ app.post("/run", express.json(), async (req, res, next) => {
return res.sendStatus(400);
}
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", "");
object.delay = (100 / delay.split("/")[0]) * delay.split("/")[1];
}
const data = await magick.run(object, true);
res.contentType(type ? type : "png");
res.send(data);
const job = new Job(object);
jobs.set(id, job);
res.send({
id: id,
status: "queued"
});
job.run();
} catch (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, () => {
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 logger = require("../utils/logger.js");
const misc = require("../utils/misc.js");
const client = require("../utils/client.js");
// run when the bot is added to a 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({
id: guild.id,
tags: misc.tagDefaults,
@ -15,4 +14,5 @@ module.exports = async (guild) => {
tagsDisabled: false
});
await guildDB.save();
return guildDB;
};

View file

@ -3,6 +3,7 @@ const client = require("../utils/client.js");
const database = require("../utils/database.js");
const logger = require("../utils/logger.js");
const collections = require("../utils/collections.js");
const guildCreate = require("./guildCreate.js");
const commands = [...collections.aliases.keys(), ...collections.commands.keys()];
// run when someone sends a message
@ -24,7 +25,10 @@ module.exports = async (message) => {
if (!valid) return;
// 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
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
});
await newGuild.save();
} else if (guildDB) {
} else {
if (!guildDB.warns) {
logger.log(`Creating warn object for guild ${id}...`);
guildDB.set("warns", {});

View file

@ -23,10 +23,10 @@ class CaptionWorker : public Napi::AsyncWorker {
Image caption_image(Geometry(query), Color("white"));
caption_image.fillColor("black");
caption_image.alpha(true);
caption_image.font("./assets/caption.otf");
caption_image.font("Futura");
caption_image.fontPointsize(width / 10);
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);
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 fileType = require("file-type");
const execPromise = promisify(require("child_process").exec);
const servers = require("../servers.json").image;
const formats = ["image/jpeg", "image/png", "image/webp", "image/gif"];
exports.run = async (object, fromAPI = false) => {
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",
body: JSON.stringify(object),
headers: {
"Content-Type": "application/json"
}
});
const buffer = await req.buffer();
console.log(buffer.toString());
if (buffer.toString() === "nogif") return {
const json = await req.json();
if (json.status === "nogif") return {
buffer: "nogif",
type: null
};
return {
buffer: buffer,
type: req.headers.get("content-type").split("/")[1]
};
let data;
while (!data) {
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 {
let type;
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 {
buffer: "nogif",
type: null

View file

@ -1,4 +1,5 @@
const fetch = require("node-fetch");
const execPromise = require("util").promisify(require("child_process").exec);
// gets the proper image paths
const getImage = async (image, image2, gifv = false) => {
@ -8,10 +9,15 @@ const getImage = async (image, image2, gifv = false) => {
path: image
};
if (gifv) {
if (image2.includes("tenor.com") && process.env.TENOR !== "") {
const data = await fetch(`https://api.tenor.com/v1/gifs?ids=${image2.split("-").pop()}&key=${process.env.TENOR}`);
const json = await data.json();
payload.path = json.results[0].media[0].gif.url;
if (image2.includes("tenor.com")) {
if (process.env.TENOR !== "") {
const data = await fetch(`https://api.tenor.com/v1/gifs?ids=${image2.split("-").pop()}&key=${process.env.TENOR}`);
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")) {
payload.path = `https://media0.giphy.com/media/${image2.split("-").pop()}/giphy.gif`;
} else if (image2.includes("imgur.com")) {

View file

@ -6,7 +6,7 @@ const moment = require("moment");
require("moment-duration-format");
const { Manager } = require("@lavacord/eris");
const nodes = require("../lavanodes.json");
const nodes = require("../servers.json").lava;
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!`);
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 (!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 { 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);
@ -59,11 +60,16 @@ exports.play = async (sound, message, music = false) => {
if (music) {
this.queues.set(voiceChannel.guild.id, oldQueue ? [...oldQueue, tracks[0].track] : [tracks[0].track]);
}
const connection = await this.manager.join({
guild: voiceChannel.guild.id,
channel: voiceChannel.id,
node: node.id
});
let connection;
if (player) {
connection = player;
} else {
connection = await this.manager.join({
guild: voiceChannel.guild.id,
channel: voiceChannel.id,
node: node.id
});
}
if (oldQueue && music) {
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);
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) => {
if (playingMessage.channel.messages.get(playingMessage.id)) playingMessage.delete();
this.manager.leave(voiceChannel.guild.id);
@ -111,24 +117,26 @@ exports.nextSong = async (message, connection, track, info, music, voiceChannel,
logger.error(error);
});
}
connection.on("end", async (data) => {
if (data.reason === "REPLACED") return;
const queue = this.queues.get(voiceChannel.guild.id);
const newQueue = queue ? queue.slice(1) : [];
this.queues.set(voiceChannel.guild.id, newQueue);
if (newQueue.length === 0) {
this.manager.leave(voiceChannel.guild.id);
connection.destroy();
this.players.delete(voiceChannel.guild.id);
this.queues.delete(voiceChannel.guild.id);
if (music) await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended.");
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
} else {
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());
this.nextSong(message, connection, newQueue[0], track, music, voiceChannel, true);
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
}
});
if (connection.listeners("end").length === 0) {
connection.on("end", async (data) => {
if (data.reason === "REPLACED") return;
const queue = this.queues.get(voiceChannel.guild.id);
const newQueue = queue ? queue.slice(1) : [];
this.queues.set(voiceChannel.guild.id, newQueue);
if (newQueue.length === 0) {
this.manager.leave(voiceChannel.guild.id);
connection.destroy();
this.players.delete(voiceChannel.guild.id);
this.queues.delete(voiceChannel.guild.id);
if (music) await client.createMessage(message.channel.id, "🔊 The current voice channel session has ended.");
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
} else {
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());
this.nextSong(message, connection, newQueue[0], track, music, voiceChannel, true);
if (playingMessage.channel.messages.get(playingMessage.id)) await playingMessage.delete();
}
});
}
};
exports.stop = async (message) => {