Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 19 additions & 91 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ require("dotenv").config() // Get .env

// Init discord.js
const Discord = require('discord.js');
const client = new Discord.Client();
const client = new Discord.Client({ partials: ['MESSAGE', 'CHANNEL', 'REACTION'] });

// Log when client is ready
client.on('ready', () => {
Expand All @@ -19,104 +19,32 @@ process.on('uncaughtException', function (err) {
console.log(err);
})

const { parseMsg, isCmd } = require("./modules/parse.js") // Import command parsing functions

// Ready and parse commands.yml
const fs = require("fs")
const YAML = require('yaml')
const commands = YAML.parse(fs.readFileSync('./commands.yml', 'utf8'))

// Import image module
let { exec, execGM, getFormat } = require("@frogebot/image")({ imageMagick: process.env.USE_IMAGEMAGICK, maxGifSize: process.env.MAX_GIF_SIZE, maxImageSize: process.env.MAX_IMAGE_SIZE })

let { findImage, sendImage } = require("./modules/utils.js") // Import image util commands
let { handleCmdMsg, handleReaction, handleMemberJoin, handleMemberLeave } = require("./modules/handler")

// On message handle command
client.on('message', async msg => {
if(msg.author.bot || !await isCmd(msg)) return // If not command or called by bot

let parsed = await parseMsg(msg); // Parses message, returns [0: Prefix, 1: Command, 2: Args string]

let cmd = commands[parsed[1]]
let args = parsed[2]
let startTime = new Date().getTime()

if(cmd.type == 'script') { // If command is set as script type
let { cmdFunc } = require('./'+cmd.path) // Gets function of command
setImmediate(async () => {
cmdFunc(msg, args, startTime) // Runs command function
});
} else if (cmd.type == 'image') { // If command is set as image type
let imageUrl = await findImage(msg); // Find image in channel
try {
procMsg = await msg.channel.send(process.env.MSG_PROCESSING);
msg.channel.startTyping()

let r;
if(cmd.r) { // Handle replacement of input args in "r" val of command
for(let i = cmd.r.split('||').length-1; i >= 0; i--) {
newR = cmd.r.split('||')[i].trim() // Separate potential r value
if(newR.match(/\(input\)/) && args.length > 0) { // If r accepts input and args are present
newR = newR.replace(/\(input\)/, args) // Replace with input value
if(cmd.r_type == 'int' && Number.isInteger(Number(newR))) r = newR // int type handling
if(cmd.r_type == 'num' && !Number.isNaN(Number(newR))) r = newR // num type handling
} else if(newR.match(/\(input\:[0-9]+\)/) && args.length > 0) { // If r accepts certain word of input and args are present
newR = newR.replace(/\(input:[0-9]+\)/, args.split(' ')[newR.replace(')','').split(':')[1]]) // Replace with input value
if(cmd.r_type.startsWith('int') && Number.isInteger(Number(newR))) r = newR // int type handling
if(cmd.r_type.startsWith('num') && !Number.isNaN(Number(newR))) r = newR // num type handling
} else if(!newR.match(/\(input\)/) && !newR.match(/\(input\:[0-9]+\)/)) { // If r is just a value
r = newR; // Set r with no worries :)
}
}
}
// Parse "r" as a number with constraints if required
if(cmd.r_type == 'int') r = parseInt(r)
if(cmd.r_type == 'num') r = Number(r)
if(cmd.r_type == 'int>0') r = Math.max(0, parseInt(r))
if(cmd.r_type == 'num>0') r = Math.max(0, Number(r))

// Replace "(r)" with the variable r in params
let list = cmd.list.map(l => { if(typeof l == "object") { return [ Object.keys(l)[0], l[Object.keys(l)[0]].params.map(p => { if(p == '(r)') { return r } else { return p } }) ] } else { return [ l, [] ] } })

// Execute command
let img;
if(cmd.library == 'jimp') img = await exec(imageUrl, list); // Execute with jimp
if(cmd.library == 'magick') img = await execGM(imageUrl, list); // Execute with magick

let extension = await getFormat(imageUrl) // Get extension of the output image

// Send image
sendImage(msg, cmd.title, startTime, img, extension, procMsg)
} catch(e) {
// If error, catch it and let the user know
console.log(e)
msg.channel.stopTyping()
msg.channel.send({
embed: {
"title": "Error",
"description": `<@${msg.author.id}> - ${ imageUrl != undefined ? process.env.MSG_ERROR : process.env.MSG_NO_IMAGE}`,
"color": Number(process.env.EMBED_COLOUR),
"timestamp": new Date(),
"author": {
"name": process.env.BOT_NAME,
"icon_url": msg.client.user.displayAvatarURL()
}
}
})
procMsg.delete();
}
} else if (cmd.type == 'music' && process.env.MUSIC_ENABLED == "true") { // If command is set as music type
musicWorker.postMessage({ msgId: msg.id, channelId: msg.channel.id, args, cmd }) // Post message to music worker
}
handleCmdMsg(msg)
});
// Handle reactions
client.on('messageReactionAdd', async (reaction, user) => {
handleReaction(reaction, user, false)
});
client.on('messageReactionRemove', async (reaction, user) => {
handleReaction(reaction, user, true)
});
// Handle member join
client.on('guildMemberAdd', async member => {
handleMemberJoin(member)
});
// Handle member leave
client.on('guildMemberRemove', async member => {
handleMemberLeave(member)
});

// Create music worker
let musicWorker;
if(process.env.MUSIC_ENABLED == "true") {
const { Worker } = require('worker_threads');

const musicWorkerPath = '/modules/music-worker.js'
musicWorker = new Worker(__dirname+musicWorkerPath) // Spawn worker
}

var path = require('path');

Expand Down
85 changes: 85 additions & 0 deletions commands/reactions/starboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
require("dotenv").config()

const escapeMarkdown = s => s.replace(/([\[\]\(\)])/g, '\\$&')
const attachmentType = a =>
({
png: 'image',
jpg: 'image',
jpeg: 'image',
gif: 'image',
webp: 'image',
mp4: 'video',
mov: 'video',
webm: 'video',
}[fileExtension(a.url).toLowerCase()])

const fileExtension = url => {
if (!url) return

return url.split('.').pop().split(/\#|\?/)[0]
}

async function starFunc(reaction, member, data, startTime) {
const channel = await reaction.client.channels.fetch(data.channel.toString())
if(!channel || reaction.message.channel.id == channel.id) return
const messages = await channel.messages.fetch({ limit: 100 });
const inBoard = messages.find(m => (m.embeds && m.embeds.length >= 1 && m.embeds[0].description.endsWith(`[Jump to Message](${reaction.message.url})`)));

if(inBoard) {
inBoard.edit(inBoard.embeds[0].setFooter(`⭐ ${reaction.count}`))
if(reaction.count < data.count) return
} else {
if(reaction.count < data.count) return
const attachments = [...reaction.message.attachments.values()]
const primaryAttachment = attachments.shift()
let attachmentEmbed = {},
files = []
if (primaryAttachment) {
switch (attachmentType(primaryAttachment)) {
case 'image':
attachmentEmbed = {
image: { url: primaryAttachment.proxyURL },
}
break
case 'video':
files = [{ attachment: primaryAttachment.proxyURL }]
break
default:
// Unknown; we'll handle it with all the other attachments
attachments.unshift(primaryAttachment)
}
}
const fields = []
// Add reamining attachments to an extra field
if (attachments.length) {
fields.push({
name: 'Attachments',
value: attachments
.map(a => `[${a.proxyURL.substring(a.proxyURL.lastIndexOf('/') + 1)}](${a.proxyURL})`,)
.join('\n'),
})
}
channel.send({
files,
embed: {
color: data.colour,
author: {
name: reaction.message.member.displayName,
icon_url: await reaction.message.author.displayAvatarURL(),
},
description: `${escapeMarkdown(reaction.message.content)}\n\n[Jump to Message](${reaction.message.url})`,
timestamp: reaction.message.createdTimestamp,
footer: {
text: `⭐ ${reaction.count}`
},
fields,
...attachmentEmbed,
},
})
}
}

module.exports = {
reactionAddFunc: starFunc,
reactionRemoveFunc: starFunc,
}
Loading