diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a3d94a3..be6c62d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,6 +2,9 @@ name: CI on: [push] +permissions: + contents: read + jobs: CI: runs-on: node @@ -13,6 +16,6 @@ jobs: with: node-version: 'latest' - name: install dependencies - run: npm install + run: npm ci - name: run tests - run: npm test \ No newline at end of file + run: npm test diff --git a/commands/admin/setPresence.js b/commands/admin/setPresence.js index bd6fe5f..5dbfd51 100644 --- a/commands/admin/setPresence.js +++ b/commands/admin/setPresence.js @@ -1,3 +1,4 @@ +const savePresence = require("../../util/savePresence"); const setPresence = require("../../util/setPresence"); module.exports = { @@ -10,12 +11,20 @@ module.exports = { ,"Updates once a minute if custom variables are used." ,"" ,"Custom Variables:" - ,"${guilds},${prefix},${uptime}"], + ,"${guilds},${prefix},${uptime},{members}"], admin: true, - execute({message, client, args, globalPrefix}) { - const savePresence = require("../../util/savePresence"); + execute({message, client, args, prefix}) { + let forceUpdate = false; + if(args.length > 1 && args[0] === "force") { + forceUpdate = true; + args.shift(); + } + if(args.length < 1){ + message.channel.send(`You need at least two arguments for this command, see \`${prefix}help setpresence\``) + return; + } let presenceType = args[0].toLocaleUpperCase(); - let sendText = "Updated presence"; + let sendText = "Presence has been set."; switch (presenceType) { case "PLAY": @@ -46,8 +55,11 @@ module.exports = { const firstArg = args[0].length + 1; let temp = args.join(" "); let presenceText = temp.slice(firstArg, temp.length) - setPresence({presenceText: presenceText,presenceType: presenceType, client: client, globalPrefix: globalPrefix}); savePresence(presenceType, presenceText, client); + if(forceUpdate) + setPresence({presenceText, presenceType, client}) + else + sendText = `${sendText} It will update ` } message.channel.send(sendText); diff --git a/commands/admin/update.js b/commands/admin/update.js index 7de9db0..7c9a75f 100644 --- a/commands/admin/update.js +++ b/commands/admin/update.js @@ -1,4 +1,5 @@ const calculateReloaded = require("../../util/calculateReloaded"); +const executeCommand = require("../../util/executeCommand"); const reloadCommands = require("../../util/reloadCommands"); module.exports = { @@ -27,10 +28,12 @@ module.exports = { let commitCount = stdout.split(/\r\n|\r|\n/).length - 1 sendText = `${sendText}\n\nLatest commits (${commitCount}):\n${stdout}` if(sendText.length >= 2000){ - sendText.slice(1955) + sendText = sendText.slice(1955) sendText = `${sendText}\n... Message is too long to show everything` } message.channel.send(sendText) + const githash = executeCommand(`git`, ["rev-parse", "--short", "HEAD"]); + client.githash = githash.error ? "N/A" : githash.output; if (err) console.log(stderr) }) } diff --git a/commands/admin/whitelist.js b/commands/admin/whitelist.js index b5659bf..327e3de 100644 --- a/commands/admin/whitelist.js +++ b/commands/admin/whitelist.js @@ -2,55 +2,106 @@ const sqlite3 = require('sqlite3').verbose(); module.exports = { name: 'whitelist', - description: 'Whitelist a command in a specific server.', + description: 'Whitelist a command in a specific server, or for a user.', admin: true, async execute({message, args, prefix, client}) { - if(args.length < 2){ - message.channel.send(`You need to supply the command and channel that you want to whitelist\n\`${prefix}whitelist \``); + if(args.length < 3){ + message.channel.send(`You need to supply the command and guild that you want to whitelist\n\`${prefix}whitelist \``); return; } - let command = args[0]; - let guild = args[1]; - if(guild === "this") guild = message.guild.id; - // TODO: Add ability to remove server from whitelist. - const whitelistedCommands = client.whitelist.get(guild); - if(whitelistedCommands && whitelistedCommands.includes(command)){ - message.channel.send("Command is already whitelisted in that server.") + let option = args[0]; + let command = args[1]; + let guildOrUser = args[2]; + + if(option === "guild" || option === "server"){ + if(guildOrUser === "this") guildOrUser = message.guild.id; + const whitelistedCommands = client.whitelist.guild.get(guildOrUser); + if(whitelistedCommands && whitelistedCommands.includes(command)){ + message.channel.send("Command is already whitelisted in that server.") + return; + } + // TODO: First check if the bot has access to that guild before whitelisting. + const databasePath = 'data/database.db' + const db = new sqlite3.Database(databasePath) + let err = false; + await new Promise((resolve, reject)=>{ + db.run(`INSERT INTO guild_whitelist ( + guildId, + command, + dateAdded + ) VALUES (?, ?, ?)`, + [ + guildOrUser, + command, + new Date().getTime() + ], function (error) { + if (error) { + console.error(error); + err = true; + resolve(); + }else{ + resolve(); + } + }); + }) + db.close(); + if(!err){ + if(whitelistedCommands){ + whitelistedCommands.push(command); + client.whitelist.guild.set(guildOrUser, whitelistedCommands); + }else { + client.whitelist.guild.set(guildOrUser, command); + } + message.channel.send(`"${command}" has been whitelisted in that server.`) + + } else message.channel.send("Could not whitelist the server with that command. Check the logs.") + return; + }else if (option === "user"){ + const whitelistedCommands = client.whitelist.user.get(guildOrUser); + if(whitelistedCommands && whitelistedCommands.includes(command)){ + message.channel.send("Command is already whitelisted for that user.") + return; + } + const databasePath = 'data/database.db' + const db = new sqlite3.Database(databasePath) + let err = false; + await new Promise((resolve, reject)=>{ + db.run(`INSERT INTO user_whitelist ( + userId, + command, + dateAdded + ) VALUES (?, ?, ?)`, + [ + guildOrUser, + command, + new Date().getTime() + ], function (error) { + if (error) { + console.error(error); + err = true; + resolve(); + }else{ + resolve(); + } + }); + }) + db.close(); + if(!err){ + if(whitelistedCommands){ + whitelistedCommands.push(command); + client.whitelist.user.set(guildOrUser, whitelistedCommands); + }else { + client.whitelist.user.set(guildOrUser, command); + } + message.channel.send(`"${command}" has been whitelisted for ${guildOrUser}.`) + + } else message.channel.send("Could not whitelist the server with that command. Check the logs.") return; } - // TODO: First check if the bot has access to that guild before whitelisting. - const databasePath = 'data/database.db' - const db = new sqlite3.Database(databasePath) - let err = false; - await new Promise((resolve, reject)=>{ - db.run(`INSERT INTO whitelist ( - serverId, - command, - dateAdded - ) VALUES (?, ?, ?)`, - [ - guild, - command, - new Date().getTime() - ], function (error) { - if (error) { - console.error(error); - err = true; - resolve(); - }else{ - resolve(); - } - }); - }) - if(!err){ - if(whitelistedCommands){ - whitelistedCommands.push(command); - client.whitelist.set(guild, whitelistedCommands); - }else { - client.whitelist.set(guild, command); - } - message.channel.send("Command has been whitelisted in that server.") - } else message.channel.send("Could not whitelist the server with that command. Check the logs.") + + + // TODO: Add ability to remove server from whitelist. } + }; \ No newline at end of file diff --git a/commands/info/botinfo.js b/commands/info/botinfo.js index df700bf..b806459 100644 --- a/commands/info/botinfo.js +++ b/commands/info/botinfo.js @@ -1,6 +1,6 @@ const {EmbedBuilder} = require('discord.js'); const getCreationDate = require('../../util/getCreationDate'); -const getGuildCount = require('../../util/getGuildCount'); +const getGuildInfo = require('../../util/getGuildInfo'); @@ -8,15 +8,28 @@ module.exports = { name: 'botinfo', description: 'Shows information about the bot', execute({message, client, prefix}) { - let guildCount = getGuildCount(client) + let guildInfo = getGuildInfo(client) + let descriptionArr = [`Name: ${client.user.username}`, + `Prefix: ${prefix}`, + `Total Servers: ${guildInfo.guildCount}`, + `Total Members: ${guildInfo.totalMembers} (${guildInfo.uniqueMemberCount} unique)`, + `Total Commands: ${client.commands.size}`, + `Creation Date: ${getCreationDate(client)}`, + `Source: [Click Here](https://github.com/SileNce5k/discord_bot)`, + `Current Version: ${client.githash}` + ] + + let description = ""; + descriptionArr.forEach(desc => { + description += `${desc}\n`; + }) + const embed = new EmbedBuilder() .setColor(15780145) .setTitle("Information about bot") .setTimestamp() .setAuthor({name: client.user.username, iconURL: client.user.avatarURL({ format: 'png', dynamic: true, size: 2048 })}) - .addFields({ - name: "General info", value: `Name: ${client.user.username}\nPrefix: ${prefix}\nTotal Servers: ${guildCount}\nTotal Commands: ${client.commands.size}\nCreation Date: ${getCreationDate(client)}\nSource: [Click Here](https://github.com/SileNce5k/discord_bot)`, - },) + .setDescription(description) message.channel.send({embeds :[embed]}) diff --git a/commands/info/guilds.js b/commands/info/guilds.js index c4eb45e..480fdda 100644 --- a/commands/info/guilds.js +++ b/commands/info/guilds.js @@ -3,10 +3,10 @@ module.exports = { description: 'Returns guild names', admin: true, execute({message, client}) { - let guildNames = ""; - client.guilds.cache.each(guild => { - guildNames = guildNames + guild.name + "\n"; - }); + let guildNames = client.guilds.cache + .sort((a, b) => b.memberCount - a.memberCount) + .map(guild => `${guild.name} (${guild.memberCount} members)`) + .join("\n"); message.channel.send(guildNames) } }; \ No newline at end of file diff --git a/commands/info/help.js b/commands/info/help.js index 36ed658..da05e34 100644 --- a/commands/info/help.js +++ b/commands/info/help.js @@ -13,7 +13,6 @@ module.exports = { execute({ message, args, prefix, client }) { let commands = "" let commandFiles = getSubdirFiles('commands/') - let x = false; let fieldName = `Page [[page]]/${Math.round(client.commands.size / 10)}`; let iteration = 0; let num_in_args = false; @@ -32,7 +31,6 @@ module.exports = { fieldName = fieldName.replace("[[page]]", page); const max_commands = iteration + 10; - if (x) return; const embed = new EmbedBuilder() .setColor(15780145) diff --git a/commands/info/mc.js b/commands/info/mc.js new file mode 100644 index 0000000..dac751b --- /dev/null +++ b/commands/info/mc.js @@ -0,0 +1,103 @@ +// Code is taken from https://github.com/stphnduvall/mcstatus/blob/master/src/index.ts +// and converted to pure js. + +const net = require('net') +const { EmbedBuilder } = require('discord.js'); + +module.exports = { + name: 'mc', + description: 'get minecraft server information', + hidden: true, + needsWhitelist: true, + async execute({ message, args }) { + let host = ""; + let port = 25565; + if (args[0]) host = args[0]; + + if (host.includes(":")) { + port = host.replace(/.+(?:\:)/g, ""); + host = host.match(/.+(?:\:)/g, "")[0].replace(":", ""); + } + if(host === "") return message.channel.send("No host provided") + let info = await getMinecraftServerInfo(host, port); + if (info) { + const embed = new EmbedBuilder() + embed.setColor("#ee7939") + embed.setTimestamp() + embed.addFields( + { name: "ping", value: info.ping.toString(), inline: false }, + { name: "Player Count", value: info.playercount.toString(), inline: false }, + { name: "Max Players", value: info.maxPlayers.toString(), inline: false }, + { name: "MOTD", value: info.motd, inline: false }, + ) + + message.channel.send({ embeds: [embed] }); + } else { + message.channel.send("Something went wrong\nThe minecraft server is likely not reachable from the discord bot") + } + + } +}; + + + +async function getMinecraftServerInfo(host, port = 25565) { + let serverInfo = { + ping: undefined, + maxPlayers: undefined, + version: undefined, + playercount: undefined, + motd: undefined + } + + let startTime = new Date(); + let data; + let ping; + serverInfo = await new Promise((resolve) => { + const client = net.connect({ host, port }, () => { + ping = Math.round(new Date().getMilliseconds() - startTime.getMilliseconds()); + + let buff = Buffer.from([0xFE, 0x01]); + client.write(buff); + + }) + let error = false; + client.on('data', (d) => { + data = d.toString() + client.destroy(); + + }) + client.once('error', (error) => { + console.error(error) + error = true; + }) + client.once('connectionAttemptFailed', (ip) => { + console.error("in attempt failed") + error = true; + }) + + client.once('connectionAttemptTimeout', (ip) => { + console.error("in attempt timeout") + error = true; + }) + client.on('close', () => { + if (!error) { + let _serverInfo = data?.split('\x00\x00\x00'); + + if (!_serverInfo) { + console.log("Something went wrong.") + resolve(serverInfo) + } + serverInfo.version = _serverInfo[2].replace(/\u0000/g, '') + serverInfo.motd = _serverInfo[3].replace(/\u0000/g, '') + serverInfo.playercount = Number(_serverInfo[4].replace(/\u0000/g, '')) + serverInfo.maxPlayers = Number(_serverInfo[5].replace(/\u0000/g, '')) + serverInfo.ping = Number(ping) + resolve(serverInfo) + } else { + resolve(0) + } + }) + }) + return serverInfo +} diff --git a/commands/info/pfp.js b/commands/info/pfp.js index 68a4640..dd76507 100644 --- a/commands/info/pfp.js +++ b/commands/info/pfp.js @@ -16,9 +16,9 @@ module.exports = { let user = message.guild.members.cache.get(info); let guildPfp = user.avatarURL({format: 'png', dynamic: true, size: 4096}); let globalPfp = user.user.avatarURL({format: 'png', dynamic: true, size: 4096}); - let sendText = `Global pfp:\n${globalPfp}`; + let sendText = `${globalPfp}`; if(guildPfp != null){ - sendText = `${sendText}\nGuild pfp:\n${guildPfp}`; + sendText = `Global pfp${sendText}\nGuild pfp:\n${guildPfp}`; } message.channel.send(sendText) diff --git a/commands/info/serverinfo.js b/commands/info/serverinfo.js index 1526c32..1f329de 100644 --- a/commands/info/serverinfo.js +++ b/commands/info/serverinfo.js @@ -15,6 +15,7 @@ module.exports = { {name: "Server Name", value: message.guild.name, inline: false}, {name: "Created", value: convertDateToISOString(message.guild.createdAt), inline: false}, {name: "Members", value: message.guild.memberCount.toString(), inline: false}, + {name: "Channels", value: message.guild.channels.channelCountWithoutThreads.toString(), inline: false}, ]) diff --git a/commands/misc/dl.js b/commands/misc/dl.js new file mode 100644 index 0000000..13d95df --- /dev/null +++ b/commands/misc/dl.js @@ -0,0 +1,71 @@ +const path = require('path'); +const fs = require('fs') + +const executeCommand = require('../../util/executeCommand'); +module.exports = { + name: 'dl', + description: 'Download a video', + moreHelp: [ + "Usage: dl " + ], + async execute({message, args}) { + const downloadsDir = path.resolve(process.cwd(), 'data', 'downloads', Date.now().toString()); + const cookieFilepath = path.resolve(process.cwd(), 'data', 'cookies.txt') + if(!fs.existsSync(cookieFilepath)) { + message.channel.send("Some dependencies are needed for the command to work properly. Please let the bot's owner know.") + return; + } + fs.mkdirSync(downloadsDir, {recursive: true}); + + let url; + + if(args.length > 0){ + if(args[0].charAt(0) === '<' && args[0].charAt(args[0].length - 1) === '>'){ + args[0] = args[0].slice(1, args[0].length - 1) + } + try { + url = new URL(args[0]); + url = url.href; + } catch (error) { + this.cleanUp(downloadsDir); + message.channel.send("Could not parse the provided argument as a URL."); + return; + } + } else { + this.cleanUp(downloadsDir); + return message.channel.send("You have to provide a URL in an argument.") + } + + const originalMessage = await message.channel.send("Downloading video...") + + if(executeCommand("yt-dlp", [url, "-P", downloadsDir, "--cookies", cookieFilepath]).error){ + originalMessage.edit("An error occurred when downloading the video."); + this.cleanUp(downloadsDir); + return; + } + + + let files = fs.readdirSync(downloadsDir); + if(files.length < 1) { + this.cleanUp(downloadsDir); + originalMessage.edit("Something went wrong when downloading the video.") + return; + } + const filename = files[0]; + + await originalMessage.edit({ + content: null, + files: [{ + attachment: path.resolve(downloadsDir, filename) + }]}) + + this.cleanUp(downloadsDir); + + + }, + + cleanUp(downloadsDir){ + fs.rmSync(downloadsDir, {force: true, recursive: true}); + }, + +} diff --git a/commands/misc/fm.js b/commands/misc/fm.js index c3a0089..e259f4c 100644 --- a/commands/misc/fm.js +++ b/commands/misc/fm.js @@ -9,6 +9,7 @@ const getTopArtists = require("../../util/lastfm/getTopArtists"); const getTopAlbums = require("../../util/lastfm/getTopAlbums"); const {EmbedBuilder} = require('discord.js'); const getNickname = require('../../util/getNickname'); +const isWhitelisted = require("../../util/isWhitelisted"); module.exports = { name: 'fm', description: 'Last fm commands. See `help fm` for more info.', @@ -16,6 +17,8 @@ module.exports = { "They behave the same (for example: `fmtt` and `fm tt`)", "Set username: `fmset `", "Get current scrobble: `fm`", + "Get top artists `fmas`", + "Get top albums: `fmabl`", "Get top tracks: `fmtt`", "Get album cover for current scrobble: `fmcover`", "Get a roast from an LLM using your top artists and albums: `fmroast`" @@ -34,12 +37,31 @@ module.exports = { args.shift(); sendText = await getTopTracks(message.author.id, args, message.guild); break; + case "topalbums": + case "topalbum": + case "abl": + args.shift(); + sendText = await getTopAlbums(message.author.id, args, message.guild) + break; + case "topartists": + case "topartist": + case "ta": + case "as": + args.shift(); + sendText = await getTopArtists(message.author.id, args, message.guild); + break; case "cover": sendText = await getCurrentCover(message.author.id, message.guild); break; case "roast": + const commandName = "fmroast" + if(!isWhitelisted.isWhitelisted(commandName, client.whitelist, message.guild.id, message.author.id, isWhitelisted.whitelistTypes.EITHER)) { + message.channel.send("fmroast has not been whitelisted for you or this server.") + return; + } let topArtists = await getTopArtists(message.author.id, ["yearly"], message.guild, true); let topAlbums = await getTopAlbums(message.author.id, ["yearly"], message.guild, true); + message.channel.sendTyping(); let result = await roast(topArtists, topAlbums); let parse = parseMention(message.author.id, message.guild) @@ -65,11 +87,13 @@ module.exports = { if(sendText.embed != null){ let parse = parseMention(message.author.id, message.guild) let user = message.guild.members.cache.get(parse); - let roleColor = 15788778; - if (user.roles.color) { - roleColor = user.roles.color.color; + if(!sendText.embed.data.color){ + let roleColor = 15788778; + if (user.roles.color) { + roleColor = user.roles.color.color; + } + sendText.embed.setColor(roleColor); } - sendText.embed.setColor(roleColor); message.channel.send({embeds :[sendText.embed]}) }else{ message.channel.send(sendText.text.replaceAll("", prefix)); diff --git a/commands/misc/ig.js b/commands/misc/ig.js new file mode 100644 index 0000000..21268b7 --- /dev/null +++ b/commands/misc/ig.js @@ -0,0 +1,28 @@ + +module.exports = { + name: 'ig', + description: 'Replaces Instagram links with kkinstagram and deletes the original message', + execute({message, args}) { + const noUrlErr = "You need to provide an Instagram link to use this command." + if(args.length < 1) { + message.channel.send(noUrlErr); + return; + } + let replacedLink = ""; + const regex = /(?<=\/)(instagram)\.com/g + if(args[0].startsWith("https://") || args[0].startsWith("http://")){ + replacedLink = args[0].replace(regex, "kkinstagram.com"); + } + if(replacedLink.length === 0 || args[0] === replacedLink){ + message.channel.send(noUrlErr); + return; + } + message.channel.send(replacedLink); + try{ + message.delete() + }catch(err){ + console.error(`${this.name}: An error occurred while trying to delete the original message.`) + console.error(err); + } + } +}; \ No newline at end of file diff --git a/commands/misc/ping.js b/commands/misc/ping.js index 6e23a0c..fe9fb09 100644 --- a/commands/misc/ping.js +++ b/commands/misc/ping.js @@ -1,7 +1,11 @@ module.exports = { name: 'ping', description: 'Just ping.', - execute({message, client}) { - message.channel.send(`Pong.\n${client.ws.ping}ms`) + async execute({message, client}) { + let apiLatency = client.ws.ping; + const time = process.hrtime.bigint(); + let initialMessage = await message.channel.send(`Pong.`) + const diff = Number(process.hrtime.bigint() - time) * 1e-6; + initialMessage.edit(`Pong.\nAPI latency: ${apiLatency} ms\nMessage latency: ${diff.toFixed(0)} ms.`); } }; \ No newline at end of file diff --git a/commands/misc/prefix.js b/commands/misc/prefix.js index 19ec7d1..954c834 100644 --- a/commands/misc/prefix.js +++ b/commands/misc/prefix.js @@ -1,10 +1,11 @@ const setServerPrefix = require("../../util/setServerPrefix"); +const { PermissionsBitField } = require('discord.js'); module.exports = { name: 'prefix', description: 'Change the prefix of the bot in this server.', execute({ message, client, args, prefix }) { - if (!message.member.permissions.has('MANAGE_GUILD')) { + if (!message.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) { message.channel.send("You do not have sufficient permissions(MANAGE_GUILD) to change the prefix of this server.") return; } diff --git a/commands/misc/say.js b/commands/misc/say.js index e8ca4e6..2995f6a 100644 --- a/commands/misc/say.js +++ b/commands/misc/say.js @@ -1,6 +1,7 @@ module.exports = { name: 'say', description: 'Repeats arguments', + admin: true, execute({message, args}) { if(args.length == 0) diff --git a/commands/misc/tdoss.js b/commands/misc/tdoss.js index d172ec5..f5430fe 100644 --- a/commands/misc/tdoss.js +++ b/commands/misc/tdoss.js @@ -3,16 +3,18 @@ const fs = require('fs'); const path = require('path'); const { writeFile } = require('node:fs/promises') const { Readable } = require('node:stream') - +const executeCommand = require('../../util/executeCommand'); module.exports = { name: 'tdoss', description: 'Combine picture with tdoss album cover template', async execute({ message, args }) { - let dataDir = path.resolve(__dirname, '..', '..', 'data'); - const directory = path.resolve(dataDir, Math.floor(new Date).toString()) - fs.mkdirSync(directory) + let tdossDir = path.resolve(process.cwd(), 'data', 'tdoss'); + const tdossTemplate = path.resolve(process.cwd(), 'resources', 'tdoss_template.png'); + + const directory = path.resolve(tdossDir, Math.floor(new Date).toString()) + fs.mkdirSync(directory, {recursive: true}) let url = ""; if(message.attachments.size > 0){ @@ -44,15 +46,22 @@ module.exports = { } // TODO: Download with correct extension. message.channel.sendTyping(); - if(await this.downloadImage(url, path.resolve(directory, "input.png")) != 0){ - message.channel.send("Something went wrong during the download.\nThe link might be unreachable for the bot or it's not an image.") + let downloadResult = await this.downloadImage(url, path.resolve(directory, "input.png")); + if(downloadResult.value != this.ERROR_CODES.SUCCESS){ + if(downloadResult.value === this.ERROR_CODES.FETCH_ERROR){ + message.channel.send(`Failed to download the provided image, got error '${downloadResult.errorMessage}'`); + }else if (downloadResult.value === this.ERROR_CODES.HTTP_ERROR){ + message.channel.send(`Failed to download the provided image, got response status '${downloadResult.errorMessage}'`); + }else if(downloadResult.value === this.ERROR_CODES.NOT_IMAGE){ + message.channel.send(`The provided url was not an image.`) + } fs.rmSync(`${directory}`, {recursive: true}) return; } - const command = `magick ${dataDir}/tdoss_template.png \\( ${directory}/input.png -resize 800x800^ -gravity center -extent 1000x1000 \\) -compose dst-over -composite ${directory}/tdoss_result.png`; - if (this.executeCommand(command).error === true) { + const commandArgs = [tdossTemplate, "(", `${directory}/input.png`, "-resize", "800x800^", "-gravity", "center", "-extent", "1000x1000", ")", "-compose", "dst-over", "-composite", `${directory}/tdoss_result.png`] + if (executeCommand("magick", commandArgs).error === true) { message.channel.send("Something went wrong during image manipulation.\nTry again and if it keeps happening, contact the owner of the bot.") fs.rmSync(`${directory}`, {recursive: true}) return @@ -66,30 +75,26 @@ module.exports = { await message.channel.send({files: [final_image]}) fs.rmSync(`${directory}`, {recursive: true}) }, - - - executeCommand(command) { - console.log("Executing:", command) - try { - const output = execSync(command, { encoding: 'utf-8' }) - if (output.length != 0) - console.log(output) - } catch (error) { - console.error(`Error executing ${command.split(" ")[0]} command:`, error); - return { error: true }; - } - return { error: false }; - }, // https://stackoverflow.com/a/77210219 async downloadImage(url, path) { - const res = await fetch(url); - if(!res.ok) return 1; - if(!res.headers.get('content-type').startsWith("image")) return 2; + let res; + try { + res = await fetch(url); + } catch (error) { + return {value: this.ERROR_CODES.FETCH_ERROR, errorMessage: error.cause?.message || error.message}; + } + if(!res.ok) return {value: this.ERROR_CODES.HTTP_ERROR, errorMessage: res.status.toString()}; + const contentType = res.headers.get('content-type'); + if(!contentType || !contentType.startsWith("image")) return {value: this.ERROR_CODES.NOT_IMAGE, errorMessage: contentType || "No content-type header"}; const stream = Readable.fromWeb(res.body) await writeFile(path, stream); - return 0; + return {value: this.ERROR_CODES.SUCCESS, errorMessage: ""}; + }, + ERROR_CODES: { + SUCCESS: 0, + HTTP_ERROR: 1, + NOT_IMAGE: 2, + FETCH_ERROR: 3 } } - - diff --git a/commands/misc/timer.js b/commands/misc/timer.js index 746689b..522a9aa 100644 --- a/commands/misc/timer.js +++ b/commands/misc/timer.js @@ -4,11 +4,11 @@ const parseTime = require('../../util/timer/parseTime'); const showTimer = require('../../util/timer/showTimer'); module.exports = { name: "timer", - description: "Set a timer for a time in minutes.", + description: "Set a timer for a date or time duration.", moreHelp: ["Usage:" ,"`timer [add|create] `" ,"`timer