├── src ├── guilds │ └── .gitkeep ├── core │ ├── warneduserids.json │ ├── emojis.js │ └── map.js ├── commands │ ├── sadlenny.js │ ├── test.js │ ├── doubleflip.js │ ├── bannedwords.js │ ├── invite.js │ ├── ping.js │ ├── dog.js │ ├── moji.js │ ├── lenny.js │ ├── cat.js │ ├── instructions.js │ ├── eval.js │ ├── stock.js │ ├── math.js │ ├── addbannedword.js │ ├── readspoiler.js │ ├── ban.js │ ├── removebannedword.js │ ├── kick.js │ ├── emptyquestion.js │ ├── speak.js │ ├── purge.js │ ├── help.js │ ├── crypto.js │ ├── advancedhelp.js │ ├── cmdhelp.js │ ├── spoiler.js │ ├── roshambo.js │ ├── urban.js │ ├── dankmeme.js │ ├── uptime.js │ ├── unity.js │ └── deep.js ├── services │ ├── currency.js │ ├── finnhub.js │ └── cryptocompare.js ├── modules │ ├── unflip.js │ ├── startup.js │ ├── serverconfigs.js │ ├── banwords.js │ ├── commands.js │ └── checkforcode.js └── bot.js ├── Procfile ├── .env.example ├── images └── avatar.png ├── docker-compose.yml ├── Dockerfile ├── config.json ├── package.json ├── CHANGELOG.md ├── LICENSE ├── .dockerignore ├── README.md ├── .gitignore ├── CONTRIBUTING.md └── CODE_OF_CONDUCT.md /src/guilds/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: node bot.js -------------------------------------------------------------------------------- /src/core/warneduserids.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | BOT_TOKEN= 2 | FINNHUB_API_KEY= 3 | -------------------------------------------------------------------------------- /images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bananaprotocol/confax/HEAD/images/avatar.png -------------------------------------------------------------------------------- /src/commands/sadlenny.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | Confax.registerCommand('sadlenny', 'default', (message, bot) => { 3 | return 'No.' 4 | }, ['sadlenny'], 'not lenny', '[]') 5 | -------------------------------------------------------------------------------- /src/commands/test.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('test', 'dm', (message) => { 4 | return '**Test successful!**' 5 | }, [], 'Test Command', '[]') 6 | -------------------------------------------------------------------------------- /src/commands/doubleflip.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('doubleflip', 'default', (message) => { 4 | return '┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻' 5 | }, [], 'Double table flip!', '[]') 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | confax: 5 | build: . 6 | container_name: confax 7 | restart: unless-stopped 8 | environment: 9 | - BOT_TOKEN= 10 | - FINNHUB_API_KEY= 11 | volumes: 12 | - './src/guilds:/usr/share/app/src/guilds' 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine 2 | 3 | LABEL maintainer="bananaprotocol@protonmail.com" 4 | 5 | WORKDIR /usr/share/app 6 | 7 | COPY package.json package-lock.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY . . 12 | 13 | ENV BOT_TOKEN= 14 | ENV FINNHUB_API_KEY= 15 | 16 | CMD ["npm", "start"] 17 | -------------------------------------------------------------------------------- /src/commands/bannedwords.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('bannedwords', 'default', (message, bot) => { 4 | message.channel.send(message.member.user + ' the banned words: ' + Confax.getConfig(message.guild.id).bannedWords.toString()) 5 | }, [], 'Show banned words list', '') 6 | -------------------------------------------------------------------------------- /src/commands/invite.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('invite', 'default', (message) => { 4 | return 'Invite Link: https://discordapp.com/oauth2/authorize?client_id=319545839951544320&permissions=519174&scope=bot' 5 | }, ['invitelink'], 'Get invite link to invite Confax to your server', '[]') 6 | -------------------------------------------------------------------------------- /src/commands/ping.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('ping', 'default', (message, bot) => { 4 | message.channel.send('Pinging...') 5 | .then(m => { 6 | m.edit(`Pong! took \`${m.createdTimestamp - message.createdTimestamp}\`ms`) 7 | }) 8 | }, ['response'], 'Bot Response Test', '[]') 9 | -------------------------------------------------------------------------------- /src/commands/dog.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const request = require('request') 3 | 4 | Confax.registerCommand('dog', 'default', (message) => { 5 | request('http://random.dog/woof', (error, response, body) => { 6 | message.channel.send(`http://random.dog/${body}`).catch(err => console.error(err.stack)) 7 | }) 8 | }, ['doggo'], 'Get a random dog picture', '[]') 9 | -------------------------------------------------------------------------------- /src/commands/moji.js: -------------------------------------------------------------------------------- 1 | const emojis = require('../core/emojis') 2 | const Confax = require('../bot.js') 3 | 4 | Confax.registerCommand('moji', 'default', (message) => { 5 | const index = Math.floor(Math.random() * (emojis.length)) // Math.random() returns a float from 0 - 1. 6 | message.channel.send(emojis[index]) 7 | }, ['emoji', 'emoticon', 'emote'], 'Get a random emote!', '[]') 8 | -------------------------------------------------------------------------------- /src/commands/lenny.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('lenny', 'default', (message, bot) => { 4 | const managePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES') 5 | if (managePerms) { 6 | message.delete() 7 | message.channel.send('( ͡° ͜ʖ ͡°)') 8 | } else { 9 | message.channel.send('( ͡° ͜ʖ ͡°)') 10 | } 11 | }, ['lenny'], 'its lenny ffs', '[]') 12 | -------------------------------------------------------------------------------- /src/commands/cat.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('cat', 'default', (message) => { 4 | const options = { 5 | host: 'aws.random.cat', 6 | path: '/meow' 7 | } 8 | 9 | Confax.getHTTP(options).then(body => { 10 | body = JSON.parse(body) 11 | message.channel.send(body.file).catch(err => console.error(err.stack)) 12 | }) 13 | }, ['kitty'], 'Get a random cat picture', '[]') 14 | -------------------------------------------------------------------------------- /src/services/currency.js: -------------------------------------------------------------------------------- 1 | const currencySymbolMap = require('../core/map.js') 2 | const getSymbolFromCurrency = (currencyCode) => { 3 | if (typeof currencyCode !== 'string') { 4 | return undefined 5 | } 6 | 7 | const code = currencyCode.toUpperCase() 8 | 9 | if (!currencySymbolMap.hasOwnProperty(code)) { 10 | return undefined 11 | } 12 | 13 | return currencySymbolMap[code] 14 | } 15 | module.exports = getSymbolFromCurrency 16 | -------------------------------------------------------------------------------- /src/commands/instructions.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('instructions', 'default', (message, bot) => { 4 | return ('\nSIMPLE INSTRUCTIONS:\n'  + 5 | '```' + 6 | '1. Go to Hastebin/Ghostbin\n' + 7 | '2. Paste your FULL script\n' + 8 | '3. Click save and generate link\n' + 9 | '4. Paste link here.\n' + 10 | '```') 11 | }, ['paste', 'haste', 'ghostbin', 'hastebin'], 'Simple Instuctions to post a link to your code', '[]') 12 | -------------------------------------------------------------------------------- /src/commands/eval.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('eval', 'master', (message, bot) => { 4 | try { 5 | const output = eval(message.content) 6 | if (!message.content.includes('bot.token') || !output.includes(bot.token)) { 7 | return '**Output:** ' + output 8 | } else { 9 | return '**Output:** ' + 'Why-Token-Please-No' 10 | } 11 | } catch (error) { 12 | return '**Error:** ```' + error + '```' 13 | } 14 | }, [], 'Execute code', '') 15 | -------------------------------------------------------------------------------- /src/modules/unflip.js: -------------------------------------------------------------------------------- 1 | /* 2 | Table unflip by Klendi Gocci 3 | This contains other dope things 4 | 18.7.2017 5 | https://github.com/klendigocci 6 | https://twitter.com/klendigocci 7 | 8 | Example: 9 | 10 | Klendi: (╯°□°)╯︵ ┻━┻ 11 | 12 | Subiex(bot): ┬─┬ ノ( ゜-゜ノ) 13 | */ 14 | const Confax = require('../bot.js') 15 | const bot = Confax.bot 16 | 17 | bot.on('message', message => { 18 | if (message.content.includes('(╯°□°)╯︵ ┻━┻')) { 19 | message.channel.send('┬─┬ ノ( ゜-゜ノ)') 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /src/commands/stock.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const { queryFinnhub } = require('../services/finnhub') 3 | 4 | Confax.registerCommand('stock', 'default', (message) => { 5 | const symbols = message.content.split(' ').map((s) => s.toUpperCase()) 6 | if (symbols.length === 0 || symbols[0] === '') { 7 | message.channel.send('Please provide a search term') 8 | return 9 | } 10 | 11 | queryFinnhub(message, symbols[0]) 12 | }, ['stock'], 'Get latest stock exchange price in USD.', '') 13 | -------------------------------------------------------------------------------- /src/modules/startup.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const bot = Confax.bot 3 | 4 | bot.on('ready', () => { 5 | bot.user.setActivity('!help to get started.', { type: 'PLAYING' }) 6 | bot.user.setStatus('online') 7 | console.log('Confax is ready to rumble!') 8 | }) 9 | 10 | bot.on('reconnecting', () => { 11 | bot.user.setGame('!help to get started.', { type: 'PLAYING' }) 12 | bot.user.setStatus('online') 13 | console.log('Confax has reconnected to Discord.') 14 | }) 15 | 16 | bot.login(process.env.BOT_TOKEN) 17 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefix": "!", 3 | "suffix": "!", 4 | "masters": ["202515321402294275", "206092093653909504"], 5 | "botID": "319545839951544320", 6 | "codeElements": [";", "{", "}", ")", "[", "]", ">"], 7 | "repostThreshold": 4, 8 | "codeLang": "csharp", 9 | "selfDestructIn": 5, 10 | "formatBlock": "```", 11 | "autoPost": false, 12 | "emojiName": "FormatMe", 13 | "backupEmojiName": "💻", 14 | "roleMuted": "muted_vampire", 15 | "muteWarningMessage": "The word: `{bannedWord}` is not allowed in this server!", 16 | "bannedWords": [] 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/commands/math.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const math = require('mathjs') 3 | 4 | Confax.registerCommand('math', 'default', (message, bot) => { 5 | let result 6 | try { 7 | result = math.evaluate(message.content) 8 | } catch (error) { 9 | console.log('Failed math calculation ' + message.content + '\nError: ' + e.stack) 10 | return 'Error while evaluating the math expression.' 11 | } finally { 12 | if (isNaN(parseFloat(result))) { 13 | return 'Invalid Calculation Expression' 14 | } else { 15 | return '**Result:** ' + result 16 | } 17 | } 18 | }, ['calculate', 'calc', 'calculator'], 'Calculate a math expression', '') 19 | -------------------------------------------------------------------------------- /src/modules/serverconfigs.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const fs = require('fs') 3 | 4 | Confax.bot.on('guildCreate', (guild) => { 5 | const guildID = guild.id 6 | const path = './guilds/' + guildID + '.json' 7 | fs.writeFileSync(path, JSON.stringify(Confax.config, null, 2)) 8 | guild.owner.send('Thank you for adding Confax to your guild named **' + guild.name + '**.\n\nConfax will use the default configuration.') 9 | }) 10 | 11 | Confax.bot.on('guildDelete', (guild) => { 12 | const guildID = guild.id 13 | const path = './guilds/' + guildID + '.json' 14 | try { fs.unlinkSync(path) } catch (error) { console.log('An error occured: ' + error.stack) } 15 | console.log('Deleted guild config file for ' + guild + '(' + path + ')') 16 | }) 17 | -------------------------------------------------------------------------------- /src/commands/addbannedword.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('addbannedword', 'moderator', (message, bot) => { 4 | const config = Confax.getConfig(message.guild.id) 5 | const word = message.content.toLowerCase() 6 | if (word.length <= 0) { 7 | return 8 | } 9 | if (config.bannedWords.includes(word)) { 10 | message.reply('**' + word + '**' + ' already exists in the banned words list.') 11 | console.log('The banned words list already contains: ' + word) 12 | return 13 | } 14 | 15 | config.bannedWords.push(word) 16 | Confax.setConfig(message.guild.id, config) 17 | message.reply('**' + word + '**' + ' has been added to the banned words list.') 18 | }, ['abw'], 'Add a word to the banned words list', '[word to add]') 19 | -------------------------------------------------------------------------------- /src/commands/readspoiler.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const spoilerMsg = require('./spoiler.js') 3 | 4 | Confax.registerCommand('readspoiler', 'default', (message, bot) => { 5 | let combinedMessage = '**Originally posted by** ' + spoilerMsg.spoilerMsgAuthor + '\n**Content:** ' + spoilerMsg.spoilerMsgContent 6 | if (spoilerMsg.spoilerMsgAttachment !== 'none') { 7 | combinedMessage += '\n**Attachment:** ' + spoilerMsg.spoilerMsgAttachment 8 | } 9 | message.author.send(combinedMessage).then(() => { message.react('👍') }) 10 | .catch((error) => { 11 | message.reply('Please enable DMs to receive the message!') 12 | message.react('😞') 13 | }) 14 | }, ['tellmethesecret', 'sendmemsg'], 'Read latest spoiler (content will be sent via DMs)', '') 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "confax", 3 | "description": "Advanced and modular discord bot written in Discord.JS", 4 | "version": "0.19.0", 5 | "author": "bananaprotocol", 6 | "bugs": { 7 | "url": "https://github.com/bananaprotocol/confax/issues" 8 | }, 9 | "dependencies": { 10 | "discord.js": "^12.5.1", 11 | "dotenv": "^8.6.0", 12 | "mathjs": "^7.6.0", 13 | "request": "^2.88.2" 14 | }, 15 | "devDependencies": { 16 | "standard": "^14.3.4" 17 | }, 18 | "homepage": "https://github.com/bananaprotocol/confax#readme", 19 | "license": "MIT", 20 | "main": "src/bot.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/bananaprotocol/confax.git" 24 | }, 25 | "scripts": { 26 | "start": "node src/bot.js" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/ban.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('ban', 'moderator', (message, bot) => { 4 | const mention = message.mentions.users.first() 5 | const banPerms = message.guild.member(bot.user).hasPermission('BAN_MEMBERS') 6 | if (!mention) { 7 | return 'Please mention a user, that you would like to ban.' 8 | } else { 9 | if (!banPerms) { 10 | return "Confax doesn't have enough permissions to ban any user." 11 | } else { 12 | const bannable = message.guild.member(mention).bannable 13 | if (!bannable) { 14 | return mention + " isn't bannable." 15 | } else { 16 | message.guild.ban(mention) 17 | return mention + ' has been banned.' 18 | } 19 | } 20 | } 21 | }, [], 'Ban a user from the server', '') 22 | -------------------------------------------------------------------------------- /src/commands/removebannedword.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('removebannedword', 'moderator', (message, bot) => { 4 | const config = Confax.getConfig(message.guild.id) 5 | const word = message.content.toLowerCase() 6 | if (word.length <= 0) { 7 | return 8 | } 9 | if (config.bannedWords.includes(word)) { 10 | config.bannedWords = config.bannedWords.filter(w => w !== word) 11 | Confax.setConfig(message.guild.id, config) 12 | message.reply('**' + word + '**' + ' has been removed from the banned words list.') 13 | return 14 | } 15 | message.reply('**' + word + '**' + ' does not exist in the banned words list.') 16 | console.log('The banned words list does not contain: ' + word) 17 | }, ['rbw'], 'Remove a word from the banned words list', '[word to remove]') 18 | -------------------------------------------------------------------------------- /src/commands/kick.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('kick', 'moderator', (message, bot) => { 4 | const mention = message.mentions.users.array()[0] 5 | const kickPerms = message.guild.member(bot.user).hasPermission('KICK_MEMBERS') 6 | if (mention === null) { 7 | return 'Please mention a user, that you would like to kick.' 8 | } else { 9 | if (!kickPerms) { 10 | return "Confax doesn't have enough permissions to kick any user." 11 | } else { 12 | const kickable = message.guild.member(mention).kickable 13 | if (!kickable) { 14 | return mention + " isn't kickable." 15 | } else { 16 | message.guild.member(mention).kick() 17 | return mention + ' has been kicked.' 18 | } 19 | } 20 | } 21 | }, [], 'Kick a user from the server', '') 22 | -------------------------------------------------------------------------------- /src/commands/emptyquestion.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('emptyquestion', 'default', (message, bot) => { 4 | const chnl = (message.guild.channels.find('name', 'unity') != null) 5 | ? message.guild.channels.find('name', 'unity') 6 | : '#unity' 7 | return ('\n**1.** If you need programming related help, use ' + chnl + '\n' + 8 | '**2.** You should ask help for very specific isolated problems. Your "problem" is too broad.\n' + 9 | '__**Example:**__```Hey, I have this character controller, and I implemented a "move" method ' + 10 | 'using the transform from the object, but it doesn\'t move! I know the code is being run because I\'m logging. ' + 11 | 'Here is the code: ```' + 12 | '**3.** You should ask, and then wait.') 13 | }, ['question', 'uselessquestion', 'moreinfo'], 'Instructions on how to properly ask questions (in order to receive as much info as possible)', '[]') 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Confax Change Log (Currently unmaintained) 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/) 7 | 8 | ## [0.4.1] 9 | ### Changed 10 | - Fixed typo in the Code Formatter ([checkforcode.js](src/modules/checkforcode.js)) 11 | 12 | ## [0.4.0] 13 | ### Added 14 | - Code Formatter (C# only for now) 15 | 16 | ## [0.3.1] 17 | ### Security 18 | - Update Node.js version to resolve security issues 19 | 20 | ## [0.3.0] 21 | ### Added 22 | - Dog command 23 | 24 | ### Change 25 | - Rewrote cat command 26 | 27 | ## [0.2.0] 28 | ### Added 29 | - Cat command 30 | 31 | ### Changed 32 | - Renamed guildconfigs.js to serverconfigs.js 33 | 34 | ### Fixed 35 | - README formatting 36 | 37 | ## [0.1.0] 38 | ### Added 39 | - Advanced Help, Command Help, Eval, Invite, Math, Mute, Purge and Uptime Commands 40 | 41 | ## [0.0.1] 42 | ### Added 43 | - Core Setup 44 | - Help, Ban, Kick and Ping Commands 45 | - Module System -------------------------------------------------------------------------------- /src/commands/speak.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('speak', 'moderator', (message, bot) => { 4 | let myMention = message.mentions.users.array()[0] 5 | const myChannel = message.mentions.channels.array()[0] 6 | const deleteMessage = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES') 7 | if (myChannel === undefined) { 8 | if (deleteMessage) message.delete() 9 | return ('Please mention a #channel so I know where to speak!') 10 | } 11 | 12 | let msg = message.content.replace(myChannel.id, '').replace('<#>', '') 13 | const channel = message.guild.channels.find('name', myChannel.name) 14 | 15 | if (myMention !== undefined) { 16 | msg = msg.replace(myMention.id, '').replace('<@>', '') 17 | msg = msg.replace('<@!>', '') // If user has a nickname 18 | } else { myMention = '' } 19 | 20 | if (deleteMessage) message.delete() 21 | if (msg === '') return "The message can't be empty!" 22 | channel.send(myMention + msg) 23 | }, ['talk', 'say'], 'Make the bot speak to a given channel', '[to channel] [message]') 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2021 bananaprotocol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/commands/purge.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('purge', 'moderator', (message, bot) => { 4 | const deletePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES') 5 | const args = message.content.split(' ') 6 | const amount = parseInt(args[0]) 7 | const mentions = message.mentions.users.array() 8 | 9 | if (!deletePerms) { 10 | return "Confax doesn't have enough permissions to delete messages." 11 | } else if (isNaN(amount)) { 12 | return 'Please provide the amount of messages you would like to delete.' 13 | } else if (amount < 1) { 14 | return 'Please provide an amount of messages you would like to delete (has to be above 0).' 15 | } else if (mentions.length === 0) { 16 | message.delete() 17 | message.channel.fetchMessages({ limit: amount }).then(deleteMsgs => message.channel.bulkDelete(deleteMsgs)) 18 | } else { 19 | message.delete() 20 | const deleteMsgs = [] 21 | message.channel.fetchMessages({ limit: amount }).then((value) => { 22 | value = value.array() 23 | for (let i = 0; i < value.length; i++) { 24 | if (mentions.includes(value[i].author)) { 25 | deleteMsgs.push(value[i]) 26 | } 27 | message.channel.bulkDelete(deleteMsgs) 28 | } 29 | }) 30 | } 31 | }, ['deletemessages', 'deletemsgs'], 'Delete any amount of messages by all users or a list of users (max 100)', ' [mentions]') 32 | -------------------------------------------------------------------------------- /src/core/emojis.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | emojis: [ 3 | '( ͡° ͜ʖ ͡°)', 4 | '¯\\_(ツ)_/¯', 5 | 'ʕ•ᴥ•ʔ', 6 | '(▀̿Ĺ̯▀̿ ̿)', 7 | '(ง ͠° ͟ل͜ ͡°)ง', 8 | 'ಠ_ಠ', 9 | "̿'̿'\\̵͇̿̿\\з=( ͠° ͟ʖ ͡°)=ε/̵͇̿̿/'̿̿ ̿ ̿ ̿ ̿ ̿", 10 | '[̲̅$̲̅(̲̅5̲̅)̲̅$̲̅]', 11 | "﴾͡๏̯͡๏﴿ O'RLY?", 12 | '[̲̅$̲̅(̲̅ ͡° ͜ʖ ͡°̲̅)̲̅$̲̅]', 13 | '(ᵔᴥᵔ)', 14 | '(¬‿¬)', 15 | '(☞゚ヮ゚)☞ ☜(゚ヮ゚☜)', 16 | '(づ ̄ ³ ̄)づ', 17 | 'ლ(ಠ益ಠლ)', 18 | 'ಠ╭╮ಠ', 19 | '♪~ ᕕ(ᐛ)ᕗ', 20 | 'ヾ(⌐■_■)ノ♪', 21 | '◉_◉', 22 | '\\ (•◡•) /', 23 | '༼ʘ̚ل͜ʘ̚༽', 24 | '┬┴┬┴┤(・_├┬┴┬┴', 25 | 'ᕦ(ò_óˇ)ᕤ', 26 | '┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻', 27 | '(╯°□°)╯︵( .o.)', 28 | 'ಠ‿↼', 29 | '◔ ⌣ ◔', 30 | '(ノಠ益ಠ)ノ彡┻━┻', 31 | '(☞゚ヮ゚)☞ ☜(゚ヮ゚☜)', 32 | "̿ ̿ ̿'̿'\̵͇̿̿\з=(•_•)=ε/̵͇̿̿/'̿'̿ ̿", 33 | '(;´༎ຶД༎ຶ`)', 34 | '♥‿♥', 35 | 'ᕦ(ò_óˇ)ᕤ', 36 | '(•_•) ( •_•)>⌐■-■ (⌐■_■)', 37 | '⌐╦╦═─ ಠ_ಠ , (¬‿¬)', 38 | '˙ ͜ʟ˙', 39 | ":')", 40 | '(°ロ°)☝', 41 | 'ಠ⌣ಠ', 42 | '(;一_一)', 43 | '( ⚆ _ ⚆ )', 44 | '☜(⌒▽⌒)☞', 45 | "(ʘᗩʘ')", 46 | '¯\\(°_o)/¯', 47 | 'ლ,ᔑ•ﺪ͟͠•ᔐ.ლ', 48 | '(ʘ‿ʘ)', 49 | 'ಠ~ಠ', 50 | 'ಠ_ಥ', 51 | 'ಠ‿↼', 52 | '(>ლ)', 53 | '(ღ˘⌣˘ღ)', 54 | 'ಠoಠ', 55 | 'ರ_ರ', 56 | '◔ ⌣ ◔', 57 | '(✿´‿`)', 58 | 'ب_ب', 59 | '┬─┬ ︵ /(.□. )', 60 | '☼.☼', 61 | '^̮^', 62 | '(>人<)', 63 | '>_>', 64 | '(/) (°,,°) (/)', 65 | '(・.◤)', 66 | '=U', 67 | '~(˘▾˘~)', 68 | '| (• ◡•)| (❍ᴥ❍ʋ)' 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | -------------------------------------------------------------------------------- /src/commands/help.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('help', 'default', (message, bot) => { 4 | const commands = Confax.commands 5 | const cmds = { 6 | master: [], 7 | moderator: [], 8 | default: [], 9 | dm: [] 10 | } 11 | 12 | for (const loopCmdType in commands) { 13 | for (const loopCmd in commands[loopCmdType]) { 14 | cmds[loopCmdType].push(loopCmd) 15 | } 16 | } 17 | 18 | for (const loopCmdType in cmds) { cmds[loopCmdType].sort() } 19 | 20 | const mastercmds = Object.keys(cmds.master).length 21 | const modcmds = Object.keys(cmds.moderator).length 22 | const defaultcmds = Object.keys(cmds.default).length 23 | const dmcmds = Object.keys(cmds.dm).length 24 | 25 | let helpList = '\n' 26 | if (defaultcmds > 0) helpList += 'Default Commands **(' + defaultcmds + ')** ```' + cmds.default.join(' \n') + ' ```\n' 27 | if (dmcmds > 0) helpList += 'DM Commands **(' + dmcmds + ')** ```' + cmds.dm.join('-\n') + ' ```\n' 28 | if (modcmds > 0) helpList += 'Moderator Commands **(' + modcmds + ')** ```' + cmds.moderator.join(' \n') + ' ```\n' 29 | if (mastercmds > 0) helpList += 'Master Commands **(' + mastercmds + ')** ```' + cmds.master.join(' \n') + ' ```\n' 30 | helpList += 'All Commands - **(' + (defaultcmds + dmcmds + modcmds + mastercmds) + ')**' + 31 | '```Use advancedhelp to get an advanced list of all commands or cmdhelp to get a detailed description of one. ```' 32 | return helpList 33 | }, ['cmds', 'commands', 'commandlist'], 'List all commands', '[]') 34 | -------------------------------------------------------------------------------- /src/commands/crypto.js: -------------------------------------------------------------------------------- 1 | /* crypto.js @GlassToeStudio - GlassToeStudio@gmail.com 2 | 3 | 16 September, 2017 4 | https://github.com/GlassToeStudio 5 | http://glasstoestudio.weebly.com/ 6 | https://twitter.com/GlassToeStudio 7 | 8 | ------------------------------------------------------------------------ 9 | Return current price of a given crypto-currency in $US 10 | 11 | The following command is uisng a free API from https://www.cryptocompare.com/ (CC BY-NC 3.0) 12 | The API request can be found here: https://www.cryptocompare.com/api#-api-data-price- 13 | 14 | Usage: 15 | !price (COIN)... 16 | 17 | Example: 18 | !price BTC ETH 19 | 20 | ------ Current exchange rates for 1 BTC ------ 21 | ETH.......... 14.47 22 | 23 | ------------------------------------------------------------------------ 24 | 25 | */ 26 | const Confax = require('../bot.js') 27 | const CryptoComparePrice = require('../services/cryptocompare') 28 | 29 | Confax.registerCommand('crypto', 'default', (message, bot) => { 30 | const symbols = (message.content.split(' ')).map((x) => x.toUpperCase()) 31 | const fromSymbol = (symbols.shift()).replace(/`/ig, '') 32 | // Init variables 33 | if (message.content === '') { 34 | message.channel.send('Please provide a search term') 35 | return 36 | } 37 | 38 | if (symbols.length === 0) symbols.push('USD') 39 | CryptoComparePrice(fromSymbol, symbols, message) 40 | }, ['crypt', 'price'], 'Get latest crypto exchange price in USD, or in other cryptos.', '') 41 | -------------------------------------------------------------------------------- /src/commands/advancedhelp.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('advancedhelp', 'default', (message) => { 4 | const config = Confax.getConfig(message.guild.id) 5 | let helpMsg = '**Advanced Help**\n\n' + 6 | 'All commands are prefixed with: `' + config.prefix + '`\n\n' 7 | 8 | const commands = Confax.commands 9 | 10 | for (const loopCmdType in commands) { 11 | helpMsg += '**~~------~~** __' + loopCmdType.toUpperCase() + ' COMMANDS__ **~~------~~**' 12 | for (const loopCmd in commands[loopCmdType]) { 13 | helpMsg += '```\nCommand: ' + loopCmd 14 | 15 | if (commands[loopCmdType][loopCmd].aliases.length > 0) { 16 | helpMsg += '\nAliases: ' + commands[loopCmdType][loopCmd].aliases.join(', ') 17 | } else { 18 | helpMsg += '\nAliases: N/A' 19 | } 20 | 21 | if (commands[loopCmdType][loopCmd].description) { 22 | helpMsg += '\nDescription: ' + commands[loopCmdType][loopCmd].description 23 | } else { 24 | helpMsg += '\nDescription: N/A' 25 | } 26 | 27 | if (commands[loopCmdType][loopCmd].usage) { 28 | helpMsg += '\nUsage: ' + commands[loopCmdType][loopCmd].usage 29 | } else { 30 | helpMsg += '\nUsage: N/A' 31 | } 32 | 33 | helpMsg += '\n```' 34 | if (helpMsg.length >= 1900) { 35 | message.author.send(helpMsg) 36 | helpMsg = '' 37 | } 38 | } 39 | } 40 | 41 | message.author.send(helpMsg, { split: true }) 42 | return 'I sent you the advanced help list as a DM!' 43 | }, [], 'List advanced information about every registered command', '[]') 44 | -------------------------------------------------------------------------------- /src/commands/cmdhelp.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('cmdhelp', 'default', (message, bot) => { 4 | let helpInfo = '' 5 | const cmd = message.content.split(' ')[0] 6 | let realCmd 7 | let cmdType 8 | const commands = Confax.commands 9 | if (!cmd) return 'Please provide a command to get the information from.' 10 | for (const loopCmdType in commands) { 11 | for (const loopCmd in commands[loopCmdType]) { 12 | if (loopCmd === cmd || commands[loopCmdType][loopCmd].aliases.includes(cmd)) { 13 | realCmd = loopCmd 14 | cmdType = loopCmdType 15 | break 16 | } 17 | } 18 | } 19 | 20 | if (realCmd) { 21 | helpInfo += 'Info on **' + realCmd + '**: ```' + 22 | '\nCommand: ' + realCmd 23 | helpInfo += '\nCommand Type: ' + cmdType 24 | 25 | if (commands[cmdType][realCmd].aliases.length > 0) { 26 | helpInfo += '\nAliases: ' + commands[cmdType][realCmd].aliases.join(', ') 27 | } else { 28 | helpInfo += '\nAliases: N/A' 29 | } 30 | 31 | if (commands[cmdType][realCmd].description) { 32 | helpInfo += '\nDescription: ' + commands[cmdType][realCmd].description 33 | } else { 34 | helpInfo += '\nDescription: N/A' 35 | } 36 | 37 | if (commands[cmdType][realCmd].usage) { 38 | helpInfo += '\nUsage: ' + commands[cmdType][realCmd].usage 39 | } else { 40 | helpInfo += '\nUsage: N/A' 41 | } 42 | helpInfo += '\n```' 43 | } else { 44 | helpInfo = 'Command with the name `' + cmd + '` not found.' 45 | } 46 | return helpInfo 47 | }, ['cmdinfo'], 'List detailed information about a command', '') 48 | -------------------------------------------------------------------------------- /src/commands/spoiler.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | const spoilerMsg = { 4 | spoilerMsg: { 5 | spoilerMsgContent: '*PSST* long form of bike is bichael', 6 | spoilerMsgAuthor: 'Yours truly', 7 | spoilerMsgAttachment: 'none' 8 | } 9 | } 10 | 11 | Confax.registerCommand('spoiler', 'default', (message, bot) => { 12 | if (message.content !== '' || message.attachments.size > 0) { 13 | spoilerMsg.spoilerMsgAuthor = message.author.tag 14 | spoilerMsg.spoilerMsgContent = message.content 15 | if (message.attachments.size > 0) { 16 | // Only first attachment will be added to the spoiler 17 | spoilerMsg.spoilerMsgAttachment = message.attachments.array()[0].url 18 | } else { 19 | spoilerMsg.spoilerMsgAttachment = 'none' 20 | } 21 | message.reply("Oof, let's not spoil this to everyone :wink:") 22 | } else { 23 | message.delete() 24 | return "Do you want me not to spoil silence? :thinking: The message can't be empty!" 25 | } 26 | sendMessageToAuthor(message) 27 | setTimeout(() => message.delete(), 1000) 28 | }, ['hidemsg', 'spoileralert'], 'Hide specific message content from all users. Message can be read with !readspoiler command', '[message]') 29 | 30 | const sendMessageToAuthor = (message) => { 31 | let combinedMessage = '**Here is the last spoiler you saved:** ' + spoilerMsg.spoilerMsgContent 32 | if (spoilerMsg.spoilerMsgAttachment !== 'none') { 33 | combinedMessage += '\n**Attachment:** ' + spoilerMsg.spoilerMsgAttachment 34 | } 35 | message.author.send(combinedMessage) 36 | .catch((error) => { 37 | if (error) { 38 | message.reply('Please enable DMs to receive the message!') 39 | } 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/services/finnhub.js: -------------------------------------------------------------------------------- 1 | const request = require('request') 2 | const Discord = require('discord.js') 3 | const getSymbolFromCurrency = require('./currency') 4 | const apiKey = process.env.FINNHUB_API_KEY 5 | 6 | module.exports = { 7 | queryFinnhub: (message, stock) => { 8 | const url = `https://finnhub.io/api/v1/quote?symbol=${stock}&token=${apiKey}` 9 | return request(url, (error, response, body) => { 10 | if (error) { 11 | message.channel.send(`Error while retrieving stock information ${error.message}`) 12 | return 13 | } 14 | if (response.status === 429) { 15 | message.channel.send('API Limit reached') 16 | return 17 | } 18 | try { 19 | const data = JSON.parse(body) 20 | const embed = makeUpMessage(data, stock) 21 | message.channel.send(embed) 22 | } catch (error) { 23 | message.channel.send(`Error while retrieving stock information ${error.message}`) 24 | } 25 | }) 26 | } 27 | } 28 | 29 | const makeUpMessage = (data, stock, currency = 'USD') => { 30 | let line = '' 31 | let suggestion = '' 32 | if (data.c) { 33 | line = `${getSymbolFromCurrency(currency)}` + ` ${data.c}` 34 | } else { 35 | line = '¯\\_(ツ)_/¯' 36 | suggestion = 'try !price ' + stock 37 | } 38 | const embed = new Discord.MessageEmbed({ 39 | title: 'finnhub.io', 40 | description: '```MarkDown\n' + `#_Exchange rates for 1 ${stock}` + '```' 41 | }) 42 | embed.setTimestamp() 43 | embed.setURL('https://www.finnhub.io') 44 | embed.setColor(586901) 45 | embed.addField(`*** ${currency} **`, '```js\n' + line + '```', true) 46 | if (suggestion.length > 0) { 47 | embed.setFooter(suggestion) 48 | } 49 | return embed 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Confax Avatar](images/avatar.png) 2 | 3 | # Confax · [![npm](https://img.shields.io/npm/v/npm.svg?style=flat-square)](https://www.npmjs.com/package/npm) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) 4 | 5 | Advanced and modular discord bot written in Discord.js 6 | 7 | ## Getting started 8 | 9 | You can invite Confax to your Server by using this [Discord Invite Link](https://discordapp.com/oauth2/authorize?client_id=319545839951544320&permissions=519174&scope=bot) 10 | 11 | If you want to host it yourself, read the [Installation Guide](#installation) 12 | 13 | ## Installation 14 | 15 | Make sure, that you have `git`, `node` and `npm` installed 16 | 17 | Clone the repo using 18 | 19 | git clone https://github.com/bananaprotocol/confax.git 20 | 21 | Install all dependencies using 22 | 23 | npm install 24 | 25 | Start the bot using 26 | 27 | npm start 28 | 29 | You can keep your bot up to date by using 30 | 31 | git pull && npm install 32 | 33 | ## Contributing 34 | 35 | Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before getting started. 36 | 37 | ## Style Guide 38 | 39 | [![standard][standard-image]][standard-url] 40 | 41 | This repository uses [`standard`][standard-url] to maintain code style and consistency, and to avoid style arguments. 42 | 43 | [standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg 44 | [standard-url]: https://github.com/feross/standard 45 | [semistandard-image]: https://cdn.rawgit.com/flet/semistandard/master/badge.svg 46 | [semistandard-url]: https://github.com/Flet/semistandard 47 | 48 | ## License 49 | 50 | [MIT](LICENSE) -------------------------------------------------------------------------------- /src/commands/roshambo.js: -------------------------------------------------------------------------------- 1 | /* roshambo.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com 2 | 3 | 19 July, 2017 4 | https://github.com/GlassToeStudio 5 | http://glasstoestudio.weebly.com/ 6 | https://twitter.com/GlassToeStudio 7 | 8 | ------------------------------------------------------------------------ 9 | Play a game of paper, rock, scissors with the bot. 10 | Input is received from user by the !roshambo command. 11 | Bot randomly selects a play. 12 | Results are return and displayed in the message channel. 13 | 14 | Usage: 15 | !roshambo (rock or r, paper or p, scissors or s) 16 | 17 | Example: 18 | !roshambo scissors 19 | 20 | Rock.. Paper.. Scissors.. SHOOT: 👊 21 | 22 | ------------------------------------------------------------------------ 23 | // 🖖 for later use :D 24 | */ 25 | 26 | const Confax = require('../bot.js') 27 | const allMoves = [ 28 | 'rock', '✊', '🤜', '🤜', '👊', 29 | 'paper', '✋', '🖐', '📄', '📃', '🗒', 30 | 'scissors', '✌', '✂' 31 | ] 32 | const mojiMoves = [' 👊', ' ✋', ' ✌'] 33 | 34 | Confax.registerCommand('roshambo', 'default', (message) => { 35 | const channel = message.guild.channels.find('name', 'chill') 36 | // Command only works in #chill 37 | if (channel != null && channel === message.channel) { 38 | let userMove = message.content.toLowerCase() 39 | userMove = allMoves.indexOf(userMove) >= 0 ? userMove : null 40 | if (userMove == null) { 41 | return '***' + message + '***' + ' is not a valid move.\n\n `Please use: ` ' + allMoves.join(', ') 42 | } else { 43 | message.channel.send('Rock.. Paper.. Scissors.. SHOOT: ' + mojiMoves[Math.floor(Math.random() * 3)]) 44 | } 45 | } 46 | }, allMoves, 'Play Rock-Paper-Scissors! !roshambo rock', '') 47 | -------------------------------------------------------------------------------- /src/commands/urban.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js') 2 | const Confax = require('../bot.js') 3 | 4 | const checkLength = (string, length) => { 5 | if (string.length > length) { 6 | string = string.substring(0, length - 3) + '...' 7 | } 8 | return string 9 | } 10 | 11 | Confax.registerCommand('urban', 'default', (message) => { 12 | const options = { 13 | host: 'api.urbandictionary.com', 14 | path: '/v0/define?term=' + encodeURIComponent(message.content), 15 | headers: { 16 | 'Content-Type': 'application/json' 17 | } 18 | } 19 | 20 | Confax.getHTTP(options).then(body => { 21 | body = JSON.parse(body) 22 | if (body.list.length < 1) { 23 | message.reply('sorry, but there are no definitions for: **' + message.content + '**') 24 | } else { 25 | const embed = new Discord.MessageEmbed() 26 | embed.setTitle('**' + body.list[0].word + '**') 27 | embed.setURL(body.list[0].permalink) 28 | const firstDefinition = body.list[0].definition 29 | const firstExample = body.list[0].example 30 | let secondDefinition 31 | let secondExample 32 | 33 | if (body.list.length > 1) { 34 | secondDefinition = body.list[1].definition 35 | secondExample = body.list[1].example 36 | } 37 | 38 | embed.addField('Definition', checkLength(firstDefinition, 1024)) 39 | if (body.list.length > 1) { 40 | embed.addField('Second Definition', checkLength(secondDefinition, 1024)) 41 | } 42 | embed.addField('**Example**', checkLength(firstExample, 1024)) 43 | if (body.list.length > 1) { 44 | embed.addField('**Second Example**', checkLength(secondExample, 1024)) 45 | } 46 | message.channel.send(embed) 47 | } 48 | }) 49 | }, ['urban-dictionary', 'urban-dict', 'ud'], 'Get a definition of a word from Urban Dictionary', '') 50 | -------------------------------------------------------------------------------- /src/modules/banwords.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const fs = require('fs') 3 | const bot = Confax.bot 4 | const warnedUserIds = Confax.warnedUserIds 5 | 6 | bot.on('message', (message) => { 7 | if (message.author.bot) return 8 | 9 | const config = Confax.getConfig(message.guild.id) 10 | const bannedWords = config.bannedWords 11 | 12 | const lowercaseMessage = message.content.toLowerCase() 13 | for (const word in bannedWords) { 14 | if (lowercaseMessage.split(' ').indexOf(bannedWords[word]) > -1) { 15 | const apiCommands = ['addbannedword', 'abw', 'removebannedword', 'rbw'] 16 | if (apiCommands.some(x => { return lowercaseMessage.indexOf(x) >= 0 })) { 17 | const roles = message.guild.member(message.author.id).roles.array() 18 | const accepted = ['Bot Commander', 'Moderator'] 19 | for (let i = 0; i < roles.length; i++) { 20 | if (accepted.includes(roles[i].name)) { 21 | return 22 | } 23 | } 24 | } 25 | message.reply(config.muteWarningMessage.replace('{bannedWord}', bannedWords[word])) 26 | checkUser(message, config) 27 | } 28 | } 29 | }) 30 | 31 | const checkUser = (message, config) => { 32 | if (warnedUserIds.includes(message.member.user.id)) { 33 | addMutedRole(message, config) 34 | } else { 35 | warnedUserIds.push(message.member.user.id) 36 | } 37 | } 38 | 39 | const addMutedRole = (message, config) => { 40 | const role = message.guild.roles.find(role => role.name === config.roleMuted) 41 | 42 | if (!role) { 43 | console.log('The role ' + config.roleMuted + ' does not exists on this server') 44 | return 45 | } 46 | 47 | message.member.addRole(role) 48 | } 49 | 50 | const persistWarnedUsers = () => { 51 | fs.writeFileSync(`${__dirname}/../core/warneduserids.json`, JSON.stringify(warnedUserIds)) 52 | } 53 | 54 | setInterval(persistWarnedUsers, 10000) 55 | -------------------------------------------------------------------------------- /src/services/cryptocompare.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js') 2 | const getSymbolFromCurrency = require('./currency') 3 | const request = require('request') 4 | 5 | /** 6 | * Return the exchange rate for one crypto currency in terms of other currencies. 7 | * @param {string} fsym 8 | * @param {string[]} tsyms 9 | * @param {string} message 10 | */ 11 | const CryptoComparePrice = (fsym, tsyms, message) => { 12 | // The API call string. 13 | const cryptoComparePrice = 'https://min-api.cryptocompare.com/data/price?fsym=' + fsym + '&tsyms=' + tsyms 14 | 15 | request(cryptoComparePrice, (error, response, body) => { 16 | const suggestion = 'try !stock ' + fsym 17 | const shrug = '¯\\_(ツ)_/¯' 18 | const embed = new Discord.MessageEmbed().setTitle('cryptocompare.com') 19 | .setDescription('```MarkDown\n#_Exchange rates for 1 ' + fsym + '```') 20 | .setURL('https://www.cryptocompare.com/api/') 21 | .setColor(586901) 22 | .setTimestamp() 23 | try { 24 | const responseBody = JSON.parse(body) 25 | for (let sym = 0; sym < tsyms.length; sym++) { 26 | let price = responseBody[tsyms[sym]] 27 | if (price === undefined) { 28 | price = shrug 29 | embed.setFooter(suggestion) 30 | } else if (getSymbolFromCurrency(tsyms[sym]) !== undefined) { 31 | price = ' ' + getSymbolFromCurrency(tsyms[sym]) + ' ' + parseFloat(price).toLocaleString('en-IN', { 32 | maximumSignificantDigits: 10, 33 | minimumFractionDigits: 2 34 | }) 35 | } else { 36 | price = parseFloat(price).toLocaleString('en-IN', { 37 | maximumSignificantDigits: 10, 38 | minimumFractionDigits: 2 39 | }) 40 | } 41 | embed.addField('* ' + tsyms[sym], '```js\n' + price + '```', true) 42 | } 43 | message.channel.send('**' + fsym + '**', { embed }) 44 | } catch (error) { 45 | console.log(error.message) 46 | } 47 | }) 48 | } 49 | 50 | module.exports = CryptoComparePrice 51 | -------------------------------------------------------------------------------- /src/commands/dankmeme.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const Discord = require('discord.js') 3 | const https = require('https') 4 | const url = 'https://www.reddit.com/r/dankmemes.json?limit=100' 5 | 6 | Confax.registerCommand('dankmeme', 'default', (message, bot) => { 7 | https.get(url, (result) => { 8 | let body = '' 9 | result.on('data', (chunk) => { 10 | body += chunk 11 | }) 12 | 13 | result.on('end', () => { 14 | const response = JSON.parse(body) 15 | const index = response.data.children[Math.floor(Math.random() * 99) + 1].data 16 | const subRedditName = index.subreddit_name_prefixed 17 | const title = index.title 18 | const link = 'https://www.reddit.com' + index.permalink 19 | const text = index.selftext 20 | if (index.post_hint !== 'image') { 21 | const textEmbed = new Discord.MessageEmbed() 22 | .setTitle(subRedditName) 23 | .setColor(9384170) 24 | .setDescription(`[${title}](${link})\n\n${text}`) 25 | .setURL(`https://www.reddit.com/${subRedditName}`) 26 | 27 | message.channel.send(textEmbed) 28 | } 29 | const image = index.preview.images[0].source.url 30 | if (index.post_hint !== 'image') { 31 | const textEmbed = new Discord.MessageEmbed() 32 | .setTitle(subRedditName) 33 | .setColor(9384170) 34 | .setDescription(`[${title}](${link})\n\n${text}`) 35 | .setURL(`https://www.reddit.com/${subRedditName}`) 36 | 37 | message.channel.send(textEmbed) 38 | } 39 | const imageEmbed = new Discord.MessageEmbed() 40 | .setTitle(subRedditName) 41 | .setImage(image) 42 | .setColor(9384170) 43 | .setDescription(`[${title}](${link})`) 44 | .setURL(`https://www.reddit.com/${subRedditName}`) 45 | message.channel.send(imageEmbed) 46 | }).on('error', (e) => { 47 | console.log('Got an error: ', e) 48 | }) 49 | }) 50 | }, ['dank', 'meme'], 'Get a random meme from r/dankmemes (potentially NSFW)', '[]') 51 | -------------------------------------------------------------------------------- /src/commands/uptime.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | 3 | Confax.registerCommand('uptime', 'default', (message, bot) => { 4 | const ms = bot.uptime 5 | const cd = 24 * 60 * 60 * 1000 // Calc days 6 | const ch = 60 * 60 * 1000 // Calc hours 7 | const cm = 60 * 1000 // Calc minutes 8 | const cs = 1000 // Calc seconds 9 | let days = Math.floor(ms / cd) 10 | const dms = days * cd // Days, in ms 11 | let hours = Math.floor((ms - dms) / ch) 12 | const hms = hours * ch // Hours, in ms 13 | let minutes = Math.floor((ms - dms - hms) / cm) 14 | const mms = minutes * cm // Minutes, in ms 15 | let seconds = Math.round((ms - dms - hms - mms) / cs) 16 | if (seconds === 60) { 17 | minutes++ // Increase by 1 18 | seconds = 0 19 | } 20 | if (minutes === 60) { 21 | hours++ // Inc by 1 22 | minutes = 0 23 | } 24 | if (hours === 24) { 25 | days++ // Increase by 1 26 | hours = 0 27 | } 28 | const dateStrings = [] 29 | 30 | if (days === 1) { 31 | dateStrings.push('**1** day') 32 | } else if (days > 1) { 33 | dateStrings.push('**' + String(days) + '** days') 34 | } 35 | 36 | if (hours === 1) { 37 | dateStrings.push('**1** hour') 38 | } else if (hours > 1) { 39 | dateStrings.push('**' + String(hours) + '** hours') 40 | } 41 | 42 | if (minutes === 1) { 43 | dateStrings.push('**1** minute') 44 | } else if (minutes > 1) { 45 | dateStrings.push('**' + String(minutes) + '** minutes') 46 | } 47 | 48 | if (seconds === 1) { 49 | dateStrings.push('**1** second') 50 | } else if (seconds > 1) { 51 | dateStrings.push('**' + String(seconds) + '** seconds') 52 | } 53 | 54 | let dateString = '' 55 | for (let i = 0; i < dateStrings.length - 1; i++) { 56 | dateString += dateStrings[i] 57 | dateString += ', ' 58 | } 59 | if (dateStrings.length >= 2) { 60 | dateString = dateString.slice(0, dateString.length - 2) + dateString.slice(dateString.length - 1) 61 | dateString += 'and ' 62 | } 63 | dateString += dateStrings[dateStrings.length - 1] 64 | return '**Uptime:** ' + dateString 65 | }, [], 'View the current uptime of Confax', '[]') 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | ### Vim ### 62 | # swap 63 | [._]*.s[a-v][a-z] 64 | [._]*.sw[a-p] 65 | [._]s[a-v][a-z] 66 | [._]sw[a-p] 67 | # session 68 | Session.vim 69 | # temporary 70 | .netrwhist 71 | *~ 72 | # auto-generated tag files 73 | tags 74 | 75 | ### SublimeText ### 76 | # cache files for sublime text 77 | *.tmlanguage.cache 78 | *.tmPreferences.cache 79 | *.stTheme.cache 80 | 81 | # workspace files are user-specific 82 | *.sublime-workspace 83 | 84 | # project files should be checked into the repository, unless a significant 85 | # proportion of contributors will probably not be using SublimeText 86 | # *.sublime-project 87 | 88 | # sftp configuration file 89 | sftp-config.json 90 | 91 | # Package control specific files 92 | Package Control.last-run 93 | Package Control.ca-list 94 | Package Control.ca-bundle 95 | Package Control.system-ca-bundle 96 | Package Control.cache/ 97 | Package Control.ca-certs/ 98 | Package Control.merged-ca-bundle 99 | Package Control.user-ca-bundle 100 | oscrypto-ca-bundle.crt 101 | bh_unicode_properties.cache 102 | 103 | # Sublime-github package stores a github token in this file 104 | # https://packagecontrol.io/packages/sublime-github 105 | GitHub.sublime-settings 106 | .vscode/settings.json 107 | 108 | # JetBrains user settings 109 | .idea/ 110 | 111 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Code of Conduct 4 | 5 | This project is intended to be a safe, welcoming space for collaboration. All contributors are expected to adhere to the [Contributor Covenant](CODE_OF_CONDUCT.md) code of conduct. Thank you for being kind to each other! 6 | 7 | ## Contributions welcome! 8 | 9 | **Before spending lots of time on something, ask for feedback on your idea first!** 10 | 11 | Please search [issues](../../issues/) and [pull requests](../../pulls/) before adding something new! This helps avoid duplicating efforts and conversations. 12 | 13 | This project welcomes any kind of contribution! Here are a few suggestions: 14 | 15 | - **Ideas**: participate in an issue thread or start your own to have your voice heard. 16 | - **Writing**: contribute your expertise in an area by helping expand the included content. 17 | - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content. 18 | - **Formatting**: help keep content easy to read with consistent formatting. 19 | - **Code**: help maintain and improve the project codebase. 20 | 21 | ## Code Style 22 | 23 | [![standard][standard-image]][standard-url] 24 | 25 | This repository uses [`standard`][standard-url] to maintain code style and consistency, and to avoid style arguments. 26 | 27 | [standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg 28 | [standard-url]: https://github.com/feross/standard 29 | [semistandard-image]: https://cdn.rawgit.com/flet/semistandard/master/badge.svg 30 | [semistandard-url]: https://github.com/Flet/semistandard 31 | 32 | ## Project Governance 33 | 34 | **This is an [OPEN Open Source Project](http://openopensource.org/).** 35 | 36 | Individuals making significant and valuable contributions are given commit access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 37 | 38 | ### Rules 39 | 40 | There are a few basic ground rules for collaborators: 41 | 42 | 1. **No `--force` pushes** or modifying the Git history in any way. 43 | 1. **Non-master branches** ought to be used for ongoing work. 44 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull request** to solicit feedback from other contributors. 45 | 1. Internal pull requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 46 | 1. Contributors should attempt to adhere to the prevailing code style. 47 | 48 | ### Releases 49 | 50 | Declaring formal releases remains the prerogative of the project maintainer. 51 | 52 | ### Changes to this arrangement 53 | 54 | This is an experiment and feedback is welcome! This document may also be subject to pull requests or changes by contributors where you believe you have something valuable to add or change. -------------------------------------------------------------------------------- /src/modules/commands.js: -------------------------------------------------------------------------------- 1 | const Confax = require('../bot.js') 2 | const bot = Confax.bot 3 | const config = Confax.config 4 | const commands = Confax.commands 5 | 6 | bot.on('message', (message) => { 7 | let cmd = null 8 | let cmdType = null 9 | if (message.author.id === config.botID) { 10 | // This is the bot speaking 11 | } else if (message.author.bot) { 12 | // This is the bot speaking 13 | } else if (message.content.indexOf(config.prefix, 0) !== 0) { 14 | // This is not a command (no prefix) 15 | } else { 16 | const userCommand = message.content.split(' ')[0].replace('!', '').toLowerCase() 17 | for (const loopCmdType in commands) { 18 | for (const loopCmd in commands[loopCmdType]) { 19 | if (userCommand.valueOf() === (loopCmd).valueOf()) { 20 | message.content = message.content.replace(config.prefix + userCommand, '') 21 | cmd = loopCmd 22 | cmdType = loopCmdType 23 | break 24 | } else { 25 | const aliases = commands[loopCmdType][loopCmd].aliases 26 | for (let i = 0; i < aliases.length; i++) { 27 | const alias = aliases[i] 28 | if (userCommand.valueOf() === alias.valueOf()) { 29 | message.content = message.content.replace(config.prefix + userCommand, '') 30 | cmd = loopCmd 31 | cmdType = loopCmdType 32 | break 33 | } 34 | } 35 | } 36 | } 37 | } 38 | message.content = message.content.indexOf(' ') === 0 ? message.content.substring(1) : message.content 39 | if (cmd !== null) { 40 | if (cmdType === 'master') { 41 | if (!(config.masters).includes(message.author.id)) { 42 | message.reply("You don't have enough permissions to execute this command.") 43 | return 44 | } 45 | } else if (cmdType === 'moderator') { 46 | const accepted = ['Bot Commander', 'Moderator'] 47 | let isMod 48 | const roles = message.guild.member(message.author.id).roles.cache.array() 49 | for (let i = 0; i < roles.length; i++) { 50 | if (accepted.includes(roles[i].name)) { 51 | isMod = true 52 | break 53 | } 54 | } 55 | if (!isMod) { 56 | message.reply("You don't have enough permissions to execute this command.") 57 | return 58 | } 59 | } else if (cmdType === 'dm') { 60 | if (message.channel.type === 'dm') { 61 | console.log(message.author.username + ' - ' + message.content) 62 | } else { 63 | return 64 | } 65 | } 66 | try { 67 | const result = commands[cmdType][cmd].process(message, bot) 68 | if (result) { 69 | if (cmdType === 'dm') { 70 | message.author.send(result) 71 | } else { 72 | message.reply(result) 73 | } 74 | } 75 | } catch (error) { 76 | console.log('An Error occured: ' + error.stack) 77 | } 78 | } 79 | } 80 | }) 81 | -------------------------------------------------------------------------------- /src/core/map.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | AED: 'د.إ', 3 | AFN: '؋', 4 | ALL: 'L', 5 | AMD: '֏', 6 | ANG: 'ƒ', 7 | AOA: 'Kz', 8 | ARS: '$', 9 | AUD: '$', 10 | AWG: 'ƒ', 11 | AZN: '₼', 12 | BAM: 'KM', 13 | BBD: '$', 14 | BDT: '৳', 15 | BGN: 'лв', 16 | BHD: '.د.ب', 17 | BIF: 'FBu', 18 | BMD: '$', 19 | BND: '$', 20 | BOB: '$b', 21 | BRL: 'R$', 22 | BSD: '$', 23 | BTC: '฿', 24 | BTN: 'Nu.', 25 | BWP: 'P', 26 | BYR: 'p.', 27 | BZD: 'BZ$', 28 | CAD: '$', 29 | CDF: 'FC', 30 | CHF: 'CHF', 31 | CLP: '$', 32 | CNY: '¥', 33 | COP: '$', 34 | CRC: '₡', 35 | CUC: '$', 36 | CUP: '₱', 37 | CVE: '$', 38 | CZK: 'Kč', 39 | DJF: 'Fdj', 40 | DKK: 'kr', 41 | DOGE: 'Ð', 42 | DOP: 'RD$', 43 | DZD: 'دج', 44 | EEK: 'kr', 45 | EGP: '£', 46 | ERN: 'Nfk', 47 | ETB: 'Br', 48 | ETH: 'Ξ', 49 | EUR: '€', 50 | FJD: '$', 51 | FKP: '£', 52 | GBP: '£', 53 | GEL: '₾', 54 | GGP: '£', 55 | GHC: '₵', 56 | GHS: 'GH₵', 57 | GIP: '£', 58 | GMD: 'D', 59 | GNF: 'FG', 60 | GTQ: 'Q', 61 | GYD: '$', 62 | HKD: '$', 63 | HNL: 'L', 64 | HRK: 'kn', 65 | HTG: 'G', 66 | HUF: 'Ft', 67 | IDR: 'Rp', 68 | ILS: '₪', 69 | IMP: '£', 70 | INR: '₹', 71 | IQD: 'ع.د', 72 | IRR: '﷼', 73 | ISK: 'kr', 74 | JEP: '£', 75 | JMD: 'J$', 76 | JOD: 'JD', 77 | JPY: '¥', 78 | KES: 'KSh', 79 | KGS: 'лв', 80 | KHR: '៛', 81 | KMF: 'CF', 82 | KPW: '₩', 83 | KRW: '₩', 84 | KWD: 'KD', 85 | KYD: '$', 86 | KZT: 'лв', 87 | LAK: '₭', 88 | LBP: '£', 89 | LKR: '₨', 90 | LRD: '$', 91 | LSL: 'M', 92 | LTC: 'Ł', 93 | LTL: 'Lt', 94 | LVL: 'Ls', 95 | LYD: 'LD', 96 | MAD: 'MAD', 97 | MDL: 'lei', 98 | MGA: 'Ar', 99 | MKD: 'ден', 100 | MMK: 'K', 101 | MNT: '₮', 102 | MOP: 'MOP$', 103 | MRO: 'UM', 104 | MUR: '₨', 105 | MVR: 'Rf', 106 | MWK: 'MK', 107 | MXN: '$', 108 | MYR: 'RM', 109 | MZN: 'MT', 110 | NAD: '$', 111 | NGN: '₦', 112 | NIO: 'C$', 113 | NOK: 'kr', 114 | NPR: '₨', 115 | NZD: '$', 116 | OMR: '﷼', 117 | PAB: 'B/.', 118 | PEN: 'S/.', 119 | PGK: 'K', 120 | PHP: '₱', 121 | PKR: '₨', 122 | PLN: 'zł', 123 | PYG: 'Gs', 124 | QAR: '﷼', 125 | RMB: '¥', 126 | RON: 'lei', 127 | RSD: 'Дин.', 128 | RUB: '₽', 129 | RWF: 'R₣', 130 | SAR: '﷼', 131 | SBD: '$', 132 | SCR: '₨', 133 | SDG: 'ج.س.', 134 | SEK: 'kr', 135 | SGD: '$', 136 | SHP: '£', 137 | SLL: 'Le', 138 | SOS: 'S', 139 | SRD: '$', 140 | SSP: '£', 141 | STD: 'Db', 142 | SVC: '$', 143 | SYP: '£', 144 | SZL: 'E', 145 | THB: '฿', 146 | TJS: 'SM', 147 | TMT: 'T', 148 | TND: 'د.ت', 149 | TOP: 'T$', 150 | TRL: '₤', 151 | TRY: '₺', 152 | TTD: 'TT$', 153 | TVD: '$', 154 | TWD: 'NT$', 155 | TZS: 'TSh', 156 | UAH: '₴', 157 | UGX: 'USh', 158 | USD: '$', 159 | UYU: '$U', 160 | UZS: 'лв', 161 | VEF: 'Bs', 162 | VND: '₫', 163 | VUV: 'VT', 164 | WST: 'WS$', 165 | XAF: 'FCFA', 166 | XBT: 'Ƀ', 167 | XCD: '$', 168 | XOF: 'CFA', 169 | XPF: '₣', 170 | YER: '﷼', 171 | ZAR: 'R', 172 | ZWD: 'Z$' 173 | } 174 | -------------------------------------------------------------------------------- /src/bot.js: -------------------------------------------------------------------------------- 1 | const Discord = require('discord.js') 2 | const bot = new Discord.Client() 3 | const { readFileSync, readdirSync, writeFileSync } = require('fs') 4 | const config = require('../config.json') 5 | const http = require('http') 6 | const warnedUserIds = require('./core/warneduserids.json') 7 | const dotenv = require('dotenv').config() 8 | 9 | exports.warnedUserIds = warnedUserIds 10 | exports.bot = bot 11 | exports.config = config 12 | exports.commands = { 13 | master: {}, 14 | moderator: {}, 15 | default: {}, 16 | dm: {} 17 | } 18 | 19 | const registerCommand = (name, type, callback, aliases, description, usage) => { 20 | exports.commands[type][name] = { aliases, description, usage, process: callback } 21 | } 22 | 23 | const loadScript = (path, reload) => { 24 | require(path) 25 | if (reload) { 26 | console.log('Reloaded script at ' + path) 27 | } else { 28 | console.log('Script loaded at ' + path) 29 | } 30 | } 31 | 32 | const changeConfig = (guildID, callback) => { 33 | const path = `${__dirname}/guilds/` + guildID + '.json' 34 | const data = JSON.parse(readFileSync(path)) 35 | callback() 36 | writeFileSync(path, JSON.stringify(data, null, 2)) 37 | console.log('Edited config in ' + path) 38 | } 39 | 40 | const getConfig = (guildID) => { 41 | const path = `${__dirname}/guilds/` + guildID + '.json' 42 | let data 43 | try { 44 | data = JSON.parse(readFileSync(path)) 45 | } catch (err) { 46 | setConfig(guildID, config) 47 | data = JSON.parse(readFileSync(path)) 48 | } 49 | try { return data } catch (error) { console.log('An error occurred: ' + error.stack) } 50 | } 51 | 52 | const setConfig = (guildID, config) => { 53 | const path = `${__dirname}/guilds/` + guildID + '.json' 54 | writeFileSync(path, JSON.stringify(config, null, 2)) 55 | } 56 | 57 | const getConfigValue = (guildID, name) => { 58 | const path = `${__dirname}/guilds/` + guildID + '.json' 59 | const data = JSON.parse(readFileSync(path)) 60 | try { return data[name] } catch (error) { console.log('An error occurred: ' + error.stack) } 61 | } 62 | 63 | exports.registerCommand = registerCommand 64 | exports.loadScript = loadScript 65 | exports.changeConfig = changeConfig 66 | exports.getConfigValue = getConfigValue 67 | exports.getConfig = getConfig 68 | exports.setConfig = setConfig 69 | 70 | const commands = readdirSync(`${__dirname}/commands/`) 71 | commands.forEach(script => { 72 | if (script.substring(script.length - 3, script.length) === '.js') { 73 | exports.loadScript(`${__dirname}/commands/` + script) 74 | } 75 | }) 76 | 77 | const modules = readdirSync(`${__dirname}/modules/`) 78 | console.log(modules) 79 | modules.forEach(script => { 80 | if (script.substring(script.length - 3, script.length) === '.js') { 81 | loadScript(`${__dirname}/modules/` + script) 82 | } 83 | }) 84 | 85 | exports.getHTTP = (link) => { 86 | if (!link) return undefined 87 | return new Promise((resolve, reject) => { 88 | let data = '' 89 | const request = http.request(link, res => { 90 | res.on('data', chunk => { 91 | data += chunk 92 | }) 93 | 94 | res.on('end', () => { 95 | resolve(data) 96 | }) 97 | 98 | res.on('error', err => { 99 | reject(err) 100 | }) 101 | }) 102 | request.end() 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /src/commands/unity.js: -------------------------------------------------------------------------------- 1 | /* unity.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com 2 | 3 | 12 September, 2017 4 | https://github.com/GlassToeStudio 5 | http://glasstoestudio.weebly.com/ 6 | https://twitter.com/GlassToeStudio 7 | 8 | ------------------------------------------------------------------------ 9 | Allow a user to input a Unity c# script reference 10 | a link to the Unity documentation online. 11 | 12 | Usage: 13 | !unity 14 | 15 | Example: 16 | !unity Collider 17 | 18 | https://docs.unity3d.com/ScriptReference/Collider 19 | 20 | ------------------------------------------------------------------------ 21 | */ 22 | const Confax = require('../bot.js') 23 | const request = require('request') 24 | const docAddress = 'https://docs.unity3d.com/ScriptReference/' 25 | const TOCAddress = 'https://docs.unity3d.com/ScriptReference/docdata/toc.js' 26 | const notFound = ' was not found. Either it does not exist or it is mispelled.' 27 | 28 | let isFound = false 29 | 30 | Confax.registerCommand('unity', 'default', (message, bot) => { 31 | // let channel = message.guild.channels.find('name', 'vip_members') 32 | // if (channel === null || channel !== message.channel) { return } 33 | isFound = false 34 | const name = message.content.toString() 35 | // Start the request to get the toc js object 36 | request(TOCAddress, (error, response, body) => { 37 | if (error) { return } 38 | // remove var toc = 39 | const tableOfContents = JSON.parse(body.slice(9)) 40 | SearchAll(tableOfContents.children, name, message) 41 | if (!isFound) { 42 | message.channel.send('__**' + name + '**__' + notFound) 43 | // TellDevThisFailed(name, message) 44 | } 45 | }) 46 | }, ['Unity', 'unity3D', 'U3D', 'u3d', 'script', 'ref'], 'Look up Unity terms in the online script reference.', '') 47 | 48 | /** 49 | * Recursive search through each child node of the toc object 50 | * return if search item is found. 51 | * @param {string} tocLevel 52 | * @param {string} name 53 | * @param {string} message 54 | */ 55 | const SearchAll = (tocLevel, name, message) => { 56 | for (let child = 0; child < tocLevel.length; child++) { 57 | if (tocLevel[child].title.toLowerCase() === name.toLowerCase()) { 58 | isFound = true 59 | CheckLink(docAddress + tocLevel[child].link, name, message) 60 | } else if (tocLevel[child].children !== null) SearchAll(tocLevel[child].children, name, message) 61 | } 62 | } 63 | 64 | /** 65 | * @param {string} address 66 | * @param {string} name 67 | * @param {string} message 68 | */ 69 | const CheckLink = (address, name, message) => { 70 | request(address, (error, response, body) => { 71 | if (error) { return false } 72 | console.log('Status code:', response && response.statusCode) 73 | if (response.statusCode.toString() === '404') { 74 | message.channel.send('__**' + name + '**__' + notFound) 75 | // TellDevThisFailed(name, message) 76 | } else { 77 | message.channel.send(address) 78 | } 79 | }) 80 | } 81 | 82 | /** 83 | * Send DM to dev notifying of an error. 84 | * @param {string} name 85 | * @param {string} message 86 | */ 87 | const TellDevThisFailed = (name, message) => { 88 | console.log(message.author.username + ' Tried to search **' + name + '** But it failed.') 89 | const GlassToe = message.client.users.get(DevID) 90 | GlassToe.send(message.author + ' Tried to search __**' + name + '**__ But it failed.') 91 | } 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at thebananaprotocol@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /src/commands/deep.js: -------------------------------------------------------------------------------- 1 | /* 2 | !deep or !funny command by Klendi Gocci :) 3 | */ 4 | 5 | const Confax = require('../bot.js') 6 | 7 | const deep_quotes = [ 8 | "How can mirrors be real if our eyes aren't real?", 9 | "It's true that we don't know what we've got until we lose it, but it's also true that we don't know what we've been missing until it arrives.", 10 | 'Tomorrow is the first day of the rest of your life', 11 | "The ballparks have gotten too crowded. That's why nobody goes to see the game anymore.\"", 12 | 'The similarities between me and my father are different.', 13 | 'You guys pair up in groups of three, then line up in a circle', 14 | "I play football. I'm not trying to be a professor. The tests don't seem to make sense to me, measuring your brain on stuff I haven't been through in school.", 15 | "I'm going to graduate on time, no matter how long it takes.", 16 | "If I did that, I'd be sticking my head in a moose", 17 | "Smoking kills, and if you're killed, you've lost a very important part of your life.", 18 | '"Who in their right mind would ever need more than 640k of ram!? - Bill Gates 1981"', 19 | 'The average woman would rather have beauty than brains, because the average man can see better than he can think.', 20 | 'One of the great things about books is sometimes there are some fantastic pictures', 21 | "Always remember: you're unique, just like everyone else.", 22 | 'The road to success is always under construction.', 23 | "When everything's coming your way, you're in the wrong lane.", 24 | 'Everybody wants to go to heaven, but nobody wants to die.', 25 | "He who laughs last, didn't get it.", 26 | 'Half of the people in the world are below average.', 27 | 'Chuck Norris frequently donates blood to the Red Cross. Just never his own.', 28 | 'Middle age is when your age starts to show around your middle.', 29 | "I am so clever that sometimes I don't understand a single word of what I am saying.", 30 | 'When it comes to thought, some people stop at nothing.', 31 | 'Happiness is having a large, loving, caring, close-knit family in another city.', 32 | "Don't tell me the sky is the limit when there are footprints on the moon.", 33 | 'Why do psychics have to ask you for your name?', 34 | 'I get enough exercise pushing my luck.', 35 | 'The more people I meet, the more I like my dog.', 36 | "There are three kinds of people in this world: those who can count and those who can't.", 37 | 'When life hands you lemons, make lemonade, find the person that life handed vodka to, and have a party.', 38 | 'God created the world, everything else is made in China.', 39 | 'Before you criticize someone, walk a mile in their shoes. That way, you’ll be a mile from them, and you’ll have their shoes.', 40 | 'You never truly understand something unless you can explain it to your grandmother.', 41 | 'Error. No keyboard. Press F1 to continue.', 42 | 'Experience is what you get when you didn’t get what you wanted.', 43 | 'hey occifer i swear to drunk im not as god as you think i am.', 44 | 'Change is good, but dollars are better.', 45 | 'Solution to two of the world’s problem: feed the homeless to the hungry.', 46 | 'When life gives you melons . . . you might be dyslexic.', 47 | 'Those who criticize our generation seem to forget who raised it!', 48 | 'Children in the back seat cause accidents, accidents in the back seat cause children!', 49 | 'How do you know when you are too drunk to drive? When you swerve to miss a tree . . . and then realize it was your air-freshener.', 50 | "Alcohol, what's that? It's not in my vodkabulary, but let me check in whiskypedia.", 51 | 'I solemnly swear that I am up to no good.', 52 | 'An apple a day keeps anyone anyway, if you throw it hard enough.', 53 | "When my boss asked me who is the stupid one, me or him? I told him everyone knows he doesn't hire stupid people.", 54 | 'No matter how smart you are you can never convince someone stupid that they are stupid.', 55 | 'A cop pulled me over and told me "Papers", so I said "Scissors, I win!" and drove off.', 56 | "If you think your boss is stupid, remember: you wouldn't have a job if he was any smarter.", 57 | "How can my feet smell if they don't have a nose?", 58 | 'A cop pulled me over and told me "Papers", so I said "Scissors, I win!" and drove off.', 59 | 'A stupid person laughs three times at a joke; once when everyone else is laughing, a second time when he actually gets the joke, and a third time when he realizes he was laughing without getting the joke at first.', 60 | 'I did not trip and fall. I attacked the floor and I believe I am winning.', 61 | 'You can stop driving me crazy, I can walk from here.', 62 | "The broccoli says 'I look like a small tree', the mushroom says 'I look like an umbrella', the walnut says 'I look like a brain', and the banana says 'Can we please change the subject?'", 63 | 'I know that I am stupid but when I look around me I feel a lot better.', 64 | 'I never apologize. I’m sorry, but that’s just the way I am..', 65 | "I put my phone in airplane mode, but it's not flying!", 66 | 'Today I was a hero. I rescued some beer that was trapped in a bottle.', 67 | 'With great power comes an even greater electricity bill.', 68 | 'A computer once beat me at chess, but it was no match for me at kick boxing.', 69 | "Just because I can't sing doesn't mean that I won't sing.", 70 | 'Stop the earth from spinning, I want to get off!' 71 | ] 72 | 73 | Confax.registerCommand('deep', 'default', (message) => { 74 | const index = Math.floor(Math.random() * (deep_quotes.length)) // Math.random() returns a float from 0 - 1. 75 | message.channel.send(deep_quotes[index]) 76 | }, ['deep', 'funny', 'quote'], 'Get a random funny quote!', '[]') 77 | -------------------------------------------------------------------------------- /src/modules/checkforcode.js: -------------------------------------------------------------------------------- 1 | /* checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com 2 | 3 | 26 May, 2018 4 | https://github.com/GlassToeStudio 5 | http://glasstoestudio.weebly.com/ 6 | https://twitter.com/GlassToeStudio 7 | 8 | ------------------------------------------------------------------------ 9 | Systematically search through Discord comments to find unformatted Code. 10 | 11 | * Search is based in chars in codeElements array. 12 | * Default: [';', '{', '}', ')', '[', ']', '>'] 13 | * Bot will parse the code line by line searching for 14 | * code elements and keep track of which line are code 15 | * and which are plain text. 16 | * The bot will do his best to only format a true code 17 | * block, leaving the plain text alone. Once complete 18 | * the bot will add code block formatting around the 19 | * code block, with the current code Lang 'csharp'. 20 | * The message will be posted anew as formatted code 21 | * and, if possible, the old message will be deleted. 22 | * 23 | * If the unformatted code is posted in a channel with 24 | * 'help' in the tile, then the new message is posted 25 | * there. 26 | * If the message is posted in any other channel, the 27 | * new message will be posted in #programing_help (if it exists) 28 | * If there is no programing_help channel, the message is 29 | * posted in the original channel. 30 | * 31 | * When a new message is posted in a new channel, the user 32 | * is notified that the post has been moved, with a link 33 | * to the channel. The new post in the new channel will 34 | * also mention the user, as an added bonus to help 35 | * the user navigate to the new message. 36 | 37 | EXAMPLE----------------------------------------------------------------- 38 | 39 | This is not code so it is not in the code block. 40 | 41 | [System.Serializable] 42 | using UnityEngine; 43 | 44 | /// 45 | /// Class summary 46 | /// 47 | public class MyClass 48 | { 49 | /// Summary of myFloat. 50 | public float myFloat; 51 | /// Summary of myOtherFloat. 52 | public float myOtherFLoat; 53 | 54 | // Default constructor 55 | public MyClass(float _myFloat, float _myOtherFLoat) 56 | { 57 | this.myFloat = _myFloat; 58 | this.myOtherFLoat = _myOtherFLoat; 59 | } 60 | } 61 | 62 | This is not code so it is not in the code block. 63 | 64 | ------------------------------------------------------------------------ 65 | 66 | Discord Markdown 101 for more formatting guidelines: 67 | https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-?page=4 68 | */ 69 | 70 | const Confax = require('../bot.js') 71 | const bot = Confax.bot 72 | const config = Confax.config 73 | 74 | // Salt to taste (config.json) 75 | const codeElements = config.codeElements 76 | const codeLang = config.codeLang 77 | const repostThreshold = config.repostThreshold 78 | const selfDestructIn = config.selfDestructIn 79 | const formatBlock = config.formatBlock 80 | const autoPost = config.autoPost 81 | let emojiName = config.emojiName 82 | const backupEmojiName = config.backupEmojiName 83 | const timeToReact = 60000 // could be in config 84 | 85 | // Messages 86 | const hereIsYourCode = ' **Here is your formatted code, I hope I did this right.** ' 87 | const helpChannelname = 'programming_help' 88 | const yourUnformattedCode = '`Your unformatted code has been formatted and moved to` ' 89 | 90 | // Variables 91 | let isFormatted = false 92 | let totalLinesOfCode = 0 93 | let hasFirstLine = false 94 | let lastLine = 0 95 | let firstReply 96 | let reactEmoji 97 | 98 | // Lets begin 99 | bot.on('message', message => { 100 | if (message.content.length > 1950) return // This message may be too long to post, once formatted. 101 | if (message.author.bot) { 102 | // Self-destruct message 103 | if (message.content.includes('Your unformatted code')) { 104 | const usr = message.mentions.users.array()[0] 105 | const chnl = (message.guild.channels.find('name', helpChannelname) != null) 106 | ? message.guild.channels.find('name', helpChannelname) 107 | : message.channel 108 | CallNTimes(selfDestructIn, 1000, EditBotMessage, message, chnl, usr) 109 | } 110 | return 111 | } 112 | ParseMessage(message) 113 | }) 114 | 115 | /** 116 | * Loop through each line in message and check for 117 | * code-like characters. If code formatting is found 118 | * return, else keep checking. 119 | * @param {string[]} message 120 | */ 121 | const ParseMessage = (message) => { 122 | InitVariables() 123 | const lines = message.content.split('\n') 124 | for (let i = 0; i < lines.length; i++) { 125 | if (lines[i].search(formatBlock) >= 0) { 126 | isFormatted = true 127 | return 128 | } else { 129 | let line = lines[i].replace(/`/g, '') 130 | lines[i] = line 131 | line = line.trim() 132 | FindCodeElements(i, line, lines) 133 | } 134 | } 135 | if (!isFormatted) CheckMessage(lines, message) 136 | } 137 | 138 | /** 139 | * Check if this is unformatted code, if so listen for emoji reactions or auto post new message. 140 | * @param {string[]} lines 141 | * @param {string} message 142 | */ 143 | const CheckMessage = (lines, message) => { 144 | if (IsBadCode() && !isFormatted) { 145 | lines[lastLine] = FormatLastLine(lines[lastLine]) 146 | if (!autoPost) { // Either listen for a react or just post the new message 147 | reactEmoji = message.guild.emojis.cache.find(emoji => emoji.name === emojiName) 148 | try { 149 | message.react(reactEmoji) 150 | } catch (error) { 151 | reactEmoji = backupEmojiName 152 | emojiName = reactEmoji 153 | message.react(reactEmoji) 154 | } 155 | message.reply(' ⬆ *If this is a block of code, please click the emoji on the message to auto format it, thanks.*') 156 | .then(msg => { 157 | firstReply = msg 158 | msg.delete({ timeout: timeToReact }) 159 | .catch(() => { 160 | console.log('Message is already deleted') 161 | }) 162 | }) 163 | .then(() => { 164 | ListenForReacts(lines, message) 165 | }) 166 | .catch((error) => { 167 | console.log(error) 168 | }) 169 | } else { 170 | CreateNewMessage(lines, message) 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * Checks the last character in a string to see of it matches a code-like character 177 | * @param {number} index 178 | * @param {string} line 179 | * @param {string[]} lines 180 | */ 181 | const FindCodeElements = (index, line, lines) => { 182 | const lineLength = line.length - 1 183 | for (let i = 0; i < codeElements.length; i++) { 184 | if (line.charAt(lineLength).valueOf() === codeElements[i].valueOf() || line.includes('public') || line.includes('class')) { 185 | if (!hasFirstLine) { 186 | lines[index] = FormatFirstLine(lines[index]) 187 | return 188 | } else { 189 | lastLine = index 190 | totalLinesOfCode += 1 191 | return 192 | } 193 | } 194 | } 195 | } 196 | 197 | /** 198 | * Listen for a 'FormatMe' reaction. If so, Create new message. 199 | * @param {string[]} lines 200 | * @param {string[]} message 201 | */ 202 | const ListenForReacts = (lines, message) => { 203 | const filter = (reaction, user) => { 204 | return [emojiName].includes(reaction.emoji.name) && reaction.count > 1 205 | } 206 | message.awaitReactions(filter, { max: 1, time: timeToReact, errors: ['time'] }) 207 | .then(collected => { 208 | const reaction = collected.first() 209 | if (reaction.emoji.name === emojiName) { 210 | firstReply.delete() 211 | .catch(() => { 212 | console.log('Message is already deleted') 213 | }) 214 | CreateNewMessage(lines, message) 215 | } 216 | }) 217 | .catch(() => { 218 | message.reactions.removeAll().catch(error => console.error('Failed to clear reactions: ', error)) 219 | }) 220 | } 221 | 222 | /** 223 | * Recreate the new message with code formatting included, then post it. 224 | * @param {string[]} lines 225 | * @param {string[]} message 226 | */ 227 | const CreateNewMessage = (lines, message) => { 228 | let newMessage = '' 229 | for (let j = 0; j < lines.length; j++) { 230 | newMessage += lines[j] + '\n' 231 | } 232 | PostNewMessage(message, newMessage) 233 | } 234 | 235 | /** 236 | * Post the formatted message in the appropriate channel 237 | * @param {string[]} message 238 | * @param {string} newMessage 239 | */ 240 | const PostNewMessage = (message, newMessage) => { 241 | const channel = message.guild.channels.cache.find(channel => channel.name === helpChannelname) 242 | const isHelp = message.channel.name.indexOf('help') > 0 243 | // Move to new channel 244 | if (channel != null && channel !== message.channel && !isHelp) { 245 | message.reply(yourUnformattedCode + channel + '.' + 246 | '\n\tThis message will self-destruct in *' + selfDestructIn + '* seconds') 247 | channel.reply(hereIsYourCode) 248 | channel.send(newMessage) 249 | // post in same channel 250 | } else { 251 | message.reply(hereIsYourCode) 252 | message.channel.send(newMessage) 253 | } 254 | DeleteOldMessage(message) 255 | } 256 | 257 | /** 258 | * Deletes the old unformatted message if bot has permission 259 | * @param {string} message 260 | */ 261 | const DeleteOldMessage = (message) => { 262 | const managePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES') 263 | if (managePerms) { 264 | message.delete() 265 | } else { 266 | message.channel.send('**`Tell the server\'s owner to grant me permission to delete your old message, thank\'s`** :wink:') 267 | } 268 | } 269 | 270 | /** 271 | * Adds code formatting block start to the first line of code 272 | * @param {string} firstLine 273 | */ 274 | const FormatFirstLine = (firstLine) => { 275 | /* TODO: 276 | What if the first line of code has some regular text at the beginning? 277 | "Here is my code: public int myInt = 0;" 278 | "Here is my code" will also be formatted. 279 | We do not want this. 280 | */ 281 | hasFirstLine = true 282 | return formatBlock + codeLang + '\n' + firstLine 283 | } 284 | 285 | /** 286 | * Add formatting code bock end to the last line of code 287 | * @param {string} lastLine 288 | */ 289 | const FormatLastLine = (lastLine) => { 290 | return lastLine.replace(/`/g, '') + '\n' + formatBlock 291 | } 292 | 293 | /** 294 | * If total line of code is greater than repostThreshold return true 295 | */ 296 | const IsBadCode = () => { 297 | return (totalLinesOfCode >= repostThreshold) 298 | } 299 | 300 | /** 301 | * Initialize variables 302 | */ 303 | const InitVariables = () => { 304 | isFormatted = false 305 | hasFirstLine = false 306 | lastLine = 0 307 | totalLinesOfCode = 0 308 | firstReply = undefined 309 | reactEmoji = '' 310 | } 311 | 312 | /** 313 | * Edits the instruction message once per second, decrementing the time variable by 1 314 | * @param {string} usr 315 | * @param {string[]} message 316 | * @param {string} channel 317 | * @param {number} t 318 | */ 319 | const EditBotMessage = (usr, message, channel, t) => { 320 | message.edit(usr + ', ' + yourUnformattedCode + channel + '.' + 321 | '\n\tThis message will self-destruct in *' + t + '* seconds') 322 | } 323 | 324 | /** 325 | * Call the passed function n times every time step. 326 | * @param {number} n 327 | * @param {number} time 328 | * @param {function} fn 329 | * @param {string[]} msg 330 | * @param {string} chnl 331 | * @param {string} usr 332 | */ 333 | const CallNTimes = (n, time, fn, msg, chnl, usr) => { 334 | function callFn () { 335 | if (--n < 1) { 336 | usr = null 337 | msg.delete() 338 | .then(m => console.log(`Deleted message from ${m.author.name}`)) 339 | .catch(console.error) 340 | return 341 | } 342 | fn(usr, msg, chnl, n) 343 | setTimeout(callFn, time) 344 | } 345 | setTimeout(callFn, time) 346 | } 347 | --------------------------------------------------------------------------------