Rework image API again, replaced many calls to replace with replaceAll
This commit is contained in:
parent
b2b8fd643a
commit
62346cbae4
15 changed files with 271 additions and 185 deletions
162
api/index.js
162
api/index.js
|
@ -4,8 +4,7 @@ require("dotenv").config();
|
|||
const os = require("os");
|
||||
const { run } = require("../utils/image-runner.js");
|
||||
const net = require("net");
|
||||
const dgram = require("dgram"); // for UDP servers
|
||||
const socket = dgram.createSocket("udp4"); // Our universal UDP socket, this might cause issues and we may have to use a seperate socket for each connection
|
||||
const http = require("http");
|
||||
|
||||
const start = process.hrtime();
|
||||
const log = (msg, jobNum) => {
|
||||
|
@ -22,74 +21,123 @@ const { v4: uuidv4 } = require("uuid");
|
|||
const MAX_JOBS = process.env.JOBS !== "" && process.env.JOBS !== undefined ? parseInt(process.env.JOBS) : os.cpus().length * 4; // Completely arbitrary, should usually be some multiple of your amount of cores
|
||||
let jobAmount = 0;
|
||||
|
||||
const acceptJob = async (uuid) => {
|
||||
const acceptJob = async (uuid, sock) => {
|
||||
jobAmount++;
|
||||
queue.shift();
|
||||
try {
|
||||
await runJob({
|
||||
uuid: uuid,
|
||||
msg: jobs[uuid].msg,
|
||||
addr: jobs[uuid].addr,
|
||||
port: jobs[uuid].port,
|
||||
num: jobs[uuid].num
|
||||
});
|
||||
}, sock);
|
||||
jobAmount--;
|
||||
if (queue.length > 0) {
|
||||
acceptJob(queue[0]);
|
||||
}
|
||||
delete jobs[uuid];
|
||||
log(`Job ${uuid} has finished`);
|
||||
} catch (err) {
|
||||
console.error(`Error on job ${uuid}:`, err);
|
||||
socket.send(Buffer.concat([Buffer.from([0x2]), Buffer.from(uuid), Buffer.from(err.toString())]), jobs[uuid].port, jobs[uuid].addr);
|
||||
jobAmount--;
|
||||
if (queue.length > 0) {
|
||||
acceptJob(queue[0]);
|
||||
}
|
||||
delete jobs[uuid];
|
||||
sock.write(Buffer.concat([Buffer.from([0x2]), Buffer.from(uuid), Buffer.from(err.toString())]));
|
||||
}
|
||||
};
|
||||
|
||||
const server = dgram.createSocket("udp4"); //Create a UDP server for listening to requests, we dont need tcp
|
||||
server.on("message", (msg, rinfo) => {
|
||||
const opcode = msg.readUint8(0);
|
||||
const req = msg.toString().slice(1,msg.length);
|
||||
// 0x0 == Cancel job
|
||||
// 0x1 == Queue job
|
||||
// 0x2 == Get CPU usage
|
||||
if (opcode == 0x0) {
|
||||
delete queue[queue.indexOf(req) - 1];
|
||||
delete jobs[req];
|
||||
} else if (opcode == 0x1) {
|
||||
const job = { addr: rinfo.address, port: rinfo.port, msg: req, num: jobAmount };
|
||||
const uuid = uuidv4();
|
||||
jobs[uuid] = job;
|
||||
queue.push(uuid);
|
||||
|
||||
if (jobAmount < MAX_JOBS) {
|
||||
log(`Got request for job ${job.msg} with id ${uuid}`, job.num);
|
||||
acceptJob(uuid);
|
||||
} else {
|
||||
log(`Got request for job ${job.msg} with id ${uuid}, queued in position ${queue.indexOf(uuid)}`, job.num);
|
||||
const httpServer = http.createServer((req, res) => {
|
||||
if (req.method !== "GET") {
|
||||
res.statusCode = 405;
|
||||
return res.end("405 Method Not Allowed");
|
||||
}
|
||||
const reqUrl = new URL(req.url, `http://${req.headers.host}`);
|
||||
if (reqUrl.pathname === "/status") {
|
||||
log(`Sending server status to ${req.socket.remoteAddress}:${req.socket.remotePort} via HTTP`);
|
||||
return res.end(Buffer.from((MAX_JOBS - jobAmount).toString()));
|
||||
} else if (reqUrl.pathname === "/image") {
|
||||
if (!reqUrl.searchParams.has("id")) {
|
||||
res.statusCode = 400;
|
||||
return res.end("400 Bad Request");
|
||||
}
|
||||
|
||||
const newBuffer = Buffer.concat([Buffer.from([0x0]), Buffer.from(uuid)]);
|
||||
socket.send(newBuffer, rinfo.port, rinfo.address);
|
||||
} else if (opcode == 0x2) {
|
||||
socket.send(Buffer.concat([Buffer.from([0x3]), Buffer.from((MAX_JOBS - jobAmount).toString())]), rinfo.port, rinfo.address);
|
||||
const id = reqUrl.searchParams.get("id");
|
||||
if (!jobs[id]) {
|
||||
res.statusCode = 410;
|
||||
return res.end("410 Gone");
|
||||
}
|
||||
log(`Sending image data for job ${id} to ${req.socket.remoteAddress}:${req.socket.remotePort} via HTTP`);
|
||||
res.setHeader("ext", jobs[id].ext);
|
||||
return res.end(jobs[id].data, (err) => {
|
||||
if (err) console.error(err);
|
||||
delete jobs[id];
|
||||
});
|
||||
} else {
|
||||
log("Could not parse message");
|
||||
res.statusCode = 404;
|
||||
return res.end("404 Not Found");
|
||||
}
|
||||
});
|
||||
|
||||
server.on("listening", () => {
|
||||
const address = server.address();
|
||||
log(`server listening ${address.address}:${address.port}`);
|
||||
httpServer.on("error", (e) => {
|
||||
console.error("An HTTP error occurred: ", e);
|
||||
});
|
||||
|
||||
server.bind(8080); // ATTENTION: Always going to be bound to 0.0.0.0 !!!
|
||||
httpServer.listen(8081, () => {
|
||||
log("HTTP listening on port 8081");
|
||||
});
|
||||
|
||||
const runJob = (job) => {
|
||||
const server = net.createServer((sock) => { // Create a TCP socket/server to listen to requests
|
||||
log(`TCP client ${sock.remoteAddress}:${sock.remotePort} has connected`);
|
||||
|
||||
sock.on("error", (e) => {
|
||||
console.error(e);
|
||||
});
|
||||
|
||||
sock.on("data", (msg) => {
|
||||
const opcode = msg.readUint8(0);
|
||||
const req = msg.toString().slice(1,msg.length);
|
||||
console.log(req);
|
||||
// 0x00 == Cancel job
|
||||
// 0x01 == Queue job
|
||||
if (opcode == 0x00) {
|
||||
delete queue[queue.indexOf(req) - 1];
|
||||
delete jobs[req];
|
||||
} else if (opcode == 0x01) {
|
||||
const length = parseInt(req.slice(0, 1));
|
||||
const num = req.slice(1, length + 1);
|
||||
const obj = req.slice(length + 1);
|
||||
const job = { addr: sock.remoteAddress, port: sock.remotePort, msg: obj, num: jobAmount };
|
||||
const uuid = uuidv4();
|
||||
jobs[uuid] = job;
|
||||
queue.push(uuid);
|
||||
|
||||
const newBuffer = Buffer.concat([Buffer.from([0x00]), Buffer.from(uuid), Buffer.from(num)]);
|
||||
sock.write(newBuffer);
|
||||
|
||||
if (jobAmount < MAX_JOBS) {
|
||||
log(`Got TCP request for job ${job.msg} with id ${uuid}`, job.num);
|
||||
acceptJob(uuid, sock);
|
||||
} else {
|
||||
log(`Got TCP request for job ${job.msg} with id ${uuid}, queued in position ${queue.indexOf(uuid)}`, job.num);
|
||||
}
|
||||
} else {
|
||||
log("Could not parse TCP message");
|
||||
}
|
||||
});
|
||||
|
||||
sock.on("end", () => {
|
||||
log(`TCP client ${sock.remoteAddress}:${sock.remotePort} has disconnected`);
|
||||
});
|
||||
});
|
||||
|
||||
server.on("error", (e) => {
|
||||
console.error("A TCP error occurred: ", e);
|
||||
});
|
||||
|
||||
server.listen(8080, () => {
|
||||
log("TCP listening on port 8080");
|
||||
});
|
||||
|
||||
const runJob = (job, sock) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
log(`Job ${job.uuid} starting...`, job.num);
|
||||
|
||||
|
@ -100,29 +148,17 @@ const runJob = (job) => {
|
|||
}
|
||||
|
||||
log(`Job ${job.uuid} started`, job.num);
|
||||
const {buffer, fileExtension} = await run(object);
|
||||
|
||||
log(`Sending result of job ${job.uuid} back to the bot`, job.num);
|
||||
const server = net.createServer(function(tcpSocket) {
|
||||
tcpSocket.write(Buffer.concat([Buffer.from(fileExtension), Buffer.from("\n"), buffer]), (err) => {
|
||||
if (err) console.error(err);
|
||||
tcpSocket.end(() => {
|
||||
server.close();
|
||||
resolve(null);
|
||||
});
|
||||
try {
|
||||
const { buffer, fileExtension } = await run(object);
|
||||
log(`Sending result of job ${job.uuid} back to the bot`, job.num);
|
||||
jobs[job.uuid].data = buffer;
|
||||
jobs[job.uuid].ext = fileExtension;
|
||||
sock.write(Buffer.concat([Buffer.from([0x1]), Buffer.from(job.uuid)]), (e) => {
|
||||
if (e) return reject(e);
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
server.listen(job.port, job.addr);
|
||||
// handle address in use errors
|
||||
server.on("error", (e) => {
|
||||
if (e.code === "EADDRINUSE") {
|
||||
log("Address in use, retrying...", job.num);
|
||||
setTimeout(() => {
|
||||
server.close();
|
||||
server.listen(job.port, job.addr);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
socket.send(Buffer.concat([Buffer.from([0x1]), Buffer.from(job.uuid), Buffer.from(job.port.toString())]), job.port, job.addr);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
12
app.js
12
app.js
|
@ -14,6 +14,7 @@ const client = require("./utils/client.js");
|
|||
// initialize command loader
|
||||
const handler = require("./utils/handler.js");
|
||||
const sound = require("./utils/soundplayer.js");
|
||||
const image = require("./utils/image.js");
|
||||
|
||||
// registers stuff and connects the bot
|
||||
async function init() {
|
||||
|
@ -41,6 +42,17 @@ async function init() {
|
|||
client.on(eventName, event);
|
||||
}
|
||||
|
||||
// connect to image api if enabled
|
||||
if (process.env.API === "true") {
|
||||
for (const server of image.servers) {
|
||||
try {
|
||||
await image.connect(server);
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// login
|
||||
client.connect();
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ exports.run = async (message, args) => {
|
|||
const { buffer, type } = await magick.run({
|
||||
cmd: "caption",
|
||||
path: image.path,
|
||||
caption: newArgs.join(" ").replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%"),
|
||||
caption: newArgs.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"),
|
||||
type: image.type
|
||||
});
|
||||
if (processMessage.channel.messages.get(processMessage.id)) await processMessage.delete();
|
||||
|
|
|
@ -9,7 +9,7 @@ exports.run = async (message, args) => {
|
|||
const { buffer, type } = await magick.run({
|
||||
cmd: "captionTwo",
|
||||
path: image.path,
|
||||
caption: newArgs.length !== 0 ? newArgs.join(" ").replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
|
||||
caption: newArgs.length !== 0 ? newArgs.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "),
|
||||
type: image.type
|
||||
});
|
||||
if (processMessage.channel.messages.get(processMessage.id)) await processMessage.delete();
|
||||
|
|
|
@ -11,7 +11,7 @@ exports.run = async (message, args) => {
|
|||
if (args[0].toLowerCase() === "disable") {
|
||||
let channel;
|
||||
if (args[1] && args[1].match(/^<?[@#]?[&!]?\d+>?$/) && args[1] >= 21154535154122752) {
|
||||
const id = args[1].replace(/@/g, "").replace(/#/g, "").replace(/!/g, "").replace(/&/g, "").replace(/</g, "").replace(/>/g, "");
|
||||
const id = args[1].replaceAll("@", "").replaceAll("#", "").replaceAll("!", "").replaceAll("&", "").replaceAll("<", "").replaceAll(">", "");
|
||||
if (guildDB.disabled.includes(id)) return `${message.author.mention}, I'm already disabled in this channel!`;
|
||||
channel = message.channel.guild.channels.get(id);
|
||||
} else {
|
||||
|
@ -24,7 +24,7 @@ exports.run = async (message, args) => {
|
|||
} else if (args[0].toLowerCase() === "enable") {
|
||||
let channel;
|
||||
if (args[1] && args[1].match(/^<?[@#]?[&!]?\d+>?$/) && args[1] >= 21154535154122752) {
|
||||
const id = args[1].replace(/@/g, "").replace(/#/g, "").replace(/!/g, "").replace(/&/g, "").replace(/</g, "").replace(/>/g, "");
|
||||
const id = args[1].replaceAll("@", "").replaceAll("#", "").replaceAll("!", "").replaceAll("&", "").replaceAll("<", "").replaceAll(">", "");
|
||||
if (!guildDB.disabled.includes(id)) return `${message.author.mention}, I'm not disabled in that channel!`;
|
||||
channel = message.channel.guild.channels.get(id);
|
||||
} else {
|
||||
|
|
|
@ -8,7 +8,7 @@ exports.run = async (message, args) => {
|
|||
const image = await require("../utils/imagedetect.js")(message);
|
||||
if (image === undefined) return `${message.author.mention}, you need to provide an image to overlay a flag onto!`;
|
||||
if (!args[0].match(emojiRegex)) return `${message.author.mention}, you need to provide an emoji of a flag to overlay!`;
|
||||
const flag = emoji.unemojify(args[0]).replace(/:/g, "").replace("flag-", "");
|
||||
const flag = emoji.unemojify(args[0]).replaceAll(":", "").replace("flag-", "");
|
||||
let path = `./assets/images/region-flags/png/${flag.toUpperCase()}.png`;
|
||||
if (flag === "🏴☠️") path = "./assets/images/pirateflag.png";
|
||||
if (flag === "rainbow-flag") path = "./assets/images/rainbowflag.png";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
exports.run = async (message, args) => {
|
||||
if (args.length === 0) return `${message.author.mention}, you need to provide some text to convert to fullwidth!`;
|
||||
return args.join("").replace(/[A-Za-z0-9]/g, (s) => { return String.fromCharCode(s.charCodeAt(0) + 0xFEE0); });
|
||||
return args.join("").replaceAll(/[A-Za-z0-9]/g, (s) => { return String.fromCharCode(s.charCodeAt(0) + 0xFEE0); });
|
||||
};
|
||||
|
||||
exports.aliases = ["aesthetic", "aesthetics", "aes"];
|
||||
|
|
|
@ -5,7 +5,7 @@ exports.run = async (message, args) => {
|
|||
message.channel.sendTyping();
|
||||
const { buffer } = await magick.run({
|
||||
cmd: "homebrew",
|
||||
caption: args.join(" ").toLowerCase().replace(/\n/g, " ")
|
||||
caption: args.join(" ").toLowerCase().replaceAll("\n", " ")
|
||||
});
|
||||
return {
|
||||
file: buffer,
|
||||
|
|
|
@ -10,8 +10,8 @@ exports.run = async (message, args) => {
|
|||
const { buffer, type } = await magick.run({
|
||||
cmd: "meme",
|
||||
path: image.path,
|
||||
top: topText.toUpperCase().replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%"),
|
||||
bottom: bottomText ? bottomText.toUpperCase().replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%") : "",
|
||||
top: topText.toUpperCase().replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"),
|
||||
bottom: bottomText ? bottomText.toUpperCase().replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : "",
|
||||
type: image.type
|
||||
});
|
||||
return {
|
||||
|
|
|
@ -10,8 +10,8 @@ exports.run = async (message, args) => {
|
|||
const { buffer, type } = await magick.run({
|
||||
cmd: "motivate",
|
||||
path: image.path,
|
||||
top: topText.replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%"),
|
||||
bottom: bottomText ? bottomText.replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%") : "",
|
||||
top: topText.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"),
|
||||
bottom: bottomText ? bottomText.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : "",
|
||||
type: image.type
|
||||
});
|
||||
if (processMessage.channel.messages.get(processMessage.id)) await processMessage.delete();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
exports.run = async (message, args) => {
|
||||
if (!args[0]) return `${message.author.mention}, you need to provide a snowflake ID!`;
|
||||
if (!args[0].match(/^<?[@#]?[&!]?\d+>?$/) && args[0] < 21154535154122752) return `${message.author.mention}, that's not a valid snowflake!`;
|
||||
return new Date((args[0].replace(/@/g, "").replace(/#/g, "").replace(/!/g, "").replace(/&/g, "").replace(/</g, "").replace(/>/g, "") / 4194304) + 1420070400000).toUTCString();
|
||||
return new Date((args[0].replaceAll("@", "").replaceAll("#", "").replaceAll("!", "").replaceAll("&", "").replaceAll("<", "").replaceAll(">", "") / 4194304) + 1420070400000).toUTCString();
|
||||
};
|
||||
|
||||
exports.aliases = ["timestamp", "snowstamp", "snow"];
|
||||
|
|
|
@ -4,7 +4,7 @@ const wrap = require("../utils/wrap.js");
|
|||
exports.run = async (message, args) => {
|
||||
if (args.length === 0) return `${message.author.mention}, you need to provide some text to make a Sonic meme!`;
|
||||
message.channel.sendTyping();
|
||||
const cleanedMessage = args.join(" ").replace(/&/g, "\\&").replace(/>/g, "\\>").replace(/</g, "\\<").replace(/"/g, "\\"").replace(/'/g, "\\'").replace(/%/g, "\\%");
|
||||
const cleanedMessage = args.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
|
||||
const { buffer } = await magick.run({
|
||||
cmd: "sonic",
|
||||
text: wrap(cleanedMessage, {width: 15, indent: ""})
|
||||
|
|
|
@ -10,11 +10,11 @@ exports.run = async (message, args) => {
|
|||
const result = await request.json();
|
||||
for (const [i, value] of result.items.entries()) {
|
||||
if (value.id.kind === "youtube#channel") {
|
||||
messages.push(`Page ${i + 1} of ${result.items.length}\n<:youtube:637020823005167626> **${decodeEntities(value.snippet.title).replace(/\*/g, "\\*")}**\nhttps://youtube.com/channel/${value.id.channelId}`);
|
||||
messages.push(`Page ${i + 1} of ${result.items.length}\n<:youtube:637020823005167626> **${decodeEntities(value.snippet.title).replaceAll("*", "\\*")}**\nhttps://youtube.com/channel/${value.id.channelId}`);
|
||||
} else if (value.id.kind === "youtube#playlist") {
|
||||
messages.push(`Page ${i + 1} of ${result.items.length}\n<:youtube:637020823005167626> **${decodeEntities(value.snippet.title).replace(/\*/g, "\\*")}**\nCreated by **${decodeEntities(value.snippet.channelTitle).replace(/\*/g, "\\*")}**\nhttps://youtube.com/playlist?list=${value.id.playlistId}`);
|
||||
messages.push(`Page ${i + 1} of ${result.items.length}\n<:youtube:637020823005167626> **${decodeEntities(value.snippet.title).replaceAll("*", "\\*")}**\nCreated by **${decodeEntities(value.snippet.channelTitle).replaceAll("*", "\\*")}**\nhttps://youtube.com/playlist?list=${value.id.playlistId}`);
|
||||
} else {
|
||||
messages.push(`Page ${i + 1} of ${result.items.length}\n<:youtube:637020823005167626> **${decodeEntities(value.snippet.title).replace(/\*/g, "\\*")}**\nUploaded by **${decodeEntities(value.snippet.channelTitle).replace(/\*/g, "\\*")}** on **${value.snippet.publishedAt.split("T")[0]}**\nhttps://youtube.com/watch?v=${value.id.videoId}`);
|
||||
messages.push(`Page ${i + 1} of ${result.items.length}\n<:youtube:637020823005167626> **${decodeEntities(value.snippet.title).replaceAll("*", "\\*")}**\nUploaded by **${decodeEntities(value.snippet.channelTitle).replaceAll("*", "\\*")}** on **${value.snippet.publishedAt.split("T")[0]}**\nhttps://youtube.com/watch?v=${value.id.videoId}`);
|
||||
}
|
||||
}
|
||||
return paginator(message, messages);
|
||||
|
|
216
utils/image.js
216
utils/image.js
|
@ -3,13 +3,20 @@ const { Worker } = require("worker_threads");
|
|||
const fetch = require("node-fetch");
|
||||
const AbortController = require("abort-controller");
|
||||
const net = require("net");
|
||||
const dgram = require("dgram");
|
||||
const fileType = require("file-type");
|
||||
const servers = require("../servers.json").image;
|
||||
exports.servers = require("../servers.json").image;
|
||||
const path = require("path");
|
||||
const { EventEmitter } = require("events");
|
||||
const logger = require("./logger.js");
|
||||
|
||||
const formats = ["image/jpeg", "image/png", "image/webp", "image/gif"];
|
||||
|
||||
const jobs = {};
|
||||
|
||||
const connections = [];
|
||||
|
||||
const statuses = {};
|
||||
|
||||
const chooseServer = async (ideal) => {
|
||||
if (ideal.length === 0) throw "No available servers";
|
||||
const sorted = ideal.sort((a, b) => {
|
||||
|
@ -18,47 +25,113 @@ const chooseServer = async (ideal) => {
|
|||
return sorted[0];
|
||||
};
|
||||
|
||||
const getIdeal = () => {
|
||||
exports.connect = (server) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = dgram.createSocket("udp4");
|
||||
let serversLeft = servers.length;
|
||||
const idealServers = [];
|
||||
const connection = net.createConnection(8080, server);
|
||||
const timeout = setTimeout(() => {
|
||||
socket.close(async () => {
|
||||
try {
|
||||
const server = await chooseServer(idealServers);
|
||||
resolve(server);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
const connectionIndex = connections.indexOf(connection);
|
||||
if (connectionIndex < 0) delete connections[connectionIndex];
|
||||
reject(`Failed to connect to ${server}`);
|
||||
}, 5000);
|
||||
socket.on("message", async (msg, rinfo) => {
|
||||
connection.once("connect", () => {
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
connection.on("data", async (msg) => {
|
||||
const opcode = msg.readUint8(0);
|
||||
const res = parseInt(msg.slice(1, msg.length).toString());
|
||||
if (opcode === 0x3) {
|
||||
const req = msg.slice(37, msg.length);
|
||||
const uuid = msg.slice(1, 37).toString();
|
||||
if (opcode === 0x00) { // Job queued
|
||||
if (jobs[req]) {
|
||||
jobs[req].emit("uuid", uuid);
|
||||
}
|
||||
} else if (opcode === 0x01) { // Job completed successfully
|
||||
// the image API sends all job responses over the same socket; make sure this is ours
|
||||
if (jobs[uuid]) {
|
||||
const imageReq = await fetch(`http://${connection.remoteAddress}:8081/image?id=${uuid}`);
|
||||
const image = await imageReq.buffer();
|
||||
// The response data is given as the file extension/ImageMagick type of the image (e.g. "png"), followed
|
||||
// by a newline, followed by the image data.
|
||||
|
||||
jobs[uuid].emit("image", image, imageReq.headers.get("ext"));
|
||||
}
|
||||
} else if (opcode === 0x02) { // Job errored
|
||||
if (jobs[uuid]) {
|
||||
jobs[uuid].emit("error", new Error(req));
|
||||
}
|
||||
} else if (opcode === 0x03) {
|
||||
// we use the uuid part here because queue info requests don't respond with one
|
||||
statuses[`${connection.remoteAddress}:${connection.remotePort}`] = parseInt(uuid);
|
||||
}
|
||||
});
|
||||
connection.on("error", (e) => {
|
||||
console.error(e);
|
||||
});
|
||||
connections.push(connection);
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const getIdeal = () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let serversLeft = connections.length;
|
||||
const idealServers = [];
|
||||
const timeout = setTimeout(async () => {
|
||||
try {
|
||||
const server = await chooseServer(idealServers);
|
||||
resolve(connections.find(val => val.remoteAddress === server.addr));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}, 5000);
|
||||
for (const connection of connections) {
|
||||
if (!connection.remoteAddress) continue;
|
||||
try {
|
||||
const statusRequest = await fetch(`http://${connection.remoteAddress}:8081/status`);
|
||||
const status = await statusRequest.text();
|
||||
serversLeft--;
|
||||
idealServers.push({
|
||||
addr: rinfo.address,
|
||||
load: res
|
||||
addr: connection.remoteAddress,
|
||||
load: parseInt(status)
|
||||
});
|
||||
if (!serversLeft) {
|
||||
clearTimeout(timeout);
|
||||
socket.close(async () => {
|
||||
try {
|
||||
const server = await chooseServer(idealServers);
|
||||
resolve(server);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
const server = await chooseServer(idealServers);
|
||||
resolve(connections.find(val => val.remoteAddress === server.addr));
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
for (const server of servers) {
|
||||
socket.send(Buffer.from([0x2]), 8080, server, (err) => {
|
||||
if (err) reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const start = (object, num) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const currentServer = await getIdeal();
|
||||
const data = Buffer.concat([Buffer.from([0x01 /* queue job */]), Buffer.from(num.length.toString()), Buffer.from(num), Buffer.from(JSON.stringify(object))]);
|
||||
currentServer.write(data, (err) => {
|
||||
if (err) {
|
||||
if (err.code === "EPIPE") {
|
||||
logger.log(`Lost connection to ${currentServer.remoteAddress}, attempting to reconnect...`);
|
||||
currentServer.connect(8080, currentServer.remoteAddress, async () => {
|
||||
const res = start(object, num);
|
||||
resolve(res);
|
||||
});
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
const event = new EventEmitter();
|
||||
event.once("uuid", (uuid) => {
|
||||
delete jobs[num];
|
||||
jobs[uuid] = event;
|
||||
resolve({ uuid, event });
|
||||
});
|
||||
jobs[num] = event;
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -105,65 +178,30 @@ exports.getType = async (image) => {
|
|||
exports.run = object => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (process.env.API === "true") {
|
||||
// Connect to best image server
|
||||
const currentServer = await getIdeal();
|
||||
const socket = dgram.createSocket("udp4");
|
||||
const data = Buffer.concat([Buffer.from([0x1 /* queue job */]), Buffer.from(JSON.stringify(object))]);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
socket.close();
|
||||
reject("UDP timed out");
|
||||
}, 25000);
|
||||
|
||||
let jobID;
|
||||
socket.on("message", (msg) => {
|
||||
const opcode = msg.readUint8(0);
|
||||
const req = msg.slice(37, msg.length);
|
||||
const uuid = msg.slice(1, 36).toString();
|
||||
if (opcode === 0x0) { // Job queued
|
||||
clearTimeout(timeout);
|
||||
jobID = uuid;
|
||||
} else if (opcode === 0x1) { // Job completed successfully
|
||||
// the image API sends all job responses over the same socket; make sure this is ours
|
||||
if (jobID === uuid) {
|
||||
const client = net.createConnection(req.toString(), currentServer.addr);
|
||||
const array = [];
|
||||
client.on("data", (rawData) => {
|
||||
array.push(rawData);
|
||||
});
|
||||
client.once("end", () => {
|
||||
const data = Buffer.concat(array);
|
||||
// The response data is given as the file extension/ImageMagick type of the image (e.g. "png"), followed
|
||||
// by a newline, followed by the image data.
|
||||
const delimIndex = data.indexOf("\n");
|
||||
socket.close();
|
||||
if (delimIndex === -1) reject("Could not parse response");
|
||||
const payload = {
|
||||
// Take just the image data
|
||||
buffer: data.slice(delimIndex + 1),
|
||||
type: data.slice(0, delimIndex).toString()
|
||||
};
|
||||
resolve(payload);
|
||||
});
|
||||
client.on("error", (err) => {
|
||||
socket.close();
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
} else if (opcode === 0x2) { // Job errored
|
||||
if (jobID === uuid) {
|
||||
socket.close();
|
||||
reject(req);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.send(data, 8080, currentServer.addr, (err) => {
|
||||
if (err) {
|
||||
socket.close();
|
||||
try {
|
||||
// Connect to best image server
|
||||
const num = Math.floor(Math.random() * 100000).toString().slice(0, 5);
|
||||
const timeout = setTimeout(() => {
|
||||
if (jobs[num]) delete jobs[num];
|
||||
reject("Request timed out");
|
||||
}, 25000);
|
||||
const { uuid, event } = await start(object, num);
|
||||
clearTimeout(timeout);
|
||||
event.once("image", (image, type) => {
|
||||
delete jobs[uuid];
|
||||
const payload = {
|
||||
// Take just the image data
|
||||
buffer: image,
|
||||
type: type
|
||||
};
|
||||
resolve(payload);
|
||||
});
|
||||
event.once("error", (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
} else {
|
||||
// Called from command (not using image API)
|
||||
const worker = new Worker(path.join(__dirname, "image-runner.js"), {
|
||||
|
|
|
@ -18,18 +18,18 @@ exports.clean = async (text) => {
|
|||
text = util.inspect(text, { depth: 1 });
|
||||
|
||||
text = text
|
||||
.replace(/`/g, `\`${String.fromCharCode(8203)}`)
|
||||
.replace(/@/g, `@${String.fromCharCode(8203)}`)
|
||||
.replace(process.env.TOKEN, optionalReplace(process.env.TOKEN))
|
||||
.replace(process.env.MASHAPE, optionalReplace(process.env.MASHAPE))
|
||||
.replace(process.env.CAT, optionalReplace(process.env.CAT))
|
||||
.replace(process.env.GOOGLE, optionalReplace(process.env.GOOGLE))
|
||||
.replace(process.env.DBL, optionalReplace(process.env.DBL))
|
||||
.replace(process.env.MONGO, optionalReplace(process.env.MONGO))
|
||||
.replace(process.env.TWITTER_KEY, optionalReplace(process.env.TWITTER_KEY))
|
||||
.replace(process.env.CONSUMER_SECRET, optionalReplace(process.env.CONSUMER_SECRET))
|
||||
.replace(process.env.ACCESS_TOKEN, optionalReplace(process.env.ACCESS_TOKEN))
|
||||
.replace(process.env.ACCESS_SECRET, optionalReplace(process.env.ACCESS_SECRET));
|
||||
.replaceAll("`", `\`${String.fromCharCode(8203)}`)
|
||||
.replaceAll("@", `@${String.fromCharCode(8203)}`)
|
||||
.replaceAll(process.env.TOKEN, optionalReplace(process.env.TOKEN))
|
||||
.replaceAll(process.env.MASHAPE, optionalReplace(process.env.MASHAPE))
|
||||
.replaceAll(process.env.CAT, optionalReplace(process.env.CAT))
|
||||
.replaceAll(process.env.GOOGLE, optionalReplace(process.env.GOOGLE))
|
||||
.replaceAll(process.env.DBL, optionalReplace(process.env.DBL))
|
||||
.replaceAll(process.env.MONGO, optionalReplace(process.env.MONGO))
|
||||
.replaceAll(process.env.TWITTER_KEY, optionalReplace(process.env.TWITTER_KEY))
|
||||
.replaceAll(process.env.CONSUMER_SECRET, optionalReplace(process.env.CONSUMER_SECRET))
|
||||
.replaceAll(process.env.ACCESS_TOKEN, optionalReplace(process.env.ACCESS_TOKEN))
|
||||
.replaceAll(process.env.ACCESS_SECRET, optionalReplace(process.env.ACCESS_SECRET));
|
||||
|
||||
return text;
|
||||
};
|
||||
|
@ -38,19 +38,19 @@ exports.clean = async (text) => {
|
|||
exports.getTweet = async (tweets, reply = false, isDownload = false) => {
|
||||
const randomTweet = this.random(reply ? (isDownload ? tweets.download : tweets.replies) : tweets.tweets);
|
||||
if (randomTweet.match("{{message}}")) {
|
||||
return randomTweet.replace(/{{message}}/gm, await this.getRandomMessage());
|
||||
return randomTweet.replaceAll("{{message}}", await this.getRandomMessage());
|
||||
} else {
|
||||
return randomTweet
|
||||
.replace(/{{media}}/gm, () => {
|
||||
.replaceAll("{{media}}", () => {
|
||||
return this.random(tweets.media);
|
||||
})
|
||||
.replace(/{{games}}/gm, () => {
|
||||
.replaceAll("{{games}}", () => {
|
||||
return this.random(tweets.games);
|
||||
})
|
||||
.replace(/{{phrases}}/gm, () => {
|
||||
.replaceAll("{{phrases}}", () => {
|
||||
return this.random(tweets.phrases);
|
||||
})
|
||||
.replace(/{{characters}}/gm, () => {
|
||||
.replaceAll("{{characters}}", () => {
|
||||
return this.random(tweets.characters);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue