diff --git a/git b/git new file mode 100644 index 00000000..e69de29b diff --git a/package.json b/package.json index 26a94a78..32ff5c38 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "node-os-utils": "^1.3.1", "node-pre-gyp": "^0.11.0", "node-schedule": "^1.3.2", + "twemoji-parser": "^13.0.0", + "weather-js": "^2.0.0", "winston": "^3.2.1", "yaml": "^1.8.3", "youtube-search": "^1.1.4" @@ -23,7 +25,9 @@ "nodemon": "^2.0.4" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "npm i && node app.js", + "dev": "nodemon app.js" }, "repository": { "type": "git", diff --git a/src/commands/Command.js b/src/commands/Command.js index 76fc2886..5ff3736b 100644 --- a/src/commands/Command.js +++ b/src/commands/Command.js @@ -82,7 +82,13 @@ class Command { * @type {boolean} */ this.disabled = options.disabled || false; - + + /** + * If command is hidden + * @type {boolean} + */ + this.hidden = options.hidden || false; + /** * Array of error types * @type {Array} diff --git a/src/commands/info/aliases.js b/src/commands/info/aliases.js index 68f367a5..42a9536d 100644 --- a/src/commands/info/aliases.js +++ b/src/commands/info/aliases.js @@ -48,7 +48,7 @@ module.exports = class AliasesCommand extends Command { if (args[0] && types.includes(type) && (type != OWNER || message.client.isOwner(message.member))) { message.client.commands.forEach(command => { - if (command.aliases && command.type === type && !disabledCommands.includes(command.name)) + if (command.aliases && command.type === type && !disabledCommands.includes(command.name) && !command.hidden) aliases[command.type].push(`**${command.name}:** ${command.aliases.map(a => `\`${a}\``).join(' ')}`); }); diff --git a/src/commands/info/covid.js b/src/commands/info/covid.js new file mode 100644 index 00000000..23d4e47d --- /dev/null +++ b/src/commands/info/covid.js @@ -0,0 +1,63 @@ +const fetch = require('node-fetch'); +const Command = require('../Command.js'); +const Discord = require('discord.js'); +const { MessageEmbed } = require('discord.js'); + +module.exports = class CovidCommand extends Command { + constructor(client) { + super(client, { + name: 'covid', + usage: 'covid ', + description: 'Shows covid stats for the provided country.', + type: client.types.INFO, + examples: ['covid usa'] + }); + } + + async run (message, args){ + + const countries = args.join(" "); + + if(!args[0]) return this.sendErrorMessage(message, 0, 'Please Provide a Argument'); + + if(args[0] === "all"){ + fetch(`https://covid19.mathdro.id/api`) + .then(response => response.json()) + .then(data => { + let confirmed = data.confirmed.value.toLocaleString() + let recovered = data.recovered.value.toLocaleString() + let deaths = data.deaths.value.toLocaleString() + const embed = new Discord.MessageEmbed() + .setTitle(`Worldwide COVID-19 Stats 🌎`) + .addField('Confirmed Cases', confirmed) + .addField('Recovered', recovered) + .addField('Deaths', deaths) + .setFooter(message.member.displayName, message.author.displayAvatarURL({ dynamic: true })) + .setTimestamp() + .setColor(message.guild.me.displayHexColor); + + message.channel.send(embed) + }) + } else { + fetch(`https://covid19.mathdro.id/api/countries/${countries}`) + .then(response => response.json()) + .then(data => { + let confirmed = data.confirmed.value.toLocaleString() + let recovered = data.recovered.value.toLocaleString() + let deaths = data.deaths.value.toLocaleString() + const embed = new Discord.MessageEmbed() + .setTitle(`COVID-19 Stats for **${countries}**`) + .addField('Confirmed Cases', confirmed) + .addField('Recovered', recovered) + .addField('Deaths', deaths) + .setFooter(message.member.displayName, message.author.displayAvatarURL({ dynamic: true })) + .setTimestamp() + .setColor(message.guild.me.displayHexColor); + message.channel.send(embed) + }).catch(err => { + message.client.logger.error(err.stack); + this.sendErrorMessage(message, 1, 'Invalid country provided', err.message); + }) + } + } +} \ No newline at end of file diff --git a/src/commands/info/help.js b/src/commands/info/help.js index 68ad7c76..36d9dfcc 100644 --- a/src/commands/info/help.js +++ b/src/commands/info/help.js @@ -73,9 +73,9 @@ module.exports = class HelpCommand extends Command { message.client.commands.forEach(command => { if (!disabledCommands.includes(command.name)) { - if (command.userPermissions && command.userPermissions.every(p => message.member.hasPermission(p)) && !all) + if (command.userPermissions && command.userPermissions.every(p => message.member.hasPermission(p)) && !all && !command.hidden) commands[command.type].push(`\`${command.name}\``); - else if (!command.userPermissions || all) { + else if (!command.userPermissions && !command.hidden || all && !command.hidden) { commands[command.type].push(`\`${command.name}\``); } } diff --git a/src/commands/info/urban.js b/src/commands/info/urban.js new file mode 100644 index 00000000..038161fa --- /dev/null +++ b/src/commands/info/urban.js @@ -0,0 +1,39 @@ +const Command = require('../Command.js'); +const { MessageEmbed } = require('discord.js'); +const fetch = require('node-fetch'); + +module.exports = class UrbanCommand extends Command { + constructor(client) { + super(client, { + name: 'urban', + usage: 'urban ', + description: 'Displays a page in urban dictonary.', + type: client.types.INFO, + }); + } + async run(message, args) { + if (!args.length) return this.sendErrorMessage(message, 0, 'You need to supply a search term!'); + + //return it as urban command may return NSFW results + if (!message.channel.nsfw) return this.sendErrorMessage(message, 0, "Please try this command again in a NSFW channel") + + const query = encodeURIComponent(args.join(' ')); //encode the args to fetch info from urban dictionary + + const { list } = await fetch(`https://api.urbandictionary.com/v0/define?term=${query}`).then(response => response.json()); + + if (!list.length) return this.sendErrorMessage(message, 0, `No results found for ${args.join(' ')}.`); + + const [answer] = list; + + const embed = new MessageEmbed() + .setColor(message.guild.me.displayHexColor) + .setTitle(answer.word) + .setURL(answer.permalink) + .addFields( + { name: 'Definition', value: (answer.definition) }, + { name: 'Example', value: (answer.example) }, + { name: 'Rating', value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.` }, + ); + message.channel.send(embed); + } +} \ No newline at end of file diff --git a/src/commands/info/weather.js b/src/commands/info/weather.js new file mode 100644 index 00000000..0356ee2e --- /dev/null +++ b/src/commands/info/weather.js @@ -0,0 +1,50 @@ +const Command = require('../Command.js'); +const { MessageEmbed } = require('discord.js'); +const weather = require('weather-js') + +module.exports = class WeatherCommand extends Command { + constructor(client) { + super(client, { + name: 'weather', + usage: 'weather ', + description: 'Shows weather of a city', + type: client.types.INFO, + examples: ['weather [city name]'] + }); + } + async run(message, args) { + try { + + if(!args[0]) return this.sendErrorMessage(message, 0, 'Please mention city name.'); + + weather.find({search: args.join(" "), degreeType: 'C'}, function(err, result){ + + if(err) message.channel.send(err.message); + + if(result.length === 0) return this.sendErrorMessage(message, 1, 'Please Enter a valid location!'); + + var current = result[0].current; + var location = result[0].location; + + const embed = new MessageEmbed() + .setDescription(`**${current.skytext}**`) + .setAuthor(`🌥️ Weather for ${current.observationpoint}`) + .setThumbnail(current.imageUrl) + .addField('**Timezone**', `UTC ${location.timezone}`, true) + .addField('**Degree Type**', `${location.degreetype}`, true) + .addField('**Temperature**', `${current.temperature} Degrees`, true) + .addField('**Feels Like**', `${current.feelslike} Degrees`, true) + .addField('**Winds**', `${current.winddisplay}`, true) + .addField('**Humidity**', `${current.humidity}%`, true) + .addField('**Date**', `${current.date}`, true) + .addField('**Day**', `${current.day}`, true) + .setFooter(message.member.displayName, message.author.displayAvatarURL({ dynamic: true })) + .setTimestamp() + .setColor(message.guild.me.displayHexColor); + message.channel.send({embed}) + }); + } catch (err) { + this.sendErrorMessage(message, 1, 'Please Enter a valid location!'); + } + } +} \ No newline at end of file diff --git a/src/commands/mod/addemoji.js b/src/commands/mod/addemoji.js new file mode 100644 index 00000000..7df51e43 --- /dev/null +++ b/src/commands/mod/addemoji.js @@ -0,0 +1,79 @@ +const Command = require('../Command.js'); +const { MessageEmbed } = require('discord.js'); +const Discord = require("discord.js"); +const { parse } = require("twemoji-parser"); +module.exports = class AddEmojiCommand extends Command { + constructor(client) { + super(client, { + name: 'addemoji', + usage: 'addemoji [:emoji: | link] ', + description: 'Add any of your preferred emoji from any server to your server.', + type: client.types.MOD, + clientPermissions: ['MANAGE_EMOJIS'], + userPermissions: ['MANAGE_EMOJIS'], + examples: ['addemoji <:HOUSE_BRILLIANCE:761678113159512114> hypesquad brilliance'] + }); + } +async run(message, args){ + try { + let name; + const urlRegex = new RegExp(/^(ftp|http|https):\/\/[^ "]+$/) + + const emoji = args[0]; + if (!emoji) this.sendErrorMessage(message, 0, 'Please mention a valid emoji.'); + + let customemoji = Discord.Util.parseEmoji(emoji) //Check if it's a emoji + + if (customemoji.id) { + const Link = `https://cdn.discordapp.com/emojis/${customemoji.id}.${ + customemoji.animated ? "gif" : "png" + }` + name = args.slice(1).join("_") + const emoji = await message.guild.emojis.create( + `${Link}`, + `${name || `${customemoji.name}`}` + ); + var Added = new MessageEmbed() + .setTitle(`${emoji}`) + .setDescription( + `Emoji Has Been Added! | Name : ${emoji.name || `${customemoji.name}`} | Preview : [Click Me](${emoji.url})`) + .setFooter(message.member.displayName, message.author.displayAvatarURL({ dynamic: true })) + .addField( + '**Links**', + '**[Invite Me](https://discordapp.com/oauth2/authorize?client_id=416451977380364288&scope=bot&permissions=403008599) | ' + + '[Support Server](https://discord.gg/pnYVdut) | ' + + '[Repository](https://github.com/sabattle/CalypsoBot)**' + ) + .setTimestamp() + .setColor(message.guild.me.displayHexColor); + return message.channel.send(Added); + } else if (urlRegex.test(args[0])) { //check for image urls + name = args.slice(1).join("_") || Math.random().toString(36).slice(2) //make the name compatible or just choose a random string + const addedEmoji = await message.guild.emojis.create( + `${emoji}`, + `${name || `${customemoji.name}`}` + ); + return message.channel.send(new MessageEmbed() + .setTitle(`${addedEmoji}`) + .setDescription( + `Emoji Has Been Added! | Name : ${addedEmoji.name || `${customemoji.name}`} | Preview : [Click Me](${addedEmoji.url})`) + .setFooter(message.member.displayName, message.author.displayAvatarURL({ dynamic: true })) + .addField( + '**Links**', + '**[Invite Me](https://discordapp.com/oauth2/authorize?client_id=416451977380364288&scope=bot&permissions=403008599) | ' + + '[Support Server](https://discord.gg/pnYVdut) | ' + + '[Repository](https://github.com/sabattle/CalypsoBot)**' + ) + .setTimestamp() + .setColor(message.guild.me.displayHexColor)); + } else { + let CheckEmoji = parse(emoji, { assetType: "png" }); + if (!CheckEmoji[0]) + return this.sendErrorMessage(message, 0, 'Please mention a valid emoji.'); + } + } catch (err) { + this.client.logger.error(err) + this.sendErrorMessage(message, 1, 'A error occured while adding the emoji. Common reasons are:- unallowed characters in emoji name, 50 emoji limit.', err) + } + } +} \ No newline at end of file diff --git a/src/commands/mod/poll.js b/src/commands/mod/poll.js new file mode 100644 index 00000000..83febb7d --- /dev/null +++ b/src/commands/mod/poll.js @@ -0,0 +1,34 @@ +const Command = require('../Command.js'); +const { MessageEmbed } = require('discord.js'); +const reactions = ['🇦', '🇧', '🇨', '🇩', '🇪', '🇫', '🇬', '🇭', '🇮', '🇯', '🇰', '🇱', '🇲', '🇳', '🇴', '🇵', '🇶', '🇷', '🇸', '🇹'] + +module.exports = class PollCommand extends Command { + constructor(client) { + super(client, { + name: 'poll', + usage: 'poll question | choice1 | choice2 | choice3 ...', + description: 'Create a poll upto 20 choices!', + type: client.types.MOD, + clientPermissions: ['SEND_MESSAGES', 'EMBED_LINKS', 'ADD_REACTIONS'], + userPermissions: ['MANAGE_MESSAGES'], + examples: ['poll is @Calypso the best bot?'] + }); + } + async run(message, args) { + const [question, ...choices] = args.join(' ').split(' | ') + + if (!question) return this.sendErrorMessage(message, 0, 'Provide a Valid Question'); + if (!choices.length) return this.sendErrorMessage(message, 0, 'Provide Valid Options'); + if (choices.length > 20) return this.sendErrorMessage(message, 0, 'Options can\'t be longer than 20'); + + let i; + const sent = await message.channel.send(new MessageEmbed() + .setTitle(`${question}`) + .setFooter(message.member.displayName, message.author.displayAvatarURL({ dynamic: true })) + .setTimestamp() + .setColor(message.guild.me.displayHexColor) + .setDescription(choices.map((choice, i) => `${reactions[i]} ${choice}`).join('\n'))) + + for (i = 0; i < choices.length; i++) await sent.react(reactions[i]) + } + } \ No newline at end of file diff --git a/src/commands/owner/reload.js b/src/commands/owner/reload.js new file mode 100644 index 00000000..b0047025 --- /dev/null +++ b/src/commands/owner/reload.js @@ -0,0 +1,48 @@ +const Command = require('../Command.js'); + +module.exports = class ReloadCommand extends Command { + constructor(client) { + super(client, { + name: 'reload', + usage: 'reload ', + description: 'Reloads a Command by category', + type: client.types.OWNER, + ownerOnly: true, + examples: ['reload owner eval'] + }); + } + async run(message, args) { + if(!args[0]) return this.sendErrorMessage(message, 0, "You must provide a category.") + if(!args[1]) return this.sendErrorMessage(message, 0, "You must provide a command name.") + + let commandCategory = args[0].toLowerCase() + let commandName = args[1].toLowerCase() + let oldCommand = message.client.commands.get(commandName); //Check for command + + if (oldCommand.aliases) { + oldCommand.aliases.forEach(async alias => { + await message.client.aliases.delete(alias); //delete Aliases to make space for new Command Aliases + })} + + try { + delete require.cache[require.resolve(`${process.cwd()}/src/commands/${commandCategory}/${commandName}.js`)] // path depends on your hosting/machine + await this.client.commands.delete(commandName) + const CommandStructure = require(`${process.cwd()}/src/commands/${commandCategory}/${commandName}.js`) + const command = new CommandStructure(message.client); + + if (command.name && !command.disabled) { + // Map command + this.client.commands.set(command.name, command); + // Map command aliases + if (command.aliases) { + command.aliases.forEach(async alias => { + await this.client.aliases.set(alias, command); + }); + } + + message.channel.send('Done ! Succesfully reloaded ' + commandName) + }} catch(err) { + return this.sendErrorMessage(message, 1, `Could not reload: ${commandName}`, err.stack); + } + } +}