This repository has been archived on 2022-01-28. You can view files and clone it, but cannot push or open issues or pull requests.
beebot/index.js

373 lines
12 KiB
JavaScript
Raw Normal View History

2017-12-31 01:54:52 +01:00
const _ = require('lodash');
const express = require('express');
const Discord = require('discord.js');
2017-12-07 03:30:56 +01:00
2017-12-31 01:54:52 +01:00
const { CanvasEx, ImageEx } = require('./imageex');
2017-12-07 03:30:56 +01:00
2017-12-31 01:54:52 +01:00
const twemoji = require('./twemoji');
const app = express();
const config = require('./config.default.json');
2017-12-07 03:30:56 +01:00
2018-02-25 18:47:47 +01:00
const filters = require('./filters.js');
const logger = require('loglevel');
logger.setLevel(process.env.LOGLEVEL || 'info');
2017-12-11 16:51:06 +01:00
try {
_.extend(config, require(`./${process.env.CONFIG || 'config'}`)); // eslint-disable-line global-require
2017-12-31 01:54:52 +01:00
} catch (err) {
logger.error('No config.json found!');
2017-12-11 16:51:06 +01:00
}
2017-12-07 03:30:56 +01:00
2017-12-15 14:18:21 +01:00
function all(x, c) {
2017-12-31 01:54:52 +01:00
_.isArray(x) ? _.each(x, c) : c(x);
2017-12-15 14:18:21 +01:00
}
2017-12-31 01:54:52 +01:00
const { templates } = config;
2017-12-10 15:03:09 +01:00
2017-12-31 01:54:52 +01:00
_.each(templates, (template, templateName) => {
const data = templates[templateName];
all(data, templatePart => {
2018-01-03 14:04:57 +01:00
templatePart.image = new ImageEx(templatePart.src);
2017-12-31 01:54:52 +01:00
});
});
2017-12-07 03:30:56 +01:00
2017-12-11 16:43:28 +01:00
// drawing: we keep the image fixed in its default position and draw the template on top/below it
// calculates the x or y position of the template to be drawn
// size = width or height of the template/image
// anchor = the corresponding anchor config
function calculatePosition(scale, anchor, imageSize) {
2018-01-07 03:15:32 +01:00
if (anchor.absolute) {
return anchor.offset;
}
2017-12-31 01:54:52 +01:00
return imageSize * anchor.position / 100 - anchor.offset * scale;
2017-12-11 16:43:28 +01:00
}
2017-12-07 03:30:56 +01:00
2019-01-25 18:16:08 +01:00
// global variable, can be used to get the previous template calculations
let previousCalculation; // eslint-disable-line no-unused-vars
2018-01-07 03:15:32 +01:00
function getNumericAnchor(anchor, imgWidth, imgHeight) { // eslint-disable-line no-unused-vars
return _.mapValues(anchor, dimension =>
_.mapValues(dimension, value => (Number.isFinite(value) ? Number(value) : eval(value)))); // eslint-disable-line no-eval
}
2017-12-19 15:43:00 +01:00
function render(template, img, size, flipH) {
2017-12-31 01:54:52 +01:00
let imgWidth = img.width;
let imgHeight = img.height;
if (size && size.height) {
imgHeight = size.height;
if (!size.width) imgWidth = imgWidth * size.height / img.height;
}
if (size && size.width) {
imgWidth = size.width;
if (!size.height) imgHeight = imgHeight * size.width / img.width;
}
logger.debug('Drawing template: ', template);
2018-01-07 03:15:32 +01:00
const anchor = getNumericAnchor(template.anchor, imgWidth, imgHeight);
logger.debug('Numeric anchor: ', anchor);
2018-01-07 03:15:32 +01:00
const xScale = imgWidth / anchor.x.size;
const yScale = imgHeight / anchor.y.size;
2017-12-31 01:54:52 +01:00
const templateScale = Math.max(0, Math.min(10, Math.max(xScale || 0, yScale || 0)));
2018-01-07 03:15:32 +01:00
let templateOffsetX;
let templateOffsetY;
templateOffsetX = calculatePosition(templateScale, anchor.x, imgWidth);
templateOffsetY = calculatePosition(templateScale, anchor.y, imgHeight);
2017-12-31 01:54:52 +01:00
logger.debug('xScale', xScale);
logger.debug('yScale', yScale);
logger.debug('templateOffsetX', templateOffsetX);
logger.debug('templateOffsetY', templateOffsetY);
2017-12-31 01:54:52 +01:00
let imageOffsetX = 0;
let imageOffsetY = 0;
let resultingWidth = imgWidth; // start with the image boundaries as defined by the image
let resultingHeight = imgHeight;
if (templateOffsetX < 0) {
resultingWidth -= templateOffsetX;
imageOffsetX = -templateOffsetX;
templateOffsetX = 0;
}
if (templateOffsetY < 0) {
resultingHeight -= templateOffsetY;
imageOffsetY = -templateOffsetY;
templateOffsetY = 0;
}
if (templateOffsetX + template.image.width * templateScale > resultingWidth) {
resultingWidth = templateOffsetX + template.image.width * templateScale;
}
if (templateOffsetY + template.image.height * templateScale > resultingHeight) {
resultingHeight = templateOffsetY + template.image.height * templateScale;
}
2019-01-25 18:16:08 +01:00
previousCalculation = {
templateOffsetX,
templateOffsetY,
resultingWidth,
resultingHeight,
xScale,
yScale,
templateScale
};
2017-12-31 01:54:52 +01:00
const toDraw = [{
z: 1,
image: img,
x: flipH ? resultingWidth - imageOffsetX - imgWidth : imageOffsetX,
y: imageOffsetY,
h: imgHeight,
w: imgWidth,
name: 'image'
}, {
z: template.z || 0,
image: template.image,
x: templateOffsetX,
y: templateOffsetY,
h: template.image.height * templateScale,
w: template.image.width * templateScale,
name: `template ${template.src}`,
2018-02-09 15:32:26 +01:00
flipH,
2018-02-25 18:47:47 +01:00
attributes: template.attributes,
filter: filters[template.filter]
2019-01-25 18:16:08 +01:00
}].sort((u, v) => u.z - v.z);
logger.debug('To draw:', toDraw);
2017-12-31 01:54:52 +01:00
2018-02-25 18:47:47 +01:00
let canvas = new CanvasEx(resultingWidth, resultingHeight);
2017-12-31 01:54:52 +01:00
for (let i = 0; i < toDraw.length; ++i) {
const subject = toDraw[i];
logger.debug(`Drawing ${subject.name}${subject.flipH ? ' (flipped)' : ''}`);
2017-12-31 01:54:52 +01:00
try {
const transform = {};
if (subject.flipH) {
transform.translate = [resultingWidth, 0];
transform.scale = [-1, 1];
}
2018-02-25 18:47:47 +01:00
if (subject.filter) {
canvas = subject.filter(canvas, subject.image, subject.x, subject.y, {
width: subject.w, height: subject.h, transform, attributes: subject.attributes
});
} else {
canvas.drawImage(subject.image, subject.x, subject.y, {
width: subject.w, height: subject.h, transform, attributes: subject.attributes
});
}
2017-12-31 01:54:52 +01:00
} catch (err) {
logger.error(err);
2017-12-31 01:54:52 +01:00
throw new Error(JSON.stringify({ status: 400, error: 'Invalid template' }));
}
}
// return the image and cache it
return (canvas);
2017-12-07 03:30:56 +01:00
}
app.get('/debug/frame/', async (req, res) => {
try {
const img = new ImageEx(req.query.url);
await img.loaded;
logger.debug(img.frames);
return img.frames[req.query.num].canvas.pngStream().pipe(res);
} catch (err) {
logger.error(err);
return res.status(400).end(err.message);
}
});
app.get('/', async (req, res) => {
try {
var templateList = [];
for (var key in templates) {
if(templates.hasOwnProperty(key)) { //to be safe
templateList.push(key);
}
}
logger.debug(templateList);
res.setHeader('Content-Type', 'application/json')
return res.end(JSON.stringify(templateList));
} catch (err) {
logger.error(err);
return res.status(400).end(err.message);
}
});
2017-12-31 01:54:52 +01:00
app.get('/:templateName/', async (req, res) => {
if (!templates[req.params.templateName]) return res.status(404).end();
try {
if (!/^https?:/.test(req.query.url)) {
return res.status(400).end('Invalid url!');
2018-02-09 13:23:38 +01:00
}
2021-02-03 09:36:14 +01:00
let direction = req.query.reverse === 'true' ? '\\' : '/';
logger.debug('Got command ', direction, req.params.templateName, direction === '\\' ? 'flipped' : 'not flipped', req.query.url);
2021-02-03 09:36:14 +01:00
result = new ImageEx(req.query.url);
await result.loaded; // eslint-disable-line no-await-in-loop
const templateData = templates[req.params.templateName];
all(templateData, template => { // eslint-disable-line no-loop-func
result = render(template, result, null, direction === '\\');
});
return result.export(res);
2017-12-31 01:54:52 +01:00
} catch (err) {
logger.error(err);
2021-02-03 09:36:14 +01:00
return res.status(400).end({'error': err.message});
2017-12-31 01:54:52 +01:00
}
2017-12-07 03:30:56 +01:00
});
2021-05-10 14:16:10 +02:00
app.listen(config.http.port, config.http.host, () => {
logger.info(`Beebot app listening on ${(config.http.host === null ? 'port ' : config.http.host + ':')}${config.http.port}!`);
2017-12-31 01:54:52 +01:00
});
2017-12-07 03:30:56 +01:00
// Discord stuff
2017-12-31 01:54:52 +01:00
const client = new Discord.Client({
autoReconnect: true
2017-12-07 03:30:56 +01:00
});
// manage roles permission is required
2017-12-31 01:54:52 +01:00
const invitelink = `https://discordapp.com/oauth2/authorize?client_id=${
config.discord.client_id}&scope=bot&permissions=0`;
/* const authlink = `https://discordapp.com/oauth2/authorize?client_id=${
config.discord.client_id}&scope=email`; */
logger.info(`Bot invite link: ${invitelink}`);
2017-12-31 01:54:52 +01:00
client.login(config.discord.token).catch(error => {
if (error) {
logger.error("Couldn't login: ", error.toString());
2017-12-31 01:54:52 +01:00
}
2017-12-07 03:30:56 +01:00
});
2018-01-03 14:38:44 +01:00
const discordAvatarRegex = /(https:\/\/cdn.discordapp.com\/avatars\/\w+\/\w+\.(\w+)\?size=)(\w+)/;
2018-01-07 15:51:19 +01:00
async function findEmoji(message) {
// find a user mention
2018-01-07 15:51:19 +01:00
if (message.mentions.users.size > 0) {
const mentionedUser = message.mentions.users.first();
const mentionedMember = message.guild.members[mentionedUser.id];
let avatarUrl = mentionedUser.displayAvatarURL;
const avatarMatch = discordAvatarRegex.exec(avatarUrl);
if (avatarMatch) {
2018-01-07 15:51:19 +01:00
// const ext = avatarMatch[2];
avatarUrl = `${avatarMatch[1]}128`;
}
return {
2018-01-07 15:51:19 +01:00
name: mentionedMember ? mentionedMember.displayName : mentionedUser.username,
id: mentionedUser.id,
url: avatarUrl,
ext: avatarUrl.indexOf('.gif') >= 0 ? 'gif' : 'png'
};
}
2018-01-03 14:38:44 +01:00
const str = message.cleanContent;
// find a discord emote
2017-12-31 13:59:14 +01:00
const discordEmote = /<(a?):(\w+):(\d+)>/g.exec(str);
2017-12-31 01:54:52 +01:00
if (discordEmote) {
2017-12-31 13:59:14 +01:00
const ext = discordEmote[1] === 'a' ? 'gif' : 'png';
2017-12-31 01:54:52 +01:00
return {
2017-12-31 13:59:14 +01:00
name: discordEmote[2],
id: discordEmote[3],
url: `https://cdn.discordapp.com/emojis/${discordEmote[3]}.${ext}`,
ext
2017-12-31 01:54:52 +01:00
};
}
2018-01-03 14:38:44 +01:00
// find a unicode emoji
2017-12-31 01:54:52 +01:00
let unicodeEmoji;
twemoji.parse(str, (name, emoji) => {
if (unicodeEmoji) return false;
unicodeEmoji = {
name,
id: name,
2018-01-03 14:04:57 +01:00
url: `${emoji.base + emoji.size}/${name}${emoji.ext}`,
ext: emoji.ext
2017-12-31 01:54:52 +01:00
};
return false;
});
2018-01-03 14:38:44 +01:00
if (unicodeEmoji) return unicodeEmoji;
return null;
2017-12-07 03:30:56 +01:00
}
2017-12-19 15:43:00 +01:00
function reverseString(str) {
2017-12-31 01:54:52 +01:00
return str.split('').reverse().join('');
2017-12-19 15:43:00 +01:00
}
2017-12-31 01:54:52 +01:00
const commands = Object.keys(templates).map(x => `/${x}`).join(', ');
2017-12-19 15:43:00 +01:00
const otherCommands = {
2017-12-31 01:54:52 +01:00
invite: `Invite link: <${invitelink}>`,
help: `Available commands: ${commands}.\nUse \\\\<command> to flip the template horizontally.${config.discord.public ? "\nInvite link: <" + invitelink + ">" : ""}`,
beebot: `Available commands: ${commands}.\nUse \\\\<command> to flip the template horizontally.${config.discord.public ? "\nInvite link: <" + invitelink + ">" : ""}`
2017-12-31 01:54:52 +01:00
};
2021-05-10 14:16:10 +02:00
client.on('ready', () => {
client.user.setPresence({
game: { name: 'BBaper' },
status: 'online'
});
});
2017-12-31 01:54:52 +01:00
client.on('message', async message => {
let commandParsed = /^([/\\])(\w+)\b/.exec(message.cleanContent);
if (commandParsed) {
const [, direction, command] = commandParsed;
if (otherCommands[command]) {
const text = otherCommands[command];
message.channel.send(direction === '\\' ? reverseString(text) : text);
return;
}
}
2018-01-05 00:07:22 +01:00
if (message.cleanContent[0] === '/' || message.cleanContent[0] === '\\') {
const messageSplit = message.cleanContent.split(' ');
2018-01-07 15:51:19 +01:00
const emoji = await findEmoji(message);
let result = null;
let count = 0;
try {
if (emoji) {
let { name } = emoji;
for (let i = 0; i < messageSplit.length && count < 4; ++i) {
commandParsed = /^([/\\])(\w+)\b/.exec(messageSplit[i]);
if (commandParsed) {
const [, direction, command] = commandParsed;
if (templates[command]) {
logger.debug('Got command ', direction, command, direction === '\\' ? 'flipped' : 'not flipped', emoji);
count++;
name += command;
if (result === null) {
result = new ImageEx(emoji.url);
await result.loaded; // eslint-disable-line no-await-in-loop
}
const templateData = templates[command];
all(templateData, template => { // eslint-disable-line no-loop-func
result = render(template, result, null, direction === '\\');
});
2017-12-31 01:54:52 +01:00
}
} else if (i === 0) return;
}
if (result) {
const attachment = await result.toBuffer();
const messageOptions = {
files: [
{ attachment, name: `${name}.${emoji.ext}` }
]
};
logger.debug('Sending message with result:', result);
await message.channel.send('', messageOptions).then(() => {
logger.debug('Message sent!');
}).catch(err => {
logger.error('Message sending failed:', err);
});
}
2017-12-31 01:54:52 +01:00
}
} catch (err) {
logger.error(err);
2017-12-31 01:54:52 +01:00
}
}
});
2017-12-31 13:59:14 +01:00
process.on('uncaughtException', exception => {
logger.error(exception);
2017-12-31 13:59:14 +01:00
});