2025-05-12 12:57:59 -05:00

250 lines
11 KiB
JavaScript

const PollDB = require("../models/polls");
const Embed = require("./embed");
const Canvas = require('canvas');
const format = `${(new Date().getMonth() + 1) < 10 ? `0${new Date().getMonth() + 1}` : new Date().getMonth() + 1}/${new Date().getDate()}/${new Date().getFullYear()} ${new Date().getHours()}:${(new Date().getMinutes() < 10 ? '0' : '') + new Date().getMinutes()}`;
class Polls {
constructor({ time, client, name, options, users, avatars, votes, owner, lang }) {
this.client = client;
this.time = time;
if (votes) this.votes = votes;
else this.votes = options.name.length === 2 ? [0, 0] : options.name.length === 3 ? [0, 0, 0] : options.name.length === 4 ? [0, 0, 0, 0] : options.name.length === 5 ? [0, 0, 0, 0, 0] : options.name.length === 6 ? [0, 0, 0, 0, 0, 0] : options.name.length === 7 ? [0, 0, 0, 0, 0, 0, 0] : options.name.length === 8 ? [0, 0, 0, 0, 0, 0, 0, 0] : options.name.length === 9 ? [0, 0, 0, 0, 0, 0, 0, 0, 0] : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
this.users = users || [];
this.avatars = avatars || [];
this.options = { name: name.name, description: name.description };
this.voteOptions = options;
this.owner = owner;
this.lang = lang;
this.size = { canvas: options.name.length === 2 ? 200 : options.name.length === 3 ? 250 : options.name.length === 4 ? 300 : options.name.length === 5 ? 350 : options.name.length === 6 ? 400 : options.name.length === 7 ? 450 : options.name.length === 8 ? 500 : options.name.length === 9 ? 550 : 600, bar: options.name.length === 2 ? 150 : options.name.length === 3 ? 200 : options.name.length === 4 ? 250 : options.name.length === 5 ? 300 : options.name.length === 6 ? 350 : options.name.length === 7 ? 400 : options.name.length === 8 ? 450 : options.name.length === 9 ? 500 : 550 };
}
async start(message, poll) {
this.client.polls.set(message.id, { poll, messageId: message.id, users: this.users, owner: this.owner, lang: this.lang })
setTimeout(async () => {
if (!this.client.polls.get(message.id)) return;
await this.update();
message.edit({ embeds: [new Embed().setMedia(await this.client.Uploader.upload(poll.canvas.toBuffer(), `Poll.png`)).setColor(`#F24646`)], content: this.client.translate.get(this.lang, "Functions.poll.end") }).catch(() => { })
this.client.polls.delete(message.id);
await PollDB.findOneAndDelete({ messageId: message.id });
}, this.time);
if (this.time < 0) return;
await (new PollDB({
owner: this.owner,
channelId: message.channelId,
messageId: message.id,
avatars: this.avatars,
users: this.users,
votes: this.votes,
name: this.options.name,
desc: this.options.description,
options: this.voteOptions,
time: this.time,
lang: this.lang,
now: Date.now(),
}).save());
}
textHeight(text, ctx, m) {
let metrics = m || ctx.measureText(text);
return metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
}
roundRect(ctx, x, y, width, height, radius, fill, stroke) { // Credit to https://stackoverflow.com/users/227299/juan-mendes
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = { tl: radius, tr: radius, br: radius, bl: radius };
} else {
var defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 };
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
async update() {
let roundRect = this.roundRect;
let textHeight = this.textHeight;
var width = 600, height = this.size.canvas, padding = 10;
const canvas = Canvas.createCanvas(width, height);
this.canvas = canvas;
const ctx = this.canvas.getContext('2d');
this.ctx = ctx;
let name = this.options.name?.length > 70 ? this.options?.name.slice(0, 67) + "..." : this.options.name;
var nameHeight = textHeight(name, ctx);
let description = this.options.description.length > 80 ? this.options.description.slice(0, 77) + "..." : this.options.description;
var descHeight = textHeight(description, ctx);
ctx.fillStyle = "#23272A";
roundRect(ctx, 0, 0, width, height, 5, true, false); // background
ctx.fillStyle = "#4E535A";
ctx.font = `normal 12px Sans-Serif`;
ctx.fillText(name, padding, padding + 2 + nameHeight / 2); // name
ctx.fillStyle = "#FFFFFF";
ctx.font = `normal 17px Sans-Serif`;
ctx.fillText(description, padding, padding + 15 + nameHeight + descHeight / 2); // description
var headerHeight = padding + descHeight + nameHeight + 15;
var dataWidth = width - padding * 2;
var barHeight = 40;
var votes = this.votes;
var names = this.voteOptions.name;
this.drawVoteBars(ctx, dataWidth - 20, barHeight, votes, { pad: padding, hHeight: headerHeight }, names);
await this.drawFooter(ctx, padding, padding + headerHeight + barHeight * 2 + 20, width, height, padding, this.avatars);
}
async addVote(option, user, avatar, id) {
if (this.avatars.length === 6) this.avatars.shift();
this.avatars.push(avatar);
this.votes[option]++;
await PollDB.findOneAndUpdate({ messageId: id }, { $push: { users: user, avatars: avatar }, $inc: { [`votes.${option}`]: 1 } });
await this.update();
return this.canvas;
}
async removeVote(option, user, avatar, id) {
this.avatars.splice(this.avatars.indexOf(avatar), 1);
this.votes[option]--;
await PollDB.findOneAndUpdate({ messageId: id }, { $pull: { users: user, avatars: avatar }, $inc: { [`votes.${option}`]: -1 } });
await this.update();
return this.canvas;
}
drawVoteBars(ctx, width, height, votes, vars, names, vote) {
let roundRect = this.roundRect;
let textHeight = this.textHeight;
let padding = vars.pad;
let headerHeight = vars.hHeight;
let sum = votes.reduce((prev, curr) => prev + curr);
let percentages = votes.map((v) => Math.floor(v / (sum / 100) * 10) / 10);
ctx.save();
ctx.translate(padding, padding + headerHeight);
var barPadding = 5;
percentages.forEach((percentage, i) => {
if (!percentage) percentage = 0;
let paddingLeft = (vote != undefined) ? 30 : 0;
ctx.fillStyle = "#2C2F33";
let y = (height + 10) * i;
roundRect(ctx, 20, y, width, height, 5, true, false); // full bar
if (vote == i || percentage) { ctx.fillStyle = "#A52F05"; }
else { ctx.fillStyle = "#24282B"; } // percentage display
roundRect(ctx, 20, y, width * (votes[i] / (sum / 100) / 100), height, 5, true, false);
ctx.fillStyle = "#4E535A";
let h = textHeight(i + 1, ctx);
ctx.fillText(i + 1, 0, y + height / 2 + h / 2);
ctx.fillStyle = "#FFFFFF"; // Option names
h = textHeight(names[i], ctx);
ctx.fillText(names[i].length > 65 ? names[i].slice(0, 62) + "..." : names[i], 30 + paddingLeft, y + 13 + h);
if (vote != undefined) {
ctx.strokeStyle = "#FFFFFF"; // selection circle
ctx.fillStyle = "#717cf4";
ctx.beginPath();
ctx.arc(35, y + 10 + h * 0.75, 6, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
if (vote == i) {
ctx.beginPath();
ctx.arc(35, y + 10 + h * 0.75, 3, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}
}
ctx.fillStyle = "#2C2F33"; // percentage and vote count background
let metrics = ctx.measureText(percentage + "% (" + votes[i] + ")");
let w = metrics.width;
h = textHeight(percentage + "% (" + votes[i] + ")", ctx, metrics);
y = y + (height - h - barPadding * 2) + barPadding * 2.6;
if (vote == i || vote == undefined) roundRect(ctx, width - barPadding - w - 3, y - h - 4, w + 5, h + 12, 5, true, false);
ctx.fillStyle = "#A52F05"; // percentage and vote count
ctx.fillText(percentage + "% (" + votes[i] + ")", width - barPadding - w, y);
});
ctx.restore();
}
async drawFooter(ctx, x, y, width, height, padding, users) {
ctx.save();
ctx.translate(10, this.size.bar);
var rad = 18;
ctx.fillStyle = "#4E535A";
ctx.lineWidth = 2;
ctx.strokeStyle = "#4E535A";
ctx.beginPath();
ctx.moveTo(0 - padding, 0);
ctx.lineTo(width, 0);
ctx.stroke();
let votes = (this.votes.reduce((p, c) => p + c) == 1) ? `${this.votes.reduce((p, c) => p + c)} vote` : `${this.votes.reduce((p, c) => p + c)} votes`;
let metrics = ctx.measureText(votes);
let h = this.textHeight(votes, ctx, metrics);
ctx.fillText(votes, 5, rad + h);
// Avatars
var pos = rad * users.length + 10 + metrics.width;
var yPos = 6;
users.reverse();
for (let i = 0; i < users.length; i++) {
ctx.beginPath();
let user = users[i];
const a = Canvas.createCanvas(rad * 2, rad * 2);
const context = a.getContext("2d");
context.beginPath();
context.arc(rad, rad, rad, 0, Math.PI * 2, true);
context.closePath();
context.clip();
const avatar = await Canvas.loadImage(user);
context.drawImage(avatar, 0, 0, rad * 2, rad * 2);
ctx.drawImage(a, pos, yPos);
ctx.closePath();
pos -= rad;
}
// Date
let date = format;
metrics = ctx.measureText(date);
h = this.textHeight(date, ctx, metrics);
ctx.fillText(date, width - 15 - metrics.width, rad + h);
ctx.restore();
}
}
module.exports = Polls;