├── .gitignore ├── BotNeck.plugin.js ├── BotNeckBot ├── api │ ├── BotNeckClient.js │ ├── BotNeckCommand.js │ ├── BotNeckConfig.js │ ├── BotNeckConverter.js │ ├── BotNeckEvent.js │ ├── BotNeckLog.js │ ├── BotNeckModule.js │ ├── BotNeckPresets.js │ ├── DiscordAPI │ │ ├── DiscordChannel.js │ │ ├── DiscordEmbed.js │ │ ├── DiscordGuild.js │ │ ├── DiscordMessage.js │ │ ├── DiscordUser.js │ │ ├── DiscordWebpack.js │ │ └── index.js │ └── v2Api │ │ ├── BotNeckAPI.js │ │ ├── V2Command.js │ │ └── index.js ├── core │ ├── BotNeckBot.js │ ├── CommandManager.js │ ├── ConfigManager.js │ ├── DiscordNetwork.js │ ├── ModuleManager.js │ ├── Sandbox.js │ ├── configParsers │ │ ├── BotNeck.js │ │ └── index.js │ └── loaders │ │ ├── GenericLoader.js │ │ ├── index.js │ │ ├── v2Loader.js │ │ └── v3Loader.js ├── index.js ├── modules │ ├── BotNeckAPI.js │ ├── BotNeckFun │ │ ├── Commands │ │ │ ├── 8ball.js │ │ │ ├── anilist.js │ │ │ ├── avatar.js │ │ │ ├── calculator.js │ │ │ ├── choose.js │ │ │ ├── coinflip.js │ │ │ ├── eff.js │ │ │ ├── embed.js │ │ │ ├── flip.js │ │ │ ├── index.js │ │ │ ├── lenny.js │ │ │ ├── lmgtfy.js │ │ │ ├── love.js │ │ │ ├── mal.js │ │ │ ├── neko.js │ │ │ ├── regional.js │ │ │ ├── roll.js │ │ │ ├── wallpaper.js │ │ │ └── xkcd.js │ │ ├── config.js │ │ └── index.js │ └── BotNeckUtils │ │ ├── Commands │ │ ├── Help.js │ │ ├── Prefix.js │ │ ├── Reload.js │ │ ├── ReloadConfig.js │ │ ├── Remote.js │ │ ├── TextOnly.js │ │ ├── Usage.js │ │ └── index.js │ │ └── index.js └── package.json ├── LICENSE ├── README.md └── old └── backupChat.botneck.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.zip 2 | 3 | BotNeckBot/node_modules 4 | BotNeckBot/package-lock.json -------------------------------------------------------------------------------- /BotNeck.plugin.js: -------------------------------------------------------------------------------- 1 | //META{"name":"BotNeck","website":"https://github.com/AtiLion/BotNeck-Bot","source":"https://github.com/AtiLion/BotNeck-Bot"}*// 2 | 3 | let BotNeckBot = require(BdApi.Plugins.folder + '/BotNeckBot').BotNeckBot; 4 | let BotInstance = null; 5 | 6 | class BotNeck { 7 | getName() { 8 | return BotNeckBot.Name; 9 | } 10 | getDescription() { 11 | return BotNeckBot.Description; 12 | } 13 | getVersion() { 14 | return BotNeckBot.Version; 15 | } 16 | getAuthor() { 17 | return BotNeckBot.Author; 18 | } 19 | 20 | load() { } 21 | 22 | start() { 23 | BotInstance = new BotNeckBot((BdApi && BdApi.version)); 24 | } 25 | stop() { 26 | BotInstance.destroy(); 27 | } 28 | } -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckClient.js: -------------------------------------------------------------------------------- 1 | const BotNeckEvent = require('./BotNeckEvent'); 2 | const { DiscordNetwork } = require('../core/DiscordNetwork'); 3 | const BotNeckBot = require('../core/BotNeckBot'); 4 | const { DiscordMessage, DiscordClientMessage } = require('./DiscordAPI'); 5 | const BotNeckLog = require('./BotNeckLog'); 6 | const { EventEmitter } = require('events'); 7 | 8 | const _onMessageSend = new BotNeckEvent(); 9 | const _onMessageResponse = new BotNeckEvent(); 10 | const _onMessageReceived = new BotNeckEvent(); 11 | const _onWebSocketReceive = new BotNeckEvent(); 12 | 13 | let _lastUserMessage = null; 14 | let _lastBotMessage = null; 15 | 16 | _onMessageResponse.addEventCallback(handleMessageResponse); 17 | function handleMessageResponse(message, isBot) { 18 | if(isBot) _lastBotMessage = message; 19 | else _lastUserMessage = message; 20 | } 21 | 22 | _onMessageReceived.addEventCallback(handleMessageReceive); 23 | function handleMessageReceive(message) { 24 | _lastUserMessage = message; 25 | } 26 | 27 | /** 28 | * Main class for handling BotNeck 29 | * @extends {EventEmitter} 30 | */ 31 | class BotNeckClient extends EventEmitter { 32 | constructor() { super(); } 33 | 34 | /** 35 | * @deprecated Use BotNeckClient.on('messageSend', ...) instead 36 | */ 37 | get onMessageSend() { return _onMessageSend; } 38 | /** 39 | * @deprecated Use BotNeckClient.on('messageResponse', ...) instead 40 | */ 41 | get onMessageResponse() { return _onMessageResponse; } 42 | /** 43 | * @deprecated Use BotNeckClient.on('messageReceived', ...) instead 44 | */ 45 | get onMessageReceived() { return _onMessageReceived; } 46 | /** 47 | * @deprecated Use BotNeckClient.on('websocketReceived', ...) instead 48 | */ 49 | get onWebSocketReceive() { return _onWebSocketReceive; } 50 | 51 | /** 52 | * The version of the bot currently running 53 | * @returns {String} 54 | */ 55 | get botVersion() { return BotNeckBot.Version; } 56 | 57 | /** 58 | * Gets the last message the user has sent 59 | * @returns {DiscordMessage} Last message the user has sent 60 | */ 61 | get getLastUserMessage() { return _lastUserMessage; } 62 | /** 63 | * Gets the last message the bot has sent 64 | * @returns {DiscordMessage} Last message the bot has sent 65 | */ 66 | get getLastBotMessage() { return _lastBotMessage; } 67 | 68 | /** 69 | * Function callback for when the message is sent 70 | * @callback afterMessageFunc 71 | * @param {DiscordMessage} message 72 | * @param {any} promiseOutput 73 | */ 74 | /** 75 | * Waits for the message to be sent and for a give promise to be complete before invoking the given functions 76 | * @param {Promise} forwardPromise A promise that has to be completed before the functions run (null if none) 77 | * @param {...afterMessageFunc} funcs The functions to execute once the promise is done and the message is sent 78 | */ 79 | runAfterMessage(forwardPromise, ...funcs) { 80 | _onMessageResponse.callbackOnce((message, isBot) => { 81 | if(isBot) return; 82 | if(!forwardPromise) { 83 | for(let func of funcs) { 84 | try { func(message, output); } 85 | catch (err) { BotNeckLog.error(err, 'Failed to invoke given functions'); } 86 | } 87 | return; 88 | } 89 | 90 | forwardPromise.then(output => { 91 | for(let func of funcs) { 92 | try { func(message, output); } 93 | catch (err) { BotNeckLog.error(err, 'Failed to invoke given functions'); } 94 | } 95 | }); 96 | }); 97 | } 98 | 99 | /** 100 | * Sends an unauthorized request to Discord's API with the specified data 101 | * @param {String} endpoint The endpoint in Discord's API to send the request to 102 | * @param {String} type The type of request to send (GET, SET, ...) 103 | * @param {any} jsonData The JSON data to send along with the request 104 | * @returns {Promise} The JSON object that gets returned from Discord's API 105 | */ 106 | sendRequest(endpoint, type, jsonData = null) { 107 | return DiscordNetwork.Instance.sendRequest(endpoint, type, jsonData); 108 | } 109 | /** 110 | * Sends an authorized request to Discord's API with the specified data 111 | * @param {String} endpoint The endpoint in Discord's API to send the request to 112 | * @param {String} type The type of request to send (GET, SET, ...) 113 | * @param {any} jsonData The JSON data to send along with the request 114 | * @returns {Promise} The JSON object that gets returned from Discord's API 115 | */ 116 | sendAuthorizedRequest(endpoint, type, jsonData = null) { 117 | return DiscordNetwork.Instance.sendAuthorizedRequest(endpoint, type, jsonData); 118 | } 119 | } 120 | const clientInstance = new BotNeckClient(); 121 | module.exports = clientInstance; 122 | 123 | /** 124 | * Event triggered every time a message is sent 125 | * @event BotNeckClient#messageSend 126 | * @param {DiscordClientMessage} message The client message that is being sent to Discord 127 | * @param {boolean} isBot If the message has been sent by the bot or by the user 128 | */ 129 | /** 130 | * Event triggered every time a message that has been sent gets a response 131 | * @event BotNeckClient#messageResponse 132 | * @param {DiscordMessage} message The message that has been successfully sent to Discord 133 | * @param {boolean} isBot If the message has been sent by the bot or by the user 134 | */ 135 | /** 136 | * Event triggered every time a message is received 137 | * @event BotNeckClient#messageReceived 138 | * @param {DiscordMessage} message The message that the client has received 139 | * @param {boolean} ownMessage Was the message sent by the current user 140 | */ 141 | /** 142 | * Event triggered every time a websocket packet is received 143 | * @event BotNeckClient#websocketReceived 144 | * @param {any} data The websocket data that was received from the server 145 | */ -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckCommand.js: -------------------------------------------------------------------------------- 1 | const { DiscordClientMessage } = require('./DiscordAPI'); 2 | 3 | module.exports = class BotNeckCommand { 4 | /** 5 | * The command that has to be written into after the prefix to execute the command 6 | * @returns {String} 7 | */ 8 | get Command() { return ''; } 9 | /** 10 | * The description of the command 11 | * @returns {String} 12 | */ 13 | get Description() { return ''; } 14 | /** 15 | * The usage of the command 16 | * @returns {String} 17 | */ 18 | get Usage() { return ''; } 19 | /** 20 | * The minimum amount of arguments required for the command to run 21 | * @returns {Number} 22 | */ 23 | get MinimumArguments() { return 0; } 24 | /** 25 | * The aliases for the command 26 | * @returns {[String]} 27 | */ 28 | get Aliases() { return []; } 29 | 30 | /** 31 | * This function gets executed when the command is called 32 | * @param {DiscordClientMessage} message The message the client is trying to send 33 | * @param {any} args The arguments of the command 34 | */ 35 | execute(message, args) {} 36 | 37 | /** 38 | * Gets all of the currently registered commands 39 | * @returns {[BotNeckCommand]} 40 | */ 41 | static get commandList() { 42 | return [...CommandManager.Instance.registeredCommands]; // Duplicate array 43 | } 44 | 45 | /** 46 | * Registers a command to the bot 47 | * @param {BotNeckCommand} instance The command instance to register 48 | */ 49 | static registerCommand(instance) { 50 | CommandManager.Instance.registerCommand(instance); 51 | } 52 | /** 53 | * Unregister a command from the bot 54 | * @param {BotNeckCommand} instance The command instance to unregister 55 | */ 56 | static unregisterCommand(instance) { 57 | CommandManager.Instance.unregisterCommand(instance); 58 | } 59 | 60 | /** 61 | * Gets the number of arguments that were passed to the command 62 | * @param {any} args The arguments passed to the command 63 | * @returns {Number} The number of passed arguments 64 | */ 65 | static getNumberOfArguments(args) { 66 | if(typeof args !== 'object') 67 | return 0; 68 | let counter = 0; 69 | 70 | for(let key in args) 71 | if(!isNaN(key)) 72 | counter++; 73 | return counter; 74 | } 75 | /** 76 | * Combines all the arguments into a string 77 | * @param {any} args The arguments passed to the command 78 | * @returns {String} The combined arguments as string 79 | */ 80 | static getArgumentsAsString(args) { 81 | let input = ''; 82 | 83 | for(let i in args) 84 | if(!isNaN(i)) 85 | input += args[i] + ' '; 86 | return input; 87 | } 88 | } 89 | const CommandManager = require('../core/CommandManager'); -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = class BotNeckConfig { 4 | /** 5 | * Initializes the configuration (only use with super function) 6 | * @param {String} configName The configuration file name 7 | */ 8 | constructor(configName) { 9 | this.configName = configName; 10 | this.config = {}; 11 | } 12 | 13 | /** 14 | * Creates and initializes an instance of the specified configuration 15 | * @param {BotNeckConfig} type The BotNeckConfig custom type to use for the config 16 | * @returns {Promise} The promise that returns the configuration instance 17 | */ 18 | static create(type) { 19 | if(!type || !(type.prototype instanceof BotNeckConfig)) return; 20 | const confInstance = new type(); 21 | const confPath = ConfigManager.Instance.convertNameToPath(confInstance.configName); 22 | 23 | return new Promise((resolve, reject) => { 24 | if(!fs.existsSync(ConfigManager.Instance.configDirectory)) 25 | fs.mkdirSync(ConfigManager.Instance.configDirectory); 26 | if(!fs.existsSync(confPath)) { 27 | ConfigManager.Instance.saveConfiguration(confInstance.configName, confInstance.config) 28 | .then(() => resolve(confInstance)).catch(reject); 29 | return; 30 | } 31 | 32 | ConfigManager.Instance.loadConfiguration(confInstance.configName) 33 | .then(conf => { 34 | confInstance.config = conf; 35 | resolve(confInstance); 36 | }).catch(reject); 37 | }); 38 | } 39 | 40 | /** 41 | * Saves the configuration to the disk 42 | * @returns {Promise} A callback for when the configuration is saved 43 | */ 44 | save() { 45 | return ConfigManager.Instance.saveConfiguration(this.configName, this.config); 46 | } 47 | /** 48 | * Reloads the configuration from the disk 49 | * @returns {Promise} A callback for when the configuration is reloaded 50 | */ 51 | reload() { 52 | const instance = this; 53 | const confPath = ConfigManager.Instance.convertNameToPath(instance.configName); 54 | 55 | return new Promise((resolve, reject) => { 56 | if(!fs.existsSync(ConfigManager.Instance.configDirectory)) 57 | fs.mkdirSync(ConfigManager.Instance.configDirectory); 58 | if(!fs.existsSync(confPath)) { 59 | ConfigManager.Instance.saveConfiguration(confInstance.configName, confInstance.config) 60 | .then(() => resolve()).catch(reject); 61 | return; 62 | } 63 | 64 | ConfigManager.Instance.loadConfiguration(instance.configName) 65 | .then(conf => { 66 | instance.config = conf; 67 | resolve(); 68 | }).catch(reject); 69 | }); 70 | } 71 | } 72 | const ConfigManager = require('../core/ConfigManager'); -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckConverter.js: -------------------------------------------------------------------------------- 1 | const { DiscordEmbed } = require('./DiscordAPI'); 2 | 3 | /** 4 | * Converts an embed into message content 5 | * @param {DiscordEmbed} embed The embed used for converting 6 | * @returns {String} The message content string from embed 7 | */ 8 | function embedToContent(embed) { 9 | let output = ''; 10 | 11 | if(embed.embed.title) output += '**' + embed.Title + '**\n'; // Add title 12 | if(embed.embed.url) output += embed.Url + '\n'; // Add url 13 | if(embed.embed.title || embed.embed.url) output += '\n'; // New line spacing 14 | 15 | if(embed.embed.description) output += '```\n' + embed.Description + '```\n'; // Add description 16 | if(embed.Fields.length > 0) output += '```\n'; 17 | for(let field of embed.Fields) { 18 | output += field.Name + '\n'; // Add field name 19 | output += '\t' + field.Value + '\n'; // Add field value 20 | } 21 | if(embed.Fields.length > 0) output += '```\n\n'; 22 | if(embed.embed.footer) output += embed.Footer.Text + '\n'; // Add footer 23 | 24 | if(embed.embed.timestamp) output += '*' + embed.Timestamp.toDateString() + '*'; // Add timestamp 25 | if(embed.embed.timestamp && embed.embed.author) output += '* | *'; // Add seperator 26 | if(embed.embed.author) output += '*' + embed.Author.Name + '* (' + embed.Author.Url + ')'; // Add author 27 | 28 | return output; 29 | } 30 | 31 | module.exports = { 32 | embedToContent 33 | } -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckEvent.js: -------------------------------------------------------------------------------- 1 | const BotNeckLog = require('./BotNeckLog') 2 | 3 | module.exports = class BotNeckEvent { 4 | constructor() { 5 | this.callbacks = []; 6 | } 7 | 8 | /** 9 | * Calls the specified function when the event is triggered 10 | * @param {Function} func The callback function to call when event is triggered 11 | */ 12 | addEventCallback(func) { 13 | if(this.callbacks.includes(func)) return; 14 | 15 | this.callbacks.push(func); 16 | } 17 | /** 18 | * Removes the specified function from the callback stack 19 | * @param {Function} func The callback function to remove from the stack 20 | */ 21 | removeEventCallback(func) { 22 | if(!this.callbacks.includes(func)) return; 23 | 24 | let index = this.callbacks.indexOf(func); 25 | this.callbacks.splice(index, 1); 26 | } 27 | 28 | /** 29 | * Calls the specified function when the event is triggered and removes the function from callback 30 | * @param {Function} func The callback function to call when event is triggered 31 | */ 32 | callbackOnce(func) { 33 | let handle = (...args) => { 34 | let index = this.callbacks.indexOf(handle); 35 | this.callbacks.splice(index, 1); 36 | 37 | func(...args); 38 | }; 39 | this.callbacks.push(handle); 40 | } 41 | 42 | /** 43 | * Calls all the callbacks on stack of the event 44 | * @param {...any} params The parameters to pass to the callbacks when the event is invoked 45 | */ 46 | invoke(...params) { 47 | for(let cb of [...this.callbacks]) { 48 | try { cb(...params); } 49 | catch (err) { BotNeckLog.error(err, 'Failed to invoke callback', cb); } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckLog.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /** 3 | * Prints a message to the console 4 | * @param {...any} message The message to print into the console 5 | */ 6 | log: function(...message) { 7 | console.log('%c%s%c%s', 'color: DodgerBlue; font-weight: bold;', '[BotNeck Bot] ', '', ...message); 8 | }, 9 | 10 | /** 11 | * Prints a custom message as well as the error stack into the console 12 | * @param {Error} error The error stack that gets printed into the console 13 | * @param {...any} message The custom message that gets printed along side the error 14 | */ 15 | error: function(error, ...message) { 16 | if(message.length > 0) 17 | console.log('%c%s%c%s', 'color: Red; font-weight: bold;', '[BotNeck Bot] ', '', ...message); 18 | console.log(error); 19 | } 20 | } -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckModule.js: -------------------------------------------------------------------------------- 1 | const BotNeckLog = require('./BotNeckLog'); 2 | 3 | module.exports = class BotNeckModule { 4 | /** 5 | * Returns the name of the module 6 | * @returns {(string|null)} The specified name of the module or null 7 | */ 8 | get Name() { return null; } 9 | /** 10 | * Returns the description of the module 11 | * @returns {(string|null)} The specified description for the module or null 12 | */ 13 | get Description() { return null; } 14 | /** 15 | * Returns the version of the module (eg. 1.0.0) 16 | * @returns {(string|null)} The specified version of the module or null 17 | */ 18 | get Version() { return null; } 19 | /** 20 | * Returns the author of the module 21 | * @returns {(string|null)} The specified author of the module or null 22 | */ 23 | get Author() { return null; } 24 | 25 | /** 26 | * Executed when the module is suppose to load 27 | */ 28 | onLoad() {} 29 | /** 30 | * Executed when the module is suppose to unload 31 | */ 32 | onUnload() {} 33 | 34 | /** 35 | * Gets the v3 modules that are loaded 36 | * @returns {[BotNeckModule]} 37 | */ 38 | static get moduleList() { 39 | return ModuleManager.Instance.modules.map(loader => loader.module); 40 | } 41 | 42 | /** 43 | * Gets the modules directory path 44 | * @returns {String} 45 | */ 46 | static get modulesPath() { return ModuleManager.Instance.modulesDirectory; } 47 | 48 | /** 49 | * Loads the specified module using it's name 50 | * @param {String} moduleName The name of the module's file to load 51 | */ 52 | static loadModule(moduleName) { 53 | if(!moduleName || typeof moduleName !== 'string') return; 54 | 55 | ModuleManager.Instance.loadModule(moduleName); 56 | } 57 | /** 58 | * Unloads the specified module instance 59 | * @param {BotNeckMoodule} module The module instance to unload 60 | */ 61 | static unloadModule(module) { 62 | if(!module || !(module instanceof BotNeckModule)) return; 63 | let loader = ModuleManager.Instance.modules.find(mod => mod.module === module); 64 | if(!loader) return; 65 | 66 | ModuleManager.Instance.unloadModule(loader); 67 | } 68 | /** 69 | * Reloads all modules 70 | * @returns {Promise} Promise to indicate when modules have been reloaded 71 | */ 72 | static reloadModules() { 73 | BotNeckLog.log('Reloading modules ...'); 74 | ModuleManager.Instance.unloadModules(); 75 | return ModuleManager.Instance.loadModules(); 76 | } 77 | } 78 | const ModuleManager = require('../core/ModuleManager'); -------------------------------------------------------------------------------- /BotNeckBot/api/BotNeckPresets.js: -------------------------------------------------------------------------------- 1 | const { DiscordEmbed, DiscordClientMessageBase } = require('./DiscordAPI'); 2 | 3 | module.exports = { 4 | /** 5 | * Changes a client message into an error embed 6 | * @param {DiscordClientMessageBase} message The client message to turn into an error embed 7 | */ 8 | createError: function(message, error) { 9 | this.createBase(message, { 10 | Title: 'BotNeck Error', 11 | Description: error, 12 | Color: 0xff6e00 13 | }); 14 | }, 15 | /** 16 | * Changes a client message into an info embed 17 | * @param {DiscordClientMessageBase} message The client message to turn into an info embed 18 | */ 19 | createInfo: function(message, info) { 20 | this.createBase(message, { 21 | Title: 'BotNeck Info', 22 | Description: info 23 | }); 24 | }, 25 | /** 26 | * Changes a client message into the base embed 27 | * @param {DiscordClientMessageBase} message The client message to turn into the base embed 28 | * @param {DiscordEmbed} embed What information to add to the embed 29 | */ 30 | createBase: function(message, embed) { 31 | message.Content = ''; 32 | message.Embed = { 33 | Description: '', 34 | Color: 0x0061ff, 35 | Author: { 36 | Name: 'BotNeck Bot', 37 | Url: 'https://github.com/AtiLion/BotNeck-Bot', 38 | IconUrl: 'https://avatars1.githubusercontent.com/u/20825809?s=460&u=04546a97a5af1295a95eb1ff7a20eb9a9f76a80d&v=4' 39 | }, 40 | Timestamp: new Date() 41 | } 42 | for(let embedKey in embed) 43 | message.Embed[embedKey] = embed[embedKey]; 44 | } 45 | } -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/DiscordChannel.js: -------------------------------------------------------------------------------- 1 | const WebpackModules = require('./DiscordWebpack'); 2 | const BotNeckClient = require('../BotNeckClient'); 3 | 4 | const ChannelStore = WebpackModules.getByProps('getChannel', 'getDMFromUserId'); 5 | const SelectedChannelStore = WebpackModules.getByProps('getLastSelectedChannelId'); 6 | module.exports = class DiscordChannel { 7 | /** 8 | * Creates a wrapper around the raw Discord channel object 9 | * @param {any} channelObject The raw Discord channel object 10 | */ 11 | constructor(channelObject) { 12 | this.discordData = channelObject; 13 | } 14 | 15 | /** 16 | * Gets the ID of the selected Discord channel 17 | * @returns {String} The ID of the selected Discord channel 18 | */ 19 | get Id() { return this.discordData.id; } 20 | 21 | /** 22 | * Creates a DiscordChannel object from a Discord channel's snowflake ID 23 | * @param {Number} id The snowflake ID of the Discord channel 24 | * @returns {Promise} The DiscordChannel object or null if not found 25 | */ 26 | static getFromId(id) { 27 | return new Promise((resolve, reject) => { 28 | const channel = ChannelStore.getChannel(id); 29 | 30 | if(channel) return resolve(new DiscordChannel(channel)); 31 | if(!channel && !DiscordNetwork.Instance) return resolve(null); 32 | 33 | // At least try to get it via request if it isn't cached by the client 34 | BotNeckClient.sendAuthorizedRequest('/channels/' + id, 'GET') 35 | .then(channelObject => { 36 | if(!channelObject.id) return resolve(null); 37 | resolve(new DiscordChannel(channelObject)); 38 | }) 39 | .catch(reject); 40 | }); 41 | } 42 | 43 | /** 44 | * Returns the DiscordChannel object of the current Discord guild 45 | * @returns {Promise} The current guild's DiscordChannel object or null if not found 46 | */ 47 | static get current() { 48 | return this.getFromId(SelectedChannelStore.getChannelId()); 49 | } 50 | } -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/DiscordEmbed.js: -------------------------------------------------------------------------------- 1 | class DiscordEmbed { 2 | /** 3 | * Creates an easy to use embed wrapper 4 | * @param {any} embedObject The raw embed object 5 | */ 6 | constructor(embedObject = null) { 7 | if(!embedObject) embedObject = { type: 'rich' }; 8 | this.embed = embedObject; 9 | } 10 | 11 | /** 12 | * Title of embed 13 | * @returns {String} 14 | */ 15 | get Title() { return this.embed.title; } 16 | /** 17 | * Title of embed 18 | * @param {String} title 19 | */ 20 | set Title(title) { this.embed.title = title; } 21 | 22 | /** 23 | * Type of embed 24 | * @returns {String} 25 | */ 26 | get Type() { return this.embed.type; } 27 | /** 28 | * Type of embed 29 | * @param {String} type 30 | */ 31 | set Type(type) { this.embed.type = type; } 32 | 33 | /** 34 | * Description of embed 35 | * @returns {String} 36 | */ 37 | get Description() { return this.embed.description; } 38 | /** 39 | * Description of embed 40 | * @param {String} description 41 | */ 42 | set Description(description) { this.embed.description = description; } 43 | 44 | /** 45 | * URL of embed 46 | * @returns {String} 47 | */ 48 | get Url() { return this.embed.url; } 49 | /** 50 | * URL of embed 51 | * @param {String} url 52 | */ 53 | set Url(url) { this.embed.url = url; } 54 | 55 | /** 56 | * Timestamp of embed content 57 | * @returns {Date} 58 | */ 59 | get Timestamp() { return new Date(this.embed.timestamp); } 60 | /** 61 | * Timestamp of embed content 62 | * @param {Date} timestamp 63 | */ 64 | set Timestamp(timestamp) { this.embed.timestamp = timestamp.toISOString(); } 65 | 66 | /** 67 | * Color code of the embed 68 | * @returns {Number} 69 | */ 70 | get Color() { return this.embed.color; } 71 | /** 72 | * Color code of the embed 73 | * @param {Number} color 74 | */ 75 | set Color(color) { this.embed.color = color; } 76 | 77 | /** 78 | * Footer information 79 | * @returns {DiscordEmbedFooter} 80 | */ 81 | get Footer() { return new DiscordEmbedFooter(this.embed.footer = this.embed.footer || {}); } 82 | /** 83 | * Footer information 84 | * @param {DiscordEmbedFooter} footer 85 | */ 86 | set Footer(footer) { 87 | if(!footer) delete this.embed.footer; 88 | else if(footer instanceof DiscordEmbedFooter) this.embed.footer = footer.footer; 89 | else { 90 | let dObj = new DiscordEmbedFooter(); 91 | 92 | for(let key in footer) dObj[key] = footer[key]; 93 | this.embed.footer = dObj.footer; 94 | } 95 | } 96 | 97 | /** 98 | * Image information 99 | * @returns {DiscordEmbedImage} 100 | */ 101 | get Image() { return new DiscordEmbedImage(this.embed.image = this.embed.image || {}); } 102 | /** 103 | * Image information 104 | * @param {DiscordEmbedImage} image 105 | */ 106 | set Image(image) { 107 | if(!image) delete this.embed.image; 108 | else if(image instanceof DiscordEmbedImage) this.embed.image = image.image; 109 | else { 110 | let dObj = new DiscordEmbedImage(); 111 | 112 | for(let key in image) dObj[key] = image[key]; 113 | this.embed.image = dObj.image; 114 | } 115 | } 116 | 117 | /** 118 | * Thumbnail information 119 | * @returns {DiscordEmbedThumbnail} 120 | */ 121 | get Thumbnail() { return new DiscordEmbedThumbnail(this.embed.thumbnail = this.embed.thumbnail || {}); } 122 | /** 123 | * Thumbnail information 124 | * @param {DiscordEmbedThumbnail} thumbnail 125 | */ 126 | set Thumbnail(thumbnail) { 127 | if(!thumbnail) delete this.embed.thumbnail; 128 | else if(thumbnail instanceof DiscordEmbedThumbnail) this.embed.thumbnail = thumbnail.thumbnail; 129 | else { 130 | let dObj = new DiscordEmbedThumbnail(); 131 | 132 | for(let key in thumbnail) dObj[key] = thumbnail[key]; 133 | this.embed.thumbnail = dObj.thumbnail; 134 | } 135 | } 136 | 137 | /** 138 | * Video information 139 | * @returns {DiscordEmbedVideo} 140 | */ 141 | get Video() { return new DiscordEmbedVideo(this.embed.video = this.embed.video || {}); } 142 | /** 143 | * Video information 144 | * @param {DiscordEmbedVideo} video 145 | */ 146 | set Video(video) { 147 | if(!video) delete this.embed.video; 148 | else if(video instanceof DiscordEmbedVideo) this.embed.video = video.video; 149 | else { 150 | let dObj = new DiscordEmbedVideo(); 151 | 152 | for(let key in video) dObj[key] = video[key]; 153 | this.embed.video = dObj.video; 154 | } 155 | } 156 | 157 | /** 158 | * Provider information 159 | * @returns {DiscordEmbedProvider} 160 | */ 161 | get Provider() { return new DiscordEmbedProvider(this.embed.provider = this.embed.provider || {}); } 162 | /** 163 | * Provider information 164 | * @param {DiscordEmbedProvider} provider 165 | */ 166 | set Provider(provider) { 167 | if(!provider) delete this.embed.provider; 168 | else if(provider instanceof DiscordEmbedProvider) this.embed.provider = provider.provider; 169 | else { 170 | let dObj = new DiscordEmbedProvider(); 171 | 172 | for(let key in provider) dObj[key] = provider[key]; 173 | this.embed.provider = dObj.provider; 174 | } 175 | } 176 | 177 | /** 178 | * Author information 179 | * @returns {DiscordEmbedAuthor} 180 | */ 181 | get Author() { return new DiscordEmbedAuthor(this.embed.author = this.embed.author || {}); } 182 | /** 183 | * Author information 184 | * @param {DiscordEmbedAuthor} author 185 | */ 186 | set Author(author) { 187 | if(!author) delete this.embed.author; 188 | else if(author instanceof DiscordEmbedAuthor) this.embed.author = author.author; 189 | else { 190 | let dObj = new DiscordEmbedAuthor(); 191 | 192 | for(let key in author) dObj[key] = author[key]; 193 | this.embed.author = dObj.author; 194 | } 195 | } 196 | 197 | /** 198 | * Fields information 199 | * @returns {[DiscordEmbedField]} 200 | */ 201 | get Fields() { 202 | let outFields = []; 203 | 204 | if(!this.embed.fields) return outFields; 205 | for(let field of this.embed.fields) 206 | outFields.push(new DiscordEmbedField(field)); 207 | return outFields; 208 | } 209 | /** 210 | * Fields information 211 | * @param {[DiscordEmbedField]} fields 212 | */ 213 | set Fields(fields) { 214 | if(!fields) return this.embed.fields = []; 215 | let outFields = []; 216 | 217 | for(let field of fields) { 218 | if(!field) continue; 219 | 220 | if(field instanceof DiscordEmbedField) outFields.push(field.field); 221 | else { 222 | let dObj = new DiscordEmbedField(); 223 | 224 | for(let key in field) dObj[key] = field[key]; 225 | outFields.push(dObj.field); 226 | } 227 | } 228 | this.embed.fields = outFields; 229 | } 230 | /** 231 | * Adds field to embed 232 | * @param {String} name The name of the field 233 | * @param {String} value The value of the field 234 | * @param {Boolean} inline Whether or not this field should display inline 235 | */ 236 | addField(name, value, inline = false) { 237 | if(!this.embed.fields) this.embed.fields = []; 238 | 239 | this.embed.fields.push({ name, value, inline }); 240 | } 241 | /** 242 | * Removes a field using an index 243 | * @param {Number} index The index of the field to remove 244 | */ 245 | removeField(index) { 246 | if(!this.embed.fields) return this.embed.fields = []; 247 | 248 | this.embed.fields.splice(index, 1); 249 | } 250 | } 251 | 252 | class DiscordEmbedFooter { 253 | /** 254 | * Creates an easy to use footer wrapper 255 | * @param {any} footerObject The raw footer object 256 | */ 257 | constructor(footerObject = null) { 258 | if(!footerObject) footerObject = {}; 259 | this.footer = footerObject; 260 | } 261 | 262 | /** 263 | * Footer text 264 | * @returns {String} 265 | */ 266 | get Text() { return this.footer.text; } 267 | /** 268 | * Footer text 269 | * @param {String} text 270 | */ 271 | set Text(text) { this.footer.text = text; } 272 | 273 | /** 274 | * Url of footer icon (only supports http(s) and attachments) 275 | * @returns {String} 276 | */ 277 | get IconUrl() { return this.footer.icon_url; } 278 | /** 279 | * Url of footer icon (only supports http(s) and attachments) 280 | * @param {String} icon_url 281 | */ 282 | set IconUrl(icon_url) { this.footer.icon_url = icon_url; } 283 | 284 | /** 285 | * A proxied url of footer icon 286 | * @returns {String} 287 | */ 288 | get ProxyIconUrl() { return this.footer.proxy_icon_url; } 289 | /** 290 | * A proxied url of footer icon 291 | * @param {String} proxy_icon_url 292 | */ 293 | set ProxyIconUrl(proxy_icon_url) { this.footer.proxy_icon_url = proxy_icon_url; } 294 | } 295 | 296 | class DiscordEmbedImage { 297 | /** 298 | * Creates an easy to use image wrapper 299 | * @param {any} imageObject The raw image object 300 | */ 301 | constructor(imageObject = null) { 302 | if(!imageObject) imageObject = {}; 303 | this.image = imageObject; 304 | } 305 | 306 | /** 307 | * Source url of image (only supports http(s) and attachments) 308 | * @returns {String} 309 | */ 310 | get Url() { return this.image.url; } 311 | /** 312 | * Source url of image (only supports http(s) and attachments) 313 | * @param {String} url 314 | */ 315 | set Url(url) { this.image.url = url; } 316 | 317 | /** 318 | * A proxied url of the image 319 | * @returns {String} 320 | */ 321 | get ProxyUrl() { return this.image.proxy_url; } 322 | /** 323 | * A proxied url of the image 324 | * @param {String} proxy_url 325 | */ 326 | set ProxyUrl(proxy_url) { this.image.proxy_url = proxy_url; } 327 | 328 | /** 329 | * Height of image 330 | * @returns {Number} 331 | */ 332 | get Height() { return this.image.height; } 333 | /** 334 | * Height of image 335 | * @param {Number} height 336 | */ 337 | set Height(height) { this.image.height = height; } 338 | 339 | /** 340 | * Width of image 341 | * @returns {Number} 342 | */ 343 | get Width() { return this.image.width; } 344 | /** 345 | * Width of image 346 | * @param {Number} width 347 | */ 348 | set Width(width) { this.image.width = width; } 349 | } 350 | 351 | class DiscordEmbedThumbnail { 352 | /** 353 | * Creates an easy to use thumbnail wrapper 354 | * @param {any} thumbnailObject The raw thumbnail object 355 | */ 356 | constructor(thumbnailObject = null) { 357 | if(!thumbnailObject) thumbnailObject = {}; 358 | this.thumbnail = thumbnailObject; 359 | } 360 | 361 | /** 362 | * Source url of thumbnail (only supports http(s) and attachments) 363 | * @returns {String} 364 | */ 365 | get Url() { return this.thumbnail.url; } 366 | /** 367 | * Source url of thumbnail (only supports http(s) and attachments) 368 | * @param {String} url 369 | */ 370 | set Url(url) { this.thumbnail.url = url; } 371 | 372 | /** 373 | * A proxied url of the thumbnail 374 | * @returns {String} 375 | */ 376 | get ProxyUrl() { return this.thumbnail.proxy_url; } 377 | /** 378 | * A proxied url of the thumbnail 379 | * @param {String} proxy_url 380 | */ 381 | set ProxyUrl(proxy_url) { this.thumbnail.proxy_url = proxy_url; } 382 | 383 | /** 384 | * Height of thumbnail 385 | * @returns {Number} 386 | */ 387 | get Height() { return this.thumbnail.height; } 388 | /** 389 | * Height of thumbnail 390 | * @param {Number} height 391 | */ 392 | set Height(height) { this.thumbnail.height = height; } 393 | 394 | /** 395 | * Width of thumbnail 396 | * @returns {Number} 397 | */ 398 | get Width() { return this.thumbnail.width; } 399 | /** 400 | * Width of thumbnail 401 | * @param {Number} width 402 | */ 403 | set Width(width) { this.thumbnail.width = width; } 404 | } 405 | 406 | class DiscordEmbedVideo { 407 | /** 408 | * Creates an easy to use video wrapper 409 | * @param {any} videoObject The raw video object 410 | */ 411 | constructor(videoObject = null) { 412 | if(!videoObject) videoObject = {}; 413 | this.video = videoObject; 414 | } 415 | 416 | /** 417 | * Source url of video 418 | * @returns {String} 419 | */ 420 | get Url() { return this.video.url; } 421 | /** 422 | * Source url of video 423 | * @param {String} url 424 | */ 425 | set Url(url) { this.video.url = url; } 426 | 427 | /** 428 | * Height of video 429 | * @returns {Number} 430 | */ 431 | get Height() { return this.video.height; } 432 | /** 433 | * Height of video 434 | * @param {Number} height 435 | */ 436 | set Height(height) { this.video.height = height; } 437 | 438 | /** 439 | * Width of video 440 | * @returns {Number} 441 | */ 442 | get Width() { return this.video.width; } 443 | /** 444 | * Width of video 445 | * @param {Number} width 446 | */ 447 | set Width(width) { this.video.width = width; } 448 | } 449 | 450 | class DiscordEmbedProvider { 451 | /** 452 | * Creates an easy to use provider wrapper 453 | * @param {any} providerObject The raw provider object 454 | */ 455 | constructor(providerObject = null) { 456 | if(!providerObject) providerObject = {}; 457 | this.provider = providerObject; 458 | } 459 | 460 | /** 461 | * Name of provider 462 | * @returns {String} 463 | */ 464 | get Name() { return this.provider.name; } 465 | /** 466 | * Name of provider 467 | * @param {String} name 468 | */ 469 | set Name(name) { this.provider.name = name; } 470 | 471 | /** 472 | * Url of provider 473 | * @returns {String} 474 | */ 475 | get Url() { return this.provider.url; } 476 | /** 477 | * Url of provider 478 | * @param {String} url 479 | */ 480 | set Url(url) { this.provider.url = url; } 481 | } 482 | 483 | class DiscordEmbedAuthor { 484 | /** 485 | * Creates an easy to use author wrapper 486 | * @param {any} authorObject The raw author object 487 | */ 488 | constructor(authorObject = null) { 489 | if(!authorObject) authorObject = {}; 490 | this.author = authorObject; 491 | } 492 | 493 | /** 494 | * Name of author 495 | * @returns {String} 496 | */ 497 | get Name() { return this.author.name; } 498 | /** 499 | * Name of author 500 | * @param {String} name 501 | */ 502 | set Name(name) { this.author.name = name; } 503 | 504 | /** 505 | * Url of author 506 | * @returns {String} 507 | */ 508 | get Url() { return this.author.url; } 509 | /** 510 | * Url of author 511 | * @param {String} url 512 | */ 513 | set Url(url) { this.author.url = url; } 514 | 515 | /** 516 | * Url of author icon (only supports http(s) and attachments) 517 | * @returns {String} 518 | */ 519 | get IconUrl() { return this.author.icon_url; } 520 | /** 521 | * Url of author icon (only supports http(s) and attachments) 522 | * @param {String} icon_url 523 | */ 524 | set IconUrl(icon_url) { this.author.icon_url = icon_url; } 525 | 526 | /** 527 | * A proxied url of author icon 528 | * @returns {String} 529 | */ 530 | get ProxyIconUrl() { return this.author.proxy_icon_url; } 531 | /** 532 | * A proxied url of author icon 533 | * @param {String} proxy_icon_url 534 | */ 535 | set ProxyIconUrl(proxy_icon_url) { this.author.proxy_icon_url = proxy_icon_url; } 536 | } 537 | 538 | class DiscordEmbedField { 539 | /** 540 | * Creates an easy to use field wrapper 541 | * @param {any} fieldObject The raw field object 542 | */ 543 | constructor(fieldObject = null) { 544 | if(!fieldObject) fieldObject = {}; 545 | this.field = fieldObject; 546 | } 547 | 548 | /** 549 | * Name of the field 550 | * @returns {String} 551 | */ 552 | get Name() { return this.field.name; } 553 | /** 554 | * Name of the field 555 | * @param {String} name 556 | */ 557 | set Name(name) { this.field.name = name; } 558 | 559 | /** 560 | * Value of the field 561 | * @returns {String} 562 | */ 563 | get Value() { return this.field.value; } 564 | /** 565 | * Value of the field 566 | * @param {String} value 567 | */ 568 | set Value(value) { this.field.value = value; } 569 | 570 | /** 571 | * Whether or not this field should display inline 572 | * @returns {Boolean} 573 | */ 574 | get Inline() { return this.field.inline; } 575 | /** 576 | * Whether or not this field should display inline 577 | * @param {Boolean} inline 578 | */ 579 | set Inline(inline) { this.field.inline = inline; } 580 | } 581 | 582 | module.exports = { 583 | DiscordEmbed, 584 | DiscordEmbedFooter, 585 | DiscordEmbedImage, 586 | DiscordEmbedThumbnail, 587 | DiscordEmbedVideo, 588 | DiscordEmbedProvider, 589 | DiscordEmbedAuthor, 590 | DiscordEmbedField 591 | } -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/DiscordGuild.js: -------------------------------------------------------------------------------- 1 | const WebpackModules = require('./DiscordWebpack'); 2 | const BotNeckClient = require('../BotNeckClient'); 3 | 4 | const GuildStore = WebpackModules.getByProps('getGuild'); 5 | const SelectedGuildStore = WebpackModules.getByProps('getLastSelectedGuildId'); 6 | module.exports = class DiscordGuild { 7 | /** 8 | * Creates a wrapper around the raw Discord guild object 9 | * @param {any} guildObject The raw Discord guild object 10 | */ 11 | constructor(guildObject) { 12 | this.discordData = guildObject; 13 | } 14 | 15 | /** 16 | * Gets the ID of the selected Discord guild 17 | * @returns {String} The ID of the selected Discord guild 18 | */ 19 | get Id() { return this.discordData.id; } 20 | 21 | /** 22 | * Creates a DiscordGuild object from a Discord guild's snowflake ID 23 | * @param {Number} id The snowflake ID of the Discord guild 24 | * @returns {Promise} The DiscordGuild object or null if not found 25 | */ 26 | static getFromId(id) { 27 | return new Promise((resolve, reject) => { 28 | const guild = GuildStore.getGuild(id); 29 | 30 | if(guild) return resolve(new DiscordGuild(guild)); 31 | if(!guild && !DiscordNetwork.Instance) return resolve(null); 32 | 33 | // At least try to get it via request if it isn't cached by the client 34 | BotNeckClient.sendAuthorizedRequest('/guilds/' + id, 'GET') 35 | .then(guildObject => { 36 | if(!guildObject.id) return resolve(null); 37 | resolve(new DiscordGuild(guildObject)); 38 | }) 39 | .catch(reject); 40 | }); 41 | } 42 | 43 | /** 44 | * Returns the DiscordGuild object of the current Discord guild 45 | * @returns {Promise} The current guild's DiscordGuild object or null if not found 46 | */ 47 | static get current() { 48 | return this.getFromId(SelectedGuildStore.getGuildId()); 49 | } 50 | } -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/DiscordMessage.js: -------------------------------------------------------------------------------- 1 | const { DiscordEmbed } = require('./DiscordEmbed'); 2 | 3 | class DiscordClientMessageBase { 4 | /** 5 | * Creates an easy to use client message wrapper 6 | * @param {any} messageObject The raw client message object 7 | */ 8 | constructor(messageObject = null) { 9 | if(!messageObject) messageObject = {}; 10 | this.message = messageObject; 11 | } 12 | 13 | /** 14 | * The message contents (up to 2000 characters) 15 | * @returns {String} 16 | */ 17 | get Content() { return this.message.content; } 18 | /** 19 | * The message contents (up to 2000 characters) 20 | * @param {String} content 21 | */ 22 | set Content(content) { this.message.content = content; } 23 | 24 | /** 25 | * Embedded rich content 26 | * @returns {DiscordEmbed} 27 | */ 28 | get Embed() { return new DiscordEmbed(this.message.embed = this.message.embed || {}); } 29 | /** 30 | * Embedded rich content 31 | * @param {DiscordEmbed} embed 32 | */ 33 | set Embed(embed) { 34 | if(!embed) delete this.message.embed; 35 | else if(embed instanceof DiscordEmbed) this.message.embed = embed.embed; 36 | else { 37 | let dObj = new DiscordEmbed(); 38 | 39 | for(let key in embed) dObj[key] = embed[key]; 40 | this.message.embed = dObj.embed; 41 | } 42 | } 43 | } 44 | class DiscordClientMessage extends DiscordClientMessageBase { 45 | /** 46 | * Creates an easy to use client message wrapper 47 | * @param {any} messageObject The raw client message object 48 | */ 49 | constructor(messageObject = null) { 50 | super(messageObject); 51 | } 52 | 53 | /** 54 | * A nonce that can be used for optimistic message sending 55 | * @returns {String|Number} 56 | */ 57 | get Nonce() { return this.message.nonce; } 58 | /** 59 | * A nonce that can be used for optimistic message sending 60 | * @param {String|Number} nonce 61 | */ 62 | set Nonce(nonce) { this.message.nonce = nonce; } 63 | 64 | /** 65 | * True if this is a TTS message 66 | * @returns {Boolean} 67 | */ 68 | get TTS() { return this.message.tts; } 69 | /** 70 | * True if this is a TTS message 71 | * @param {Boolean} tts 72 | */ 73 | set TTS(tts) { this.message.tts = tts; } 74 | 75 | /** 76 | * The contents of the file being sent 77 | * @returns {String} 78 | */ 79 | get File() { return this.message.file; } 80 | /** 81 | * The contents of the file being sent 82 | * @param {String} file 83 | */ 84 | set File(file) { this.message.file = file; } 85 | } 86 | 87 | class DiscordMessage { 88 | /** 89 | * Creates an easy to use message wrapper 90 | * @param {any} messageObject The raw message object 91 | */ 92 | constructor(messageObject = null) { 93 | if(!messageObject) messageObject = {}; 94 | this.message = messageObject; 95 | } 96 | 97 | /** 98 | * Id of the message 99 | * @returns {String} 100 | */ 101 | get Id() { return this.message.id; } 102 | 103 | /** 104 | * Id of the channel 105 | * @returns {String} 106 | */ 107 | get ChannelId() { return this.message.channel_id; } 108 | 109 | /** 110 | * The message contents (up to 2000 characters) 111 | * @returns {String} 112 | */ 113 | get Content() { return this.message.content; } 114 | 115 | /** 116 | * Embedded rich content 117 | * @returns {DiscordEmbed} 118 | */ 119 | get Embed() { return new DiscordEmbed(this.message.embed = this.message.embed || {}); } 120 | 121 | /** 122 | * True if this is a TTS message 123 | * @returns {Boolean} 124 | */ 125 | get TTS() { return this.message.tts; } 126 | 127 | /** 128 | * A nonce that can be used for optimistic message sending 129 | * @returns {String|Number} 130 | */ 131 | get Nonce() { return this.message.nonce; } 132 | 133 | /** 134 | * Edits the current message 135 | * @param {DiscordClientMessageBase} newMessage The new contents of the message 136 | */ 137 | editMessage(newMessage) { 138 | if(!(newMessage instanceof DiscordClientMessageBase)) { 139 | let dObj = new DiscordClientMessageBase(); 140 | 141 | for(let key in newMessage) dObj[key] = newMessage[key]; 142 | newMessage = dObj; 143 | } 144 | if(!newMessage.message.embed) 145 | newMessage.message.embed = null; 146 | 147 | return new Promise((resolve, reject) => { 148 | BotNeckClient.sendAuthorizedRequest(`/channels/${this.ChannelId}/messages/${this.Id}`, 'PATCH', newMessage.message) 149 | .then(newObj => { 150 | this.message = newObj; 151 | resolve(); 152 | }).catch(reject); 153 | }); 154 | } 155 | } 156 | 157 | module.exports = { DiscordClientMessageBase, DiscordClientMessage, DiscordMessage } 158 | const BotNeckClient = require('../BotNeckClient'); -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/DiscordUser.js: -------------------------------------------------------------------------------- 1 | const WebpackModules = require('./DiscordWebpack'); 2 | const BotNeckClient = require('../BotNeckClient'); 3 | const BotNeckLog = require('../BotNeckLog'); 4 | 5 | const UserStore = WebpackModules.getByProps('getCurrentUser'); 6 | module.exports = class DiscordUser { 7 | /** 8 | * Creates a wrapper around the raw Discord user object 9 | * @param {any} userObject The raw Discord user object 10 | */ 11 | constructor(userObject) { 12 | this.discordData = userObject; 13 | } 14 | 15 | /** 16 | * Gets the ID of the selected Discord user 17 | * @returns {String} The ID of the selected Discord user 18 | */ 19 | get Id() { return this.discordData.id; } 20 | 21 | /** 22 | * Gets the avatar url id of the selected Discord user 23 | * @returns {String} The avatar url id of the selected Discord user 24 | */ 25 | get Avatar() { return this.discordData.avatar; } 26 | 27 | /** 28 | * Gets the username of the selected Discord user 29 | * @returns {String} The username of the selected Discord user 30 | */ 31 | get Username() { return this.discordData.username; } 32 | 33 | /** 34 | * Gets the discriminator of the selected Discord user 35 | * @returns {String} The discriminator of the selected Discord user 36 | */ 37 | get Discriminator() { return this.discordData.discriminator; } 38 | 39 | /** 40 | * Creates a DiscordUser object from a Discord user's snowflake ID 41 | * @param {Number} id The snowflake ID of the Discord user 42 | * @returns {Promise} The DiscordUser object or null if not found 43 | */ 44 | static getFromId(id) { 45 | return new Promise((resolve, reject) => { 46 | const user = UserStore.getUser(id); 47 | 48 | if(user) return resolve(new DiscordUser(user)); 49 | if(!user && !DiscordNetwork.Instance) return resolve(null); 50 | 51 | // At least try to get it via request if it isn't cached by the client 52 | BotNeckClient.sendAuthorizedRequest('/users/' + id, 'GET') 53 | .then(userObject => { 54 | if(!userObject.id) return resolve(null); 55 | resolve(new DiscordUser(userObject)); 56 | }) 57 | .catch(reject); 58 | }); 59 | } 60 | 61 | /** 62 | * Gets the user id out of a mention tag 63 | * @param {String} mention The mention string to convert to the user id 64 | * @returns {Promise} The DiscordUser object or null if not found 65 | */ 66 | static getFromMention(mention) { 67 | if(!mention.startsWith('<@!') || !mention.endsWith('>')) return null; 68 | let userId = mention.substring(3, mention.length - 1); 69 | 70 | return new Promise(((resolve, reject) => { 71 | if(!userId || isNaN(userId)) return reject('Invalid mention string!'); 72 | 73 | this.getFromId(userId).then(resolve).catch(reject); 74 | }).bind(this)); 75 | } 76 | 77 | /** 78 | * Returns the DiscordUser object of the current Discord user 79 | * @returns {DiscordUser} The current user's DiscordUser object or null if not found 80 | */ 81 | static get current() { 82 | const user = UserStore.getCurrentUser(); 83 | return (user ? new DiscordUser(user) : null); 84 | } 85 | } -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/DiscordWebpack.js: -------------------------------------------------------------------------------- 1 | // https://github.com/rauenzi/BDPluginLibrary/blob/master/src/modules/webpackmodules.js 2 | 3 | /** 4 | * Random set of utilities that didn't fit elsewhere. 5 | * @module WebpackModules 6 | * @version 0.0.2 7 | * @author rauenzi 8 | */ 9 | 10 | /** 11 | * Checks if a given module matches a set of parameters. 12 | * @callback module:WebpackModules.Filters~filter 13 | * @param {*} module - module to check 14 | * @returns {boolean} - True if the module matches the filter, false otherwise 15 | */ 16 | 17 | /** 18 | * Filters for use with {@link module:WebpackModules} but may prove useful elsewhere. 19 | */ 20 | class Filters { 21 | /** 22 | * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties. 23 | * @param {Array} props - Array of property names 24 | * @param {module:WebpackModules.Filters~filter} filter - Additional filter 25 | * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties 26 | */ 27 | static byProperties(props, filter = m => m) { 28 | return module => { 29 | const component = filter(module); 30 | if (!component) return false; 31 | for (let p = 0; p < props.length; p++) { 32 | if (module[props[p]] === undefined) return false; 33 | } 34 | return true; 35 | }; 36 | } 37 | 38 | /** 39 | * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties on the object's prototype. 40 | * @param {Array} fields - Array of property names 41 | * @param {module:WebpackModules.Filters~filter} filter - Additional filter 42 | * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties on the object's prototype 43 | */ 44 | static byPrototypeFields(fields, filter = m => m) { 45 | return module => { 46 | const component = filter(module); 47 | if (!component) return false; 48 | if (!component.prototype) return false; 49 | for (let f = 0; f < fields.length; f++) { 50 | if (module.prototype[fields[f]] === undefined) return false; 51 | } 52 | return true; 53 | }; 54 | } 55 | 56 | /** 57 | * Generates a {@link module:WebpackModules.Filters~filter} that filters by a regex. 58 | * @param {RegExp} search - A RegExp to check on the module 59 | * @param {module:WebpackModules.Filters~filter} filter - Additional filter 60 | * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties 61 | */ 62 | static byCode(search, filter = m => m) { 63 | return module => { 64 | const method = filter(module); 65 | if (!method) return false; 66 | let methodString = ""; 67 | try {methodString = method.toString([]);} 68 | catch (err) {methodString = method.toString();} 69 | return methodString.search(search) !== -1; 70 | }; 71 | } 72 | 73 | /** 74 | * Generates a {@link module:WebpackModules.Filters~filter} that filters by strings. 75 | * @param {...String} search - A RegExp to check on the module 76 | * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of strings 77 | */ 78 | static byString(...strings) { 79 | return module => { 80 | let moduleString = ""; 81 | try {moduleString = module.toString([]);} 82 | catch (err) {moduleString = module.toString();} 83 | for (const s of strings) { 84 | if (!moduleString.includes(s)) return false; 85 | } 86 | return true; 87 | }; 88 | } 89 | 90 | /** 91 | * Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties. 92 | * @param {string} name - Name the module should have 93 | * @param {module:WebpackModules.Filters~filter} filter - Additional filter 94 | * @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties 95 | */ 96 | static byDisplayName(name) { 97 | return module => { 98 | return module && module.displayName === name; 99 | }; 100 | } 101 | 102 | /** 103 | * Generates a combined {@link module:WebpackModules.Filters~filter} from a list of filters. 104 | * @param {...module:WebpackModules.Filters~filter} filters - A list of filters 105 | * @returns {module:WebpackModules.Filters~filter} - Combinatory filter of all arguments 106 | */ 107 | static combine(...filters) { 108 | return module => { 109 | return filters.every(filter => filter(module)); 110 | }; 111 | } 112 | } 113 | 114 | module.exports = class WebpackModules { 115 | 116 | static find(filter, first = true) {return this.getModule(filter, first);} 117 | static findAll(filter) {return this.getModule(filter, false);} 118 | static findByUniqueProperties(props, first = true) {return first ? this.getByProps(...props) : this.getAllByProps(...props);} 119 | static findByDisplayName(name) {return this.getByDisplayName(name);} 120 | 121 | /** 122 | * Finds a module using a filter function. 123 | * @param {Function} filter A function to use to filter modules 124 | * @param {Boolean} first Whether to return only the first matching module 125 | * @return {Any} 126 | */ 127 | static getModule(filter, first = true) { 128 | const wrappedFilter = (m) => { 129 | try {return filter(m);} 130 | catch (err) {return false;} 131 | }; 132 | const modules = this.getAllModules(); 133 | const rm = []; 134 | for (const index in modules) { 135 | if (!modules.hasOwnProperty(index)) continue; 136 | const module = modules[index]; 137 | const {exports} = module; 138 | let foundModule = null; 139 | 140 | if (!exports) continue; 141 | if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = exports.default; 142 | if (wrappedFilter(exports)) foundModule = exports; 143 | if (!foundModule) continue; 144 | if (first) return foundModule; 145 | rm.push(foundModule); 146 | } 147 | return first || rm.length == 0 ? undefined : rm; 148 | } 149 | 150 | /** 151 | * Gets the index in the webpack require cache of a specific 152 | * module using a filter. 153 | * @param {Function} filter A function to use to filter modules 154 | * @return {Number|null} 155 | */ 156 | static getIndex(filter) { 157 | const wrappedFilter = (m) => { 158 | try {return filter(m);} 159 | catch (err) {return false;} 160 | }; 161 | const modules = this.getAllModules(); 162 | for (const index in modules) { 163 | if (!modules.hasOwnProperty(index)) continue; 164 | const module = modules[index]; 165 | const exports = module.exports; 166 | let foundModule = null; 167 | 168 | if (!exports) continue; 169 | if (exports.__esModule && exports.default && wrappedFilter(exports.default)) foundModule = exports.default; 170 | if (wrappedFilter(exports)) foundModule = exports; 171 | if (!foundModule) continue; 172 | return index; 173 | } 174 | return null; 175 | } 176 | 177 | /** 178 | * Gets the index in the webpack require cache of a specific 179 | * module that was already found. 180 | * @param {Any} module An already acquired module 181 | * @return {Number|null} 182 | */ 183 | static getIndexByModule(module) { 184 | return this.getIndex(m => m == module); 185 | } 186 | 187 | /** 188 | * Finds all modules matching a filter function. 189 | * @param {Function} filter A function to use to filter modules 190 | */ 191 | static getModules(filter) {return this.getModule(filter, false);} 192 | 193 | /** 194 | * Finds a module by its name. 195 | * @param {String} name The name of the module 196 | * @param {Function} fallback A function to use to filter modules if not finding a known module 197 | * @return {Any} 198 | */ 199 | static getModuleByName(name, fallback) { 200 | /*if (DiscordModules.hasOwnProperty(name)) return DiscordModules[name]; 201 | if (!fallback) return undefined; 202 | const module = this.getModule(fallback, true); 203 | return module ? DiscordModules[name] = module : undefined;*/ 204 | return this.getModule(fallback, true); 205 | } 206 | 207 | /** 208 | * Finds a module by its display name. 209 | * @param {String} name The display name of the module 210 | * @return {Any} 211 | */ 212 | static getByDisplayName(name) { 213 | return this.getModule(Filters.byDisplayName(name), true); 214 | } 215 | 216 | /** 217 | * Finds a module using its code. 218 | * @param {RegEx} regex A regular expression to use to filter modules 219 | * @param {Boolean} first Whether to return the only the first matching module 220 | * @return {Any} 221 | */ 222 | static getByRegex(regex, first = true) { 223 | return this.getModule(Filters.byCode(regex), first); 224 | } 225 | 226 | /** 227 | * Finds a single module using properties on its prototype. 228 | * @param {...string} prototypes Properties to use to filter modules 229 | * @return {Any} 230 | */ 231 | static getByPrototypes(...prototypes) { 232 | return this.getModule(Filters.byPrototypeFields(prototypes), true); 233 | } 234 | 235 | /** 236 | * Finds all modules with a set of properties of its prototype. 237 | * @param {...string} prototypes Properties to use to filter modules 238 | * @return {Any} 239 | */ 240 | static getAllByPrototypes(...prototypes) { 241 | return this.getModule(Filters.byPrototypeFields(prototypes), false); 242 | } 243 | 244 | /** 245 | * Finds a single module using its own properties. 246 | * @param {...string} props Properties to use to filter modules 247 | * @return {Any} 248 | */ 249 | static getByProps(...props) { 250 | return this.getModule(Filters.byProperties(props), true); 251 | } 252 | 253 | /** 254 | * Finds all modules with a set of properties. 255 | * @param {...string} props Properties to use to filter modules 256 | * @return {Any} 257 | */ 258 | static getAllByProps(...props) { 259 | return this.getModule(Filters.byProperties(props), false); 260 | } 261 | 262 | /** 263 | * Finds a single module using a set of strings. 264 | * @param {...String} props Strings to use to filter modules 265 | * @return {Any} 266 | */ 267 | static getByString(...strings) { 268 | return this.getModule(Filters.byString(...strings), true); 269 | } 270 | 271 | /** 272 | * Finds all modules with a set of strings. 273 | * @param {...String} strings Strings to use to filter modules 274 | * @return {Any} 275 | */ 276 | static getAllByString(...strings) { 277 | return this.getModule(Filters.byString(...strings), false); 278 | } 279 | 280 | /** 281 | * Gets a specific module by index of the webpack require cache. 282 | * Best used in combination with getIndex in order to patch a 283 | * specific function. 284 | * 285 | * Note: this gives the **raw** module, meaning the actual module 286 | * is in returnValue.exports. This is done in order to be able 287 | * to patch modules which export a single function directly. 288 | * @param {Number} index Index into the webpack require cache 289 | * @return {Any} 290 | */ 291 | static getByIndex(index) { 292 | return WebpackModules.require.c[index].exports; 293 | } 294 | 295 | /** 296 | * Discord's __webpack_require__ function. 297 | */ 298 | static get require() { 299 | if (this._require) return this._require; 300 | const id = "zl-webpackmodules"; 301 | const __webpack_require__ = window.webpackJsonp.push([[], { 302 | [id]: (module, exports, req) => module.exports = req 303 | }, [[id]]]); 304 | delete __webpack_require__.m[id]; 305 | delete __webpack_require__.c[id]; 306 | return this._require = __webpack_require__; 307 | } 308 | 309 | /** 310 | * Returns all loaded modules. 311 | * @return {Array} 312 | */ 313 | static getAllModules() { 314 | return this.require.c; 315 | } 316 | } -------------------------------------------------------------------------------- /BotNeckBot/api/DiscordAPI/index.js: -------------------------------------------------------------------------------- 1 | const DiscordUser = require('./DiscordUser'); 2 | const DiscordWebpack = require('./DiscordWebpack'); 3 | const { DiscordClientMessageBase, DiscordClientMessage, DiscordMessage } = require('./DiscordMessage'); 4 | const { DiscordEmbed, 5 | DiscordEmbedAuthor, 6 | DiscordEmbedField, 7 | DiscordEmbedFooter, 8 | DiscordEmbedImage, 9 | DiscordEmbedProvider, 10 | DiscordEmbedThumbnail, 11 | DiscordEmbedVideo 12 | } = require('./DiscordEmbed'); 13 | 14 | module.exports = { 15 | DiscordUser, 16 | DiscordWebpack, 17 | DiscordClientMessageBase, 18 | DiscordClientMessage, 19 | DiscordMessage, 20 | DiscordEmbed, 21 | DiscordEmbedAuthor, 22 | DiscordEmbedField, 23 | DiscordEmbedFooter, 24 | DiscordEmbedImage, 25 | DiscordEmbedProvider, 26 | DiscordEmbedThumbnail, 27 | DiscordEmbedVideo 28 | } -------------------------------------------------------------------------------- /BotNeckBot/api/v2Api/BotNeckAPI.js: -------------------------------------------------------------------------------- 1 | const { DiscordUser } = require('../DiscordAPI'); 2 | const BotNeckLog = require('../BotNeckLog'); 3 | const BotNeckCommand = require('../BotNeckCommand'); 4 | const BotNeckClient = require('../BotNeckClient'); 5 | const BotNeckModule = require('../BotNeckModule'); 6 | 7 | module.exports = class BotNeckAPI { 8 | static getCurrentServerId() { return window.location.pathname.split('/')[2]; } 9 | static getCurrentChannelId() { return window.location.pathname.split('/')[3]; } 10 | static getLastUserMessageId() { 11 | let lastMessage = BotNeckClient.getLastUserMessage(); 12 | if(!lastMessage) return null; 13 | 14 | return lastMessage.Id; 15 | } 16 | static getLastBotMessageId() { 17 | let lastMessage = BotNeckClient.getLastBotMessage(); 18 | if(!lastMessage) return null; 19 | 20 | return lastMessage.Id; 21 | } 22 | static getModulesPath() { return BotNeckModule.modulesPath; } 23 | static getCurrentUser(apiKey, callback) { 24 | callback(DiscordUser.current.discordData); 25 | } 26 | static getTargetUser(apiKey, userId, callback) { 27 | DiscordUser.getFromId(userId) 28 | .then(user => { callback(user.discordData) }) 29 | .catch(err => { BotNeckLog.error(err, 'Failed to get target user!'); callback(null); }) 30 | } 31 | 32 | static setAuthHeader(req, apiKey) { 33 | req.escalateAuthorization = true; 34 | return true; 35 | } 36 | static nextMessagePost(func) { 37 | BotNeckClient.onMessageResponse.callbackOnce(() => { 38 | try { func(); } 39 | catch (err) { BotNeckLog.error(err, 'Failed to invoke message post function!'); } 40 | }); 41 | } 42 | 43 | static generateError(error) { 44 | return { 45 | title: 'BotNeck Error', 46 | type: 'rich', 47 | description: error, 48 | color: 0xff6e00 49 | } 50 | } 51 | static getArgumentNumber(args) { 52 | return BotNeckCommand.getNumberOfArguments(args); 53 | } 54 | static getArgumentsAsString(args) { 55 | return BotNeckCommand.getArgumentsAsString(args); 56 | } 57 | static getMentionUserId(mention) { 58 | if(!mention.startsWith('<@!') || !mention.endsWith('>')) return null; 59 | 60 | return mention.substring(3, mention.length - 1); 61 | } 62 | } -------------------------------------------------------------------------------- /BotNeckBot/api/v2Api/V2Command.js: -------------------------------------------------------------------------------- 1 | const BotNeckCommand = require('../BotNeckCommand'); 2 | 3 | module.exports = class v2Command extends BotNeckCommand { 4 | constructor(command, description, usage, exec) { 5 | super(); 6 | 7 | this.command = command; 8 | this.description = description; 9 | this.usage = usage; 10 | 11 | this.exec = exec; 12 | } 13 | 14 | get Command() { return this.command; } 15 | get Description() { return this.description; } 16 | get Usage() { return this.usage; } 17 | 18 | execute(message, args) { this.exec(message.message, args); } 19 | } -------------------------------------------------------------------------------- /BotNeckBot/api/v2Api/index.js: -------------------------------------------------------------------------------- 1 | const { getModulesPath } = require('./BotNeckAPI'); 2 | const BotNeckAPI = require('./BotNeckAPI'); 3 | const v2Command = require('./V2Command'); 4 | 5 | module.exports = { BotNeckAPI, v2Command }; -------------------------------------------------------------------------------- /BotNeckBot/core/BotNeckBot.js: -------------------------------------------------------------------------------- 1 | const {} = require('../modules/BotNeckAPI'); // Cache BotNeckAPI 2 | const BotNeckLog = require('../api/BotNeckLog'); 3 | const ConfigManager = require('./ConfigManager'); 4 | const { DiscordNetwork, DiscordNetworkCleanup } = require('./DiscordNetwork'); 5 | const ModuleManager = require('./ModuleManager'); 6 | const CommandManager = require('./CommandManager'); 7 | const BotNeckClient = require('../api/BotNeckClient'); 8 | const { DiscordClientMessageBase, DiscordClientMessage, DiscordMessage, DiscordUser } = require('../api/DiscordAPI'); 9 | const { BotNeckParser } = require('./configParsers'); 10 | const BotNeckConfig = require('../api/BotNeckConfig'); 11 | const { embedToContent } = require('../api/BotNeckConverter'); 12 | 13 | /** 14 | * @type {ConfigManager} 15 | */ 16 | let _configManager; 17 | /** 18 | * @type {DiscordNetwork} 19 | */ 20 | let _discordNetwork; 21 | /** 22 | * @type {CommandManager} 23 | */ 24 | let _commandManager; 25 | /** 26 | * @type {ModuleManager} 27 | */ 28 | let _moduleManager; 29 | 30 | module.exports = class BotNeckBot { 31 | constructor(isSandboxed) { 32 | const currentUser = DiscordUser.current; 33 | 34 | window.BotNeck = window.BotNeck || { 35 | isSandboxed: isSandboxed 36 | }; 37 | 38 | _configManager = new ConfigManager(); 39 | BotNeckConfig.create(BotNeckParser) 40 | .then(() => { 41 | const parsedConfig = BotNeckParser.Instance; // Just for the type declaration 42 | 43 | _discordNetwork = new DiscordNetwork(); 44 | _discordNetwork.onRequestSent = (requestJson, isBotRequest) => { 45 | if(requestJson.content === null) return; 46 | BotNeckClient.emit('messageSend', new DiscordClientMessage(requestJson), isBotRequest); 47 | BotNeckClient.onMessageSend.invoke(new DiscordClientMessage(requestJson), isBotRequest); 48 | } 49 | _discordNetwork.onResponseReceived = (responseJson, isBotRequest) => { 50 | if(!responseJson.id || !responseJson.author) return; 51 | BotNeckClient.emit('messageResponse', new DiscordMessage(responseJson), isBotRequest); 52 | BotNeckClient.onMessageResponse.invoke(new DiscordMessage(responseJson), isBotRequest); 53 | } 54 | _discordNetwork.onWSReceived = (wsJson) => { 55 | if(!wsJson.d) return; // Make sure it's valid 56 | 57 | if(wsJson.t === 'MESSAGE_CREATE') { 58 | BotNeckClient.emit('messageReceived', new DiscordMessage(wsJson.d), (wsJson.d.author && currentUser.Id === wsJson.d.author.id)); 59 | if(wsJson.d.author && currentUser.Id === wsJson.d.author.id) 60 | BotNeckClient.onMessageReceived.invoke(new DiscordMessage(wsJson.d)); 61 | } 62 | BotNeckClient.emit('websocketReceived', wsJson); 63 | BotNeckClient.onWebSocketReceive.invoke(wsJson); 64 | } 65 | 66 | _commandManager = new CommandManager(parsedConfig); 67 | BotNeckClient.on('messageSend', (message, isBotRequest) => { 68 | if(isBotRequest) return; 69 | if(_commandManager.handleMessage(message)) { 70 | if(BotNeckParser.Instance.TextOnly && message.Embed) { 71 | message.Content += '\n\n' + embedToContent(message.Embed); 72 | message.Embed = null; 73 | } 74 | } 75 | }); 76 | BotNeckClient.on('messageReceived', (message, sentByCurrentUser) => { 77 | if(!sentByCurrentUser) return; // Make sure it's sent by the current user 78 | if(!parsedConfig.IsMaster) return; // Make sure our current client is the master 79 | let baseMessage = new DiscordClientMessageBase(); 80 | 81 | baseMessage.Content = message.Content; 82 | baseMessage.Embed = message.Embed; 83 | if(_commandManager.handleMessage(baseMessage)) { 84 | if(baseMessage.Content === '' || !baseMessage.Content) baseMessage.Content = ' '; // Make sure we have an empty message content 85 | 86 | if(BotNeckParser.Instance.TextOnly && message.Embed) { 87 | baseMessage.Content += '\n\n' + embedToContent(baseMessage.Embed); 88 | baseMessage.Embed = null; 89 | } 90 | 91 | message.editMessage(baseMessage) 92 | .then(() => BotNeckLog.log('Invoked remote message', message)) 93 | .catch(err => BotNeckLog.error(err, 'Failed to invoke remote message', message)); 94 | } 95 | }); 96 | 97 | _moduleManager = new ModuleManager(); 98 | _moduleManager.loadModules(); 99 | }) 100 | .catch(err => BotNeckLog.error(err, 'Failed to load BotNeck configuration!')); 101 | } 102 | destroy() { 103 | if(_moduleManager) { 104 | _moduleManager.destroy(); 105 | _moduleManager = null; 106 | } 107 | 108 | if(_commandManager) { 109 | _commandManager.destroy(); 110 | _commandManager = null; 111 | } 112 | 113 | if(_discordNetwork) { 114 | DiscordNetworkCleanup(); 115 | _discordNetwork = null; 116 | } 117 | 118 | if(_configManager) { 119 | _configManager.destroy(); 120 | _configManager = null; 121 | } 122 | } 123 | 124 | static get Name() { return 'BotNeck Bot'; } 125 | static get Description() { return 'Adds selfbot commands to the Discord client.'; } 126 | static get Version() { return '3.0.6'; } 127 | static get Author() { return 'AtiLion'; } 128 | } -------------------------------------------------------------------------------- /BotNeckBot/core/CommandManager.js: -------------------------------------------------------------------------------- 1 | const { DiscordClientMessageBase, DiscordEmbed } = require('../api/DiscordAPI'); 2 | const { BotNeckParser } = require('./configParsers'); 3 | const BotNeckLog = require('../api/BotNeckLog'); 4 | const BotNeckCommand = require('../api/BotNeckCommand'); 5 | const BotNeckPresets = require('../api/BotNeckPresets'); 6 | 7 | let _instance = null; 8 | module.exports = class CommandManager { 9 | /** 10 | * Creates the CommandManager to easily work with commands 11 | * @param {BotNeckParser} config The parsed configuration object for BotNeck 12 | */ 13 | constructor(config) { 14 | if(_instance) { 15 | BotNeckLog.error('CommandManager instance already exists!'); 16 | return; 17 | } 18 | _instance = this; 19 | 20 | // Setup the memory objects 21 | /** 22 | * @type {[BotNeckCommand]} 23 | */ 24 | this.registeredCommands = []; 25 | 26 | // Get out the data from the config 27 | this.config = config; 28 | } 29 | destroy() { 30 | BotNeckLog.log('Cleaning up CommandManager ...'); 31 | _instance = null; 32 | } 33 | 34 | /** 35 | * Gets the main instance of the CommandManager 36 | * @returns {CommandManager} The instance of the command manager 37 | */ 38 | static get Instance() { return _instance; } 39 | 40 | /** 41 | * Handles the client message object to find a command 42 | * @param {DiscordClientMessageBase} message The message object that the client is trying to send 43 | */ 44 | handleMessage(message) { 45 | if(!message || !message.Content) return false; 46 | if(!message.Content.startsWith(this.config.Prefix)) return false; 47 | let rawCommand = message.Content.substring(this.config.Prefix.length); // Remove the prefix 48 | let justCommand = rawCommand.split(' ')[0]; 49 | 50 | // Find the correct command, parse and execute 51 | for(let command of this.registeredCommands) { 52 | if(justCommand !== command.Command && !command.Aliases.includes(justCommand)) continue; 53 | let commandArgs = this.parseCommand(rawCommand); 54 | 55 | if(command.MinimumArguments > BotNeckCommand.getNumberOfArguments(commandArgs)) { 56 | BotNeckLog.log('Not enough arguments for message', message.Content); 57 | 58 | if(!this.config.ErrorOnNotEnoughArguments) return false; 59 | BotNeckPresets.createError(message, 'Not enough arguments provided! Check the usage below!'); 60 | message.Embed.addField('Command Usage', command.Usage, false); 61 | return true; 62 | } 63 | 64 | command.execute(message, commandArgs); 65 | return true; 66 | } 67 | 68 | // Handle when not found 69 | BotNeckLog.log('Failed to find command for message', message.Content); 70 | if(!this.config.ErrorOnCommandNotFound) return false; 71 | BotNeckPresets.createError(message, 'Failed to find specified command!'); 72 | return true; 73 | } 74 | /** 75 | * Parses the raw command message and returns the arguments 76 | * @param {String} rawCommand The raw command message to parse 77 | * @returns {any} The parsed arguments in the raw command message 78 | */ 79 | parseCommand(rawCommand) { 80 | let rawArgs = rawCommand.split(' ').slice(1).join(' '); // Make sure to remove the command 81 | let args = {}; 82 | 83 | let _escaped = false; 84 | let _inString = ''; 85 | let currentText = ''; 86 | let activeKeys = []; 87 | let index = 0; 88 | 89 | function pushArg() { 90 | if(activeKeys.length) { 91 | for(let key of activeKeys) 92 | args[key] = currentText; 93 | 94 | activeKeys = []; 95 | currentText = ''; 96 | return; 97 | } 98 | 99 | if(currentText.length) { 100 | args[index++] = currentText; 101 | currentText = ''; 102 | } 103 | } 104 | 105 | for(let char of rawArgs) { 106 | if(!_escaped) { 107 | if(char === '\\') { 108 | _escaped = true; 109 | continue; 110 | } 111 | else if(char === '"' || char === '\'') { 112 | if(_inString === char) { 113 | _inString = ''; 114 | continue; 115 | } 116 | else if(!_inString) { 117 | _inString = char; 118 | continue; 119 | } 120 | } 121 | 122 | if(!_inString) { 123 | if(char === '=') { 124 | activeKeys.push(currentText); 125 | currentText = ''; 126 | continue; 127 | } 128 | else if(char === ' ') { 129 | pushArg(); 130 | continue; 131 | } 132 | } 133 | } 134 | 135 | _escaped = false; 136 | currentText += char; 137 | } 138 | 139 | pushArg(); 140 | return args; 141 | } 142 | 143 | /** 144 | * Register the command to the BotNeck bot 145 | * @param {BotNeckCommand} command The command object to register to the bot 146 | */ 147 | registerCommand(command) { 148 | if(!command) return; 149 | if(this.registeredCommands.includes(command)) return; 150 | 151 | this.registeredCommands.push(command); 152 | } 153 | /** 154 | * Unregister the command from the BotNeck bot 155 | * @param {BotNeckCommand} command The command object to unregister from the bot 156 | */ 157 | unregisterCommand(command) { 158 | if(!command) return; 159 | if(!this.registeredCommands.includes(command)) return; 160 | 161 | let index = this.registeredCommands.indexOf(command); 162 | this.registeredCommands.splice(index, 1); 163 | } 164 | } -------------------------------------------------------------------------------- /BotNeckBot/core/ConfigManager.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const BotNeckLog = require('../api/BotNeckLog'); 5 | 6 | let _instance = null; 7 | module.exports = class ConfigManager { 8 | /** 9 | * Creates the ConfigManager to handle configuration files 10 | */ 11 | constructor() { 12 | this.configDirectory = path.resolve(__dirname, '../config'); 13 | 14 | if(_instance) { 15 | BotNeckLog.error('ConfigManager instance already exists!'); 16 | return; 17 | } 18 | _instance = this; 19 | } 20 | /** 21 | * Destroys and cleans up the ConfigManager instance 22 | */ 23 | destroy() { 24 | BotNeckLog.log('Cleaning up ConfigManager ...'); 25 | _instance = null; 26 | } 27 | 28 | /** 29 | * Returns the main instance of ConfigManager 30 | * @returns {ConfigManager} The instance of config manager 31 | */ 32 | static get Instance() { return _instance; } 33 | 34 | /** 35 | * Converts the configuration name into the path to the config file 36 | * @param {String} configName The config name to convert 37 | * @returns {String} The config file path 38 | */ 39 | convertNameToPath(configName) { 40 | return path.resolve(this.configDirectory, configName + '.json'); 41 | } 42 | 43 | /** 44 | * Loads the specified configuration file using it's name 45 | * @param {String} configName The configuration file name 46 | * @returns {Promise} The parsed configuration or null 47 | */ 48 | loadConfiguration(configName) { 49 | return new Promise((resolve, reject) => { 50 | const configPath = this.convertNameToPath(configName); 51 | 52 | if(!fs.existsSync(configPath)) 53 | return reject('Configuration file does not exist'); 54 | 55 | try { 56 | fs.readFile(configPath, (err, data) => { 57 | if(err) return reject(err); 58 | let jsonData = JSON.parse(data.toString()); 59 | 60 | if(!jsonData) return resolve(null); 61 | return resolve(jsonData); 62 | }); 63 | } catch (err) { 64 | return reject(err); 65 | } 66 | }) 67 | } 68 | /** 69 | * Changes the configuration object into JSON and saves it into the specified file 70 | * @param {String} configName The configuration file name 71 | * @param {any} configObject The object to turn into a JSON string 72 | * @returns {Promise} Called resolve if successful or reject if file failed to write 73 | */ 74 | saveConfiguration(configName, configObject) { 75 | return new Promise((resolve, reject) => { 76 | const configPath = this.convertNameToPath(configName); 77 | 78 | if(!configObject) return reject('Configuration object cannot be null'); 79 | try { 80 | fs.writeFile(configPath, JSON.stringify(configObject, null, '\t'), err => { 81 | if(err) return reject(err); 82 | resolve(); 83 | }); 84 | } catch (err) { 85 | return reject(err); 86 | } 87 | }); 88 | } 89 | } -------------------------------------------------------------------------------- /BotNeckBot/core/DiscordNetwork.js: -------------------------------------------------------------------------------- 1 | const erlpack = DiscordNative.nativeModules.requireModule('discord_erlpack'); 2 | const { contextBridge, ipcRenderer } = require('electron'); 3 | const BotNeckLog = require('../api/BotNeckLog'); 4 | const DiscordWebpack = require('../api/DiscordAPI/DiscordWebpack'); 5 | 6 | const discordAPIUrl = 'https://discordapp.com/api/v8'; 7 | const validRequestTypes = [ 'POST', 'GET', 'DELETE', 'PATCH' ]; 8 | 9 | const payloads = {}; 10 | let hasExposedFunctions = false; 11 | 12 | //------------------------------- Utilities 13 | function safeParseJson(jsonString) { 14 | if(!jsonString) return null; 15 | 16 | try { return JSON.parse(jsonString); } 17 | catch (err) { return null; } 18 | } 19 | function sendScript(script) { 20 | ipcRenderer.invoke('bd-run-script', ` 21 | } catch {}}); 22 | 23 | (function() { ${script} })(); 24 | 25 | (() => {try { 26 | `); 27 | } 28 | 29 | 30 | //------------------------------- XMLHttpRequest.open 31 | payloads['httpOpenLoad'] = function(responseText, isBotNeckMessage) { 32 | const parsedMessage = safeParseJson(responseText); 33 | if(!parsedMessage) return; 34 | 35 | safeInvokeEvent('onResponseReceived', parsedMessage, isBotNeckMessage); 36 | } 37 | 38 | const originalHttpOpen = XMLHttpRequest.prototype.open; 39 | function overrideHttpOpen() { 40 | BotNeckLog.log('Overriding XMLHttpRequest.open ...'); 41 | 42 | if(window.BotNeck.isSandboxed) { 43 | sendScript(` 44 | BotNeckBot.originals['originalHttpOpen'] = XMLHttpRequest.prototype.open; 45 | 46 | XMLHttpRequest.prototype.open = function(method, url) { 47 | const reqResult = BotNeckBot.originals['originalHttpOpen'].apply(this, [].slice.call(arguments)); 48 | this._url = url; 49 | 50 | this.addEventListener('load', () => { 51 | BotNeckBot.payloads['httpOpenLoad'](this.responseText, false); 52 | }); 53 | 54 | return reqResult; 55 | } 56 | `); 57 | } 58 | XMLHttpRequest.prototype.open = function(method, url) { 59 | const reqResult = originalHttpOpen.apply(this, [].slice.call(arguments)); 60 | this._url = url; 61 | 62 | this.addEventListener('load', () => { 63 | payloads['httpOpenLoad'](this.responseText, this.botNeckMessage || false); 64 | }); 65 | 66 | return reqResult; 67 | } 68 | } 69 | function cleanHttpOpen() { 70 | BotNeckLog.log('Cleaning XMLHttpRequest.open ...'); 71 | 72 | if(window.BotNeck.isSandboxed) 73 | sendScript(`XMLHttpRequest.prototype.open = BotNeckBot.originals['originalHttpOpen']`); 74 | XMLHttpRequest.prototype.open = originalHttpOpen; 75 | } 76 | 77 | 78 | //------------------------------- XMLHttpRequest.setRequestHeader 79 | let authorizationToken = null; 80 | payloads['httpSetHeader'] = function(header, value, isBotNeckMessage, url) { 81 | if((url.startsWith('https://discordapp.com') || url.startsWith('https://canary.discord.com')) && header.toLowerCase() === 'authorization' && !isBotNeckMessage) { 82 | if(!authorizationToken) 83 | BotNeckLog.log('Set authorization token!'); 84 | else if(authorizationToken !== value) 85 | BotNeckLog.log('Updated authorization token!'); 86 | 87 | authorizationToken = value; // Save the token for authorized requests 88 | } 89 | } 90 | 91 | const originalHttpSetHeader = XMLHttpRequest.prototype.setRequestHeader; 92 | function overrideHttpSetRequestHeader() { 93 | BotNeckLog.log('Overriding XMLHttpRequest.setRequestHeader ...'); 94 | 95 | if(window.BotNeck.isSandboxed) { 96 | sendScript(` 97 | BotNeckBot.originals['originalHttpSetHeader'] = XMLHttpRequest.prototype.setRequestHeader; 98 | 99 | XMLHttpRequest.prototype.setRequestHeader = function(header, value) { 100 | if(header.toLowerCase() === 'content-type' && value === 'application/json') this._allowEdits = true; 101 | BotNeckBot.payloads['httpSetHeader'](header, value, false, this._url); 102 | 103 | return BotNeckBot.originals['originalHttpSetHeader'].call(this, header, value); 104 | } 105 | `); 106 | } 107 | XMLHttpRequest.prototype.setRequestHeader = function(header, value) { 108 | if(header.toLowerCase() === 'content-type' && value === 'application/json') this._allowEdits = true; 109 | payloads['httpSetHeader'](header, value, this.botNeckMessage || false, this._url); 110 | 111 | return originalHttpSetHeader.call(this, header, value); 112 | } 113 | } 114 | function cleanHttpSetRequestHeader() { 115 | BotNeckLog.log('Cleaning XMLHttpRequest.setRequestHeader ...'); 116 | 117 | if(window.BotNeck.isSandboxed) 118 | sendScript(`XMLHttpRequest.prototype.setRequestHeader = BotNeckBot.originals['originalHttpSetHeader']`); 119 | XMLHttpRequest.prototype.setRequestHeader = originalHttpSetHeader; 120 | } 121 | 122 | 123 | //------------------------------- XMLHttpRequest.send 124 | payloads['httpSend'] = function(data, isBotNeckMessage, escalateAuthorization, url) { 125 | const parsedData = safeParseJson(data); 126 | if(parsedData) { 127 | safeInvokeEvent('onRequestSent', parsedData, isBotNeckMessage); 128 | data = JSON.stringify(parsedData); 129 | } 130 | 131 | const authorization = null; 132 | if(escalateAuthorization && (url.startsWith('https://discordapp.com/') || url.startsWith('https://canary.discord.com/')) && authorizationToken) 133 | authorization = authorizationToken; 134 | 135 | return { data, authorization }; 136 | } 137 | 138 | const originalHttpSend = XMLHttpRequest.prototype.send; 139 | function overrideHttpSend() { 140 | BotNeckLog.log('Overriding XMLHttpRequest.send ...'); 141 | 142 | if(window.BotNeck.isSandboxed) { 143 | sendScript(` 144 | BotNeckBot.originals['originalHttpSend'] = XMLHttpRequest.prototype.send; 145 | 146 | XMLHttpRequest.prototype.send = function(originalData) { 147 | if(!this._allowEdits) return BotNeckBot.originals['originalHttpSend'].call(this, originalData); 148 | 149 | const { data, authorization } = BotNeckBot.payloads['httpSend'](originalData, false, this.escalateAuthorization, this._url); 150 | 151 | if(authorization) this.setRequestHeader('Authorization', authorization); 152 | return BotNeckBot.originals['originalHttpSend'].call(this, data); 153 | } 154 | `); 155 | } 156 | XMLHttpRequest.prototype.send = function(originalData) { 157 | if(!this._allowEdits) return BotNeckBot.originals['originalHttpSend'].call(this, originalData); 158 | 159 | const { data, authorization } = payloads['httpSend'](originalData, this.botNeckMessage || false, this.escalateAuthorization, this._url); 160 | 161 | if(authorization) this.setRequestHeader('Authorization', authorization); 162 | return originalHttpSend.call(this, data); 163 | } 164 | } 165 | function cleanHttpSend() { 166 | BotNeckLog.log('Cleaning XMLHttpRequest.send ...'); 167 | 168 | if(window.BotNeck.isSandboxed) 169 | sendScript(`XMLHttpRequest.prototype.send = BotNeckBot.originals['originalHttpSend']`); 170 | XMLHttpRequest.prototype.send = originalHttpSend; 171 | } 172 | 173 | 174 | // EarlPack 175 | payloads['earlpack'] = function(packedData) { 176 | let result = originalUnpack(packedData); 177 | safeInvokeEvent('onWSReceived', result); 178 | 179 | return result; 180 | } 181 | 182 | const originalUnpack = erlpack.unpack; 183 | function overrideErlpack() { 184 | BotNeckLog.log('Overriding erlpack ...'); 185 | 186 | if(window.BotNeck.isSandboxed) { 187 | // TODO: For some reason these no longer get cached on the main process, figure out how to fix that? 188 | sendScript(` 189 | const erlpack = DiscordNative.nativeModules.requireModule('discord_erlpack'); 190 | BotNeckBot.originals['originalUnpack'] = erlpack.unpack; 191 | 192 | console.log(erlpack); 193 | erlpack.unpack = function(packedData) { 194 | return BotNeckBot.payloads['earlpack'](packedData); 195 | } 196 | `); 197 | } 198 | erlpack.unpack = function(packedData) { 199 | return payloads['earlpack'](packedData); 200 | } 201 | } 202 | function cleanEarlpack() { 203 | BotNeckLog.log('Cleaning erlpack ...'); 204 | 205 | if(window.BotNeck.isSandboxed) { 206 | sendScript(` 207 | const erlpack = DiscordNative.nativeModules.requireModule('discord_erlpack'); 208 | 209 | erlpack.unpack = BotNeckBot.originals['originalUnpack']; 210 | `); 211 | } 212 | erlpack.unpack = originalUnpack; 213 | } 214 | 215 | 216 | //------------------------------- AJAX support 217 | window.$ = window.$ || { ajax: function() {} }; // TODO: Initialize or replicate AJAX 218 | const originalAjax = $.ajax; 219 | function overrideAjax() { 220 | BotNeckLog.log('Overriding AJAX ...'); 221 | 222 | $.ajax = function(reqObj) { 223 | let origBeforeSend = reqObj.beforeSend; 224 | 225 | reqObj.beforeSend = (xhr) => { 226 | const setRequestHeader = xhr.setRequestHeader; 227 | 228 | { origBeforeSend(xhr); } 229 | 230 | if(xhr.escalateAuthorization && (reqObj.url.startsWith('https://discordapp.com/') || reqObj.url.startsWith('https://canary.discord.com/'))) 231 | setRequestHeader('Authorization', authorizationToken); 232 | }; 233 | 234 | return originalAjax.call(this, reqObj); 235 | } 236 | } 237 | 238 | ////////////////////////// DiscordNetwork 239 | let _instance = null; 240 | function safeInvokeEvent(event, ...args) { 241 | if(!event) 242 | throw 'No event specified!'; 243 | 244 | try { 245 | if(_instance && _instance[event]) 246 | _instance[event].apply(this, args); 247 | } 248 | catch (err) { BotNeckLog.error(err, 'Failed to invoke event', event); } 249 | } 250 | class DiscordNetwork { 251 | /** 252 | * Creates the DiscordNetwork handler for easy interaction with Discord's API 253 | */ 254 | constructor() { 255 | //this.onEventReceived = null; 256 | this.onRequestSent = null; // Args: Parsed JSON content, Was sent by bot 257 | this.onResponseReceived = null; // Args: Parsed JSON, Was sent by bot 258 | this.onWSReceived = null; // Args: Parsed JSON 259 | 260 | if(_instance) { 261 | BotNeckLog.error('DiscordNetwork instance already exists!'); 262 | return; 263 | } 264 | _instance = this; 265 | 266 | if(!hasExposedFunctions && window.BotNeck.isSandboxed) { 267 | hasExposedFunctions = true; 268 | 269 | contextBridge.exposeInMainWorld('BotNeckBotPayloads', payloads); 270 | sendScript(` 271 | const BotNeckBot = window.BotNeckBot = window.BotNeckBot || {}; 272 | 273 | BotNeckBot.originals = BotNeckBot.originals || {}; 274 | BotNeckBot.payloads = window.BotNeckBotPayloads; 275 | `); 276 | } 277 | 278 | // Override all the network functions 279 | overrideHttpOpen(); 280 | overrideHttpSend(); 281 | overrideHttpSetRequestHeader(); 282 | overrideAjax(); 283 | overrideErlpack(); 284 | } 285 | 286 | /** 287 | * Returns the main instance of DiscordNetwork 288 | * @returns {DiscordNetwork} 289 | */ 290 | static get Instance() { return _instance; } 291 | 292 | /** 293 | * Sends an unauthorized request to Discord's API with the specified data 294 | * @param {String} endpoint The endpoint in Discord's API to send the request to 295 | * @param {String} type The type of request to send (GET, SET, ...) 296 | * @param {any} jsonData The JSON data to send along with the request 297 | * @returns {Promise} The JSON object that gets returned from Discord's API 298 | */ 299 | sendRequest(endpoint, type, jsonData = null) { 300 | if(_instance !== this) throw 'This instance of DiscordNetwork is invalid!'; 301 | if(!validRequestTypes.includes(type)) throw 'Invalid request type!'; 302 | if(!endpoint) throw 'Empty endpoint!'; 303 | 304 | return new Promise((resolve, reject) => { 305 | if(!endpoint.startsWith('/')) endpoint = '/' + endpoint; 306 | //if(jsonData) jsonData = safeParseJson(jsonData); 307 | 308 | let req = new XMLHttpRequest(); 309 | req.botNeckMessage = true; 310 | 311 | req.addEventListener('load', () => resolve(safeParseJson(req.responseText))); 312 | req.addEventListener('error', () => reject()); 313 | 314 | req.open(type, discordAPIUrl + endpoint); 315 | req.setRequestHeader('Content-Type', 'application/json'); 316 | req.send(JSON.stringify(jsonData)); 317 | }); 318 | } 319 | /** 320 | * Sends an authorized request to Discord's API with the specified data 321 | * @param {String} endpoint The endpoint in Discord's API to send the request to 322 | * @param {String} type The type of request to send (GET, SET, ...) 323 | * @param {any} jsonData The JSON data to send along with the request 324 | * @returns {Promise} The JSON object that gets returned from Discord's API 325 | */ 326 | sendAuthorizedRequest(endpoint, type, jsonData = null) { 327 | if(_instance !== this) throw 'This instance of DiscordNetwork is invalid!'; 328 | if(!validRequestTypes.includes(type)) throw 'Invalid request type!'; 329 | if(!endpoint) throw 'Empty endpoint!'; 330 | //if(!authorizationToken) throw 'No authorization token saved!'; 331 | if(!authorizationToken) { 332 | let authObject = DiscordWebpack.getByProps('FINGERPRINT_KEY'); 333 | if(!authObject) throw 'No authorization token saved!'; 334 | 335 | authorizationToken = authObject.getToken(); 336 | BotNeckLog.log('Found the token through webpack!'); 337 | } 338 | 339 | return new Promise((resolve, reject) => { 340 | if(!endpoint.startsWith('/')) endpoint = '/' + endpoint; 341 | //if(jsonData) jsonData = safeParseJson(jsonData); 342 | 343 | let req = new XMLHttpRequest(); 344 | req.botNeckMessage = true; 345 | 346 | req.addEventListener('load', () => resolve(safeParseJson(req.responseText))); 347 | req.addEventListener('error', () => reject()); 348 | 349 | req.open(type, discordAPIUrl + endpoint); 350 | req.setRequestHeader('Content-Type', 'application/json'); 351 | req.setRequestHeader('Authorization', authorizationToken); 352 | req.send(JSON.stringify(jsonData)); 353 | }); 354 | } 355 | } 356 | /** 357 | * Cleans up the DiscordNetwork system enough to be used on next start 358 | */ 359 | function DiscordNetworkCleanup() { 360 | BotNeckLog.log('Removing DiscordNetwork instances ...'); 361 | _instance = null; 362 | 363 | cleanHttpOpen(); 364 | cleanHttpSetRequestHeader(); 365 | cleanHttpSend(); 366 | cleanEarlpack(); 367 | 368 | BotNeckLog.log('Cleaning AJAX ...'); 369 | $.ajax = originalAjax; 370 | } 371 | module.exports = { DiscordNetwork, DiscordNetworkCleanup } -------------------------------------------------------------------------------- /BotNeckBot/core/ModuleManager.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const BotNeckLog = require('../api/BotNeckLog'); 4 | const { v2Loader, GenericLoader, v3Loader } = require('./loaders'); 5 | 6 | let _instance = null; 7 | module.exports = class ModuleManager { 8 | /** 9 | * Creates the ModuleManager to load/use modules with 10 | */ 11 | constructor() { 12 | this.modulesDirectory = path.resolve(__dirname, '../modules'); 13 | /** 14 | * @type {[GenericLoader]} 15 | */ 16 | this.modules = []; 17 | 18 | if(_instance) { 19 | BotNeckLog.error('ModuleManager instance already exists!'); 20 | return; 21 | } 22 | _instance = this; 23 | 24 | if(!fs.existsSync(this.modulesDirectory)) { 25 | BotNeckLog.error('The modules directory does not exist!'); 26 | return; 27 | } 28 | } 29 | /** 30 | * Destroys and cleans up the ModuleManager instance 31 | */ 32 | destroy() { 33 | BotNeckLog.log('Unloading BotNeck modules ...'); 34 | this.unloadModules(); 35 | 36 | BotNeckLog.log('Cleaning up ModuleManager ...'); 37 | _instance = null; 38 | } 39 | 40 | /** 41 | * Gets the main instance of the ModuleManager 42 | * @returns {ModuleManager} The instance of the module manager 43 | */ 44 | static get Instance() { return _instance; } 45 | 46 | // TODO: Implement sandboxing when needed 47 | /** 48 | * Loads a module by it's name 49 | * @param {String} moduleName The module's name to load 50 | */ 51 | loadModule(moduleName) { 52 | const modulePath = path.resolve(this.modulesDirectory, moduleName); 53 | if(!fs.existsSync(modulePath) || moduleName.startsWith('.')) return; 54 | BotNeckLog.log('Initializing module', moduleName); 55 | 56 | const requiredModule = require(modulePath); 57 | if(v2Loader.verifyFormat(modulePath, requiredModule)) { 58 | BotNeckLog.log('Loading v2 module', moduleName); 59 | let modLoader = new v2Loader(modulePath, requiredModule); 60 | 61 | if(modLoader.load()) { 62 | this.modules.push(modLoader); 63 | BotNeckLog.log('Loaded v2 module', moduleName); 64 | } 65 | } 66 | else if(v3Loader.verifyFormat(modulePath, requiredModule)) { 67 | BotNeckLog.log('Loading v3 module', moduleName); 68 | let modLoader = new v3Loader(modulePath, requiredModule); 69 | 70 | if(modLoader.load()) { 71 | this.modules.push(modLoader); 72 | BotNeckLog.log('Loaded v3 module', moduleName); 73 | } 74 | } 75 | } 76 | /** 77 | * Loads all modules in the modules directory 78 | */ 79 | loadModules() { 80 | return new Promise((resolve, reject) => { 81 | fs.readdir(this.modulesDirectory, (err, files) => { 82 | if(err) return reject(err); 83 | 84 | for(let moduleFile of files) { 85 | try { this.loadModule(moduleFile); } 86 | catch (err) { BotNeckLog.error(err, 'Failed to auto-load module', moduleFile); } 87 | } 88 | resolve(); 89 | }); 90 | }); 91 | } 92 | 93 | /** 94 | * Unloads the specified module with the module loader 95 | * @param {GenericLoader} moduleLoader The loader instance of the module you want to unload 96 | */ 97 | unloadModule(moduleLoader) { 98 | if(!moduleLoader || !(moduleLoader instanceof GenericLoader)) return; 99 | 100 | moduleLoader.unload(); 101 | let index = this.modules.indexOf(moduleLoader); 102 | this.modules.splice(index, 1); 103 | BotNeckLog.log('Unloaded module', path.basename(moduleLoader.file)); 104 | } 105 | /** 106 | * Unloads all modules from the modules directory 107 | */ 108 | unloadModules() { 109 | for(let modLoader of [...this.modules]) 110 | this.unloadModule(modLoader); 111 | } 112 | } -------------------------------------------------------------------------------- /BotNeckBot/core/Sandbox.js: -------------------------------------------------------------------------------- 1 | const BotNeckModule = require('../api/BotNeckModule'); 2 | 3 | module.exports = class Sandbox extends BotNeckModule { 4 | constructor(filePath) { 5 | this.safeRequire = require.bind({}); // Re-create to prevent shared memory between modules 6 | this.module = this.safeRequire(filePath); 7 | this.instance = new this.module(); 8 | } 9 | 10 | invokeFunction(func, ...args) { 11 | return this.instance[func].bind(this.instance)(...args); 12 | } 13 | getPropertyValue(property) { 14 | return this.instance[property]; 15 | } 16 | setPropertyValue(property, value) { 17 | this.instance[property] = value; 18 | } 19 | 20 | get Name() { return this.getPropertyValue('Name'); } 21 | get Description() { return this.getPropertyValue('Description'); } 22 | get Version() { return this.getPropertyValue('Version'); } 23 | get Author() { return this.getPropertyValue('Author'); } 24 | 25 | onLoad() { return this.invokeFunction('onLoad'); } 26 | onUnload() { return this.invokeFunction('onUnload'); } 27 | 28 | registerCommand(command) { return this.invokeFunction('registerCommand', command); } 29 | unregisterCommand(command) { return this.invokeFunction('unregisterCommand', command); } 30 | } -------------------------------------------------------------------------------- /BotNeckBot/core/configParsers/BotNeck.js: -------------------------------------------------------------------------------- 1 | const BotNeckConfig = require('../../api/BotNeckConfig'); 2 | const BotNeckLog = require('../../api/BotNeckLog'); 3 | 4 | let _instance = null; 5 | module.exports = class BotNeckParser extends BotNeckConfig { 6 | constructor() { 7 | super('BotNeck'); 8 | 9 | if(_instance) { 10 | BotNeckLog.error('Main config has already been loaded!'); 11 | return; 12 | } 13 | _instance = this; 14 | 15 | // Set defaults 16 | this.TextOnly = false; 17 | this.Prefix = '->'; 18 | this.ErrorOnCommandNotFound = true; 19 | this.ErrorOnNotEnoughArguments = true; 20 | this.IsMaster = false; 21 | } 22 | 23 | /** 24 | * Returns the main config instance of BotNeck 25 | * @returns {BotNeckParser} The instance of the BotNeck config 26 | */ 27 | static get Instance() { return _instance; } 28 | 29 | /** 30 | * When enbaled the bot will convert embeds into text 31 | * @returns {Boolean} 32 | */ 33 | get TextOnly() { return this.config.textOnly; } 34 | set TextOnly(textOnly) { this.config.textOnly = textOnly; } 35 | 36 | /** 37 | * The prefix used to specify what is a command 38 | * @returns {String} 39 | */ 40 | get Prefix() { return this.config.prefix; } 41 | set Prefix(prefix) { this.config.prefix = prefix; } 42 | 43 | /** 44 | * Should an error be displayed if a command is not found 45 | * @returns {Boolean} 46 | */ 47 | get ErrorOnCommandNotFound() { return this.config.errorOnCommandNotFound; } 48 | set ErrorOnCommandNotFound(errorOnCommandNotFound) { this.config.errorOnCommandNotFound = errorOnCommandNotFound; } 49 | 50 | /** 51 | * Should an error be displayed if not enough arguments were provided to the command 52 | * @returns {Boolean} 53 | */ 54 | get ErrorOnNotEnoughArguments() { return this.config.errorOnNotEnoughArguments; } 55 | set ErrorOnNotEnoughArguments(errorOnNotEnoughArguments) { this.config.errorOnNotEnoughArguments = errorOnNotEnoughArguments; } 56 | 57 | /** 58 | * Is the current client the master (used for remote commands) 59 | * @returns {Boolean} 60 | */ 61 | get IsMaster() { return this.config.isMaster; } 62 | set IsMaster(isMaster) { this.config.isMaster = isMaster; } 63 | } -------------------------------------------------------------------------------- /BotNeckBot/core/configParsers/index.js: -------------------------------------------------------------------------------- 1 | const BotNeckParser = require('./BotNeck'); 2 | 3 | module.exports = { BotNeckParser } -------------------------------------------------------------------------------- /BotNeckBot/core/loaders/GenericLoader.js: -------------------------------------------------------------------------------- 1 | module.exports = class GenericLoader { 2 | /** 3 | * Creates a generic wrapper around the module that let's you easily load it 4 | * @param {String} file The module file to wrap 5 | * @param {any} module The require of the module to wrap 6 | * @param {String} type The type of the loader that is using the generic one 7 | */ 8 | constructor(file, module, type) { 9 | this.file = file; 10 | this.module = module; 11 | this.type = type; 12 | } 13 | 14 | /** 15 | * Loads the module and starts it 16 | */ 17 | load() {} 18 | /** 19 | * Stops and unloads the module 20 | */ 21 | unload() {} 22 | } -------------------------------------------------------------------------------- /BotNeckBot/core/loaders/index.js: -------------------------------------------------------------------------------- 1 | const v2Loader = require('./v2Loader'); 2 | const GenericLoader = require('./GenericLoader'); 3 | const v3Loader = require('./v3Loader'); 4 | 5 | module.exports = { v2Loader, GenericLoader, v3Loader } -------------------------------------------------------------------------------- /BotNeckBot/core/loaders/v2Loader.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const GenericLoader = require('./GenericLoader'); 4 | const BotNeckLog = require('../../api/BotNeckLog'); 5 | const { v2Command } = require('../../api/v2Api'); 6 | const BotNeckCommand = require('../../api/BotNeckCommand'); 7 | 8 | module.exports = class v2Loader extends GenericLoader { 9 | /** 10 | * Creates a wrapper around the module that let's you easily load it 11 | * @param {String} file The module file to wrap 12 | * @param {any} module The require of the module to wrap 13 | */ 14 | constructor(file, module) { 15 | super(file, module, 'v2Loader'); 16 | 17 | this.commandModule = null; 18 | } 19 | 20 | /** 21 | * Loads the module and starts it 22 | */ 23 | load() { 24 | if(!fs.existsSync(this.file)) return false; 25 | const code = fs.readFileSync(this.file).toString(); 26 | const name = path.basename(this.file).slice(0, -('.botneck.js'.length)); 27 | 28 | try { 29 | let generatedCode = 'const { BotNeckAPI } = require("../../api/v2Api");'; 30 | generatedCode += '(function() {'; 31 | generatedCode += 'let APIKey = "No longer required";'; 32 | generatedCode += '\n' + code + '\n'; 33 | generatedCode += `return new ${name}();` 34 | generatedCode += '})();'; 35 | 36 | let oldModule = eval(generatedCode); 37 | this.commandModule = new v2Command(oldModule.command, oldModule.description, oldModule.usage, oldModule.execute); 38 | 39 | // Some use custom fields and functions, so we need to copy them over 40 | for(let extraField in oldModule) { 41 | if(extraField === 'command' || extraField === 'description' || extraField === 'usage') continue; 42 | this.commandModule[extraField] = oldModule[extraField]; 43 | } 44 | for(let extraFunc of Object.getOwnPropertyNames(oldModule.__proto__)) { 45 | if(extraFunc === 'execute' || extraFunc === 'constructor') continue; 46 | this.commandModule[extraFunc] = oldModule.__proto__[extraFunc]; 47 | } 48 | 49 | BotNeckCommand.registerCommand(this.commandModule); 50 | return true; 51 | } catch (err) { 52 | BotNeckLog.error(err, 'Failed to load module', name); 53 | return false; 54 | } 55 | } 56 | /** 57 | * Stops and unloads the module 58 | */ 59 | unload() { 60 | BotNeckCommand.unregisterCommand(this.commandModule); 61 | } 62 | 63 | /** 64 | * Verifies the module's format if the v2 loader can load the module format 65 | * @param {String} file The file path to the module 66 | * @param {any} module The module.exports of the module 67 | * @returns {Boolean} If the v2 loader can load the module's format 68 | */ 69 | static verifyFormat(file, module) { 70 | if(Object.keys(module).length > 0) return false; 71 | if(!file.endsWith('.botneck.js')) return false; 72 | 73 | // Get required variables 74 | const name = path.basename(file).slice(0, -('.botneck.js'.length)); 75 | 76 | // Make an meh sandbox 77 | { 78 | try { 79 | if(!fs.existsSync(file)) return false; 80 | const code = fs.readFileSync(file).toString(); 81 | 82 | let generatedCode = '(function(){'; 83 | generatedCode += `let APIKey = 'No longer required';`; 84 | generatedCode += '\n' + code + '\n'; 85 | generatedCode += `return (typeof ${name} === 'function');`; 86 | generatedCode += '})()'; 87 | return eval(generatedCode); 88 | } catch (err) { 89 | BotNeckLog.error(err, 'Failed to verify V2 module!'); 90 | return false; 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /BotNeckBot/core/loaders/v3Loader.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const GenericLoader = require('./GenericLoader'); 3 | const BotNeckModule = require('../../api/BotNeckModule'); 4 | const BotNeckLog = require('../../api/BotNeckLog'); 5 | 6 | module.exports = class v3Loader extends GenericLoader { 7 | /** 8 | * Creates a wrapper around the module that let's you easily load it 9 | * @param {String} file The module file path to wrap 10 | * @param {any} module The require of the module to wrap 11 | */ 12 | constructor(file, module) { 13 | super(file, module, 'v3Loader'); 14 | 15 | /** 16 | * @type {BotNeckModule} 17 | */ 18 | this.moduleInstance = null; 19 | } 20 | 21 | /** 22 | * Loads the module and starts it 23 | */ 24 | load() { 25 | try { 26 | this.moduleInstance = new this.module(); 27 | this.moduleInstance.onLoad(); 28 | return true; 29 | } catch (err) { 30 | BotNeckLog.error(err, 'Failed to load module', path.basename(this.file)); 31 | return false; 32 | } 33 | } 34 | /** 35 | * Stops and unloads the module 36 | */ 37 | unload() { 38 | if(!this.moduleInstance) return; 39 | 40 | try { 41 | let cachedNames = []; 42 | for(let cachedModule in require.cache) { 43 | if(!cachedModule.startsWith(this.file)) continue; 44 | cachedNames.push(cachedModule); 45 | } 46 | for(let cachedName of cachedNames) 47 | delete require.cache[cachedName]; 48 | 49 | this.moduleInstance.onUnload(); 50 | } catch (err) { 51 | BotNeckLog.error(err, 'Failed to load module', path.basename(this.file)); 52 | } 53 | } 54 | 55 | /** 56 | * Verifies the module's format if the v2 loader can load the module format 57 | * @param {String} file The file path to the module 58 | * @param {any} module The module.exports of the module 59 | * @returns {Boolean} If the v2 loader can load the module's format 60 | */ 61 | static verifyFormat(file, module) { 62 | if(Object.keys(module).length > 0) return false; 63 | if(!(module.prototype instanceof BotNeckModule)) return false; 64 | 65 | return true; 66 | } 67 | } -------------------------------------------------------------------------------- /BotNeckBot/index.js: -------------------------------------------------------------------------------- 1 | const BotNeckBot = require('./core/BotNeckBot'); 2 | 3 | module.exports = { BotNeckBot } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckAPI.js: -------------------------------------------------------------------------------- 1 | const BotNeckLog = require('../api/BotNeckLog'); 2 | const BotNeckModule = require('../api/BotNeckModule'); 3 | const BotNeckCommand = require('../api/BotNeckCommand'); 4 | const BotNeckClient = require('../api/BotNeckClient'); 5 | const BotNeckEvent = require('../api/BotNeckEvent'); 6 | const BotNeckPresets = require('../api/BotNeckPresets'); 7 | const BotNeckConfig = require('../api/BotNeckConfig'); 8 | const DiscordAPI = require('../api/DiscordAPI'); 9 | 10 | module.exports = { 11 | BotNeckLog, 12 | BotNeckModule, 13 | BotNeckCommand, 14 | BotNeckClient, 15 | BotNeckEvent, 16 | BotNeckPresets, 17 | BotNeckConfig, 18 | DiscordAPI 19 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/8ball.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class EigthBallCommand extends BotNeckCommand { 10 | get Command() { return '8ball'; } 11 | get Description() { return 'The magic 8ball will answer any of your questions(as long as they are a yes or no question)'; } 12 | get Usage() { return '8ball '; } 13 | get MinimumArguments() { return 1; } 14 | 15 | get Answers() { return ['It is certain.', 'It is decidedly so.', 'Without a doubt.', 'Yes, definitely.', 'You may rely on it.', 'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Yes.', 'Signs point to yes.', 'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.', 'Cannot predict now.', 'Concentrate and ask again.', 'Don\'t count on it.', 'My reply is no.', 'My sources say no.', 'Outlook not so good.', 'Very doubtful.']; } 16 | 17 | /** 18 | * This function gets executed when the command is called 19 | * @param {DiscordClientMessage} message The message the client is trying to send 20 | * @param {any} args The arguments of the command 21 | */ 22 | execute(message, args) { 23 | BotNeckPresets.createBase(message, { 24 | Title: 'Magic 8Ball' 25 | }); 26 | message.Embed.addField('Question', BotNeckCommand.getArgumentsAsString(args)); 27 | message.Embed.addField('Answer', this.Answers[Math.floor(Math.random() * this.Answers.length)]); 28 | } 29 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/anilist.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const { 3 | BotNeckCommand, 4 | BotNeckClient, 5 | BotNeckPresets, 6 | DiscordAPI: { 7 | DiscordClientMessage, 8 | DiscordClientMessageBase 9 | } 10 | } = require('../../BotNeckAPI'); 11 | 12 | module.exports = class AniListCommand extends BotNeckCommand { 13 | get Command() { return 'anilist'; } 14 | get Description() { return 'Looks up a specific anime/manga'; } 15 | get Usage() { return 'anilist '; } 16 | get MinimumArguments() { return 2; } 17 | 18 | get APIUrl() { return 'https://graphql.anilist.co'; } 19 | get APITypes() { return ['anime', 'manga']; } 20 | get Months() { return ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Oct', 'Nov', 'Dec']; } 21 | 22 | /** 23 | * Converts AniList date object to string 24 | * @param {any} dateObj AniList date object 25 | * @returns {String} AniList date object as a string 26 | */ 27 | convertToDate(dateObj) { 28 | return this.Months[dateObj.month] + ' ' + dateObj.day.toString() + ', ' + dateObj.year.toString(); 29 | } 30 | /** 31 | * Extracts the text from an HTML string 32 | * @param {String} html The HTML string 33 | * @returns {String} The output text without HTML 34 | */ 35 | stripHTML(html) { 36 | let tmpDiv = document.createElement('div'); 37 | 38 | tmpDiv.innerHTML = html; 39 | return tmpDiv.innerText; 40 | } 41 | 42 | /** 43 | * Gets AniList information on anime/manga of a specific title 44 | * @param {String} type The type to search for (anime/manga) 45 | * @param {String} title the title of the anime/manga 46 | * @returns {Promise} Request output 47 | */ 48 | getAniListInfo(url, type, title) { 49 | return new Promise((resolve) => { 50 | let variables = { 51 | title 52 | } 53 | let query = ` 54 | query($title: String) { 55 | Media(search: $title, type: ${type === 'anime' ? 'ANIME' : 'MANGA'}) { 56 | title { 57 | romaji, 58 | english, 59 | native 60 | }, 61 | format, 62 | volumes, 63 | chapters, 64 | episodes, 65 | status, 66 | meanScore, 67 | averageScore, 68 | popularity, 69 | startDate { 70 | year, 71 | month, 72 | day 73 | } 74 | duration, 75 | isAdult, 76 | coverImage { 77 | medium 78 | }, 79 | siteUrl, 80 | description 81 | } 82 | }`; 83 | let options = { 84 | url: url, 85 | method: 'POST', 86 | body: { 87 | query: query, 88 | variables: variables 89 | }, 90 | json: true 91 | } 92 | request(options, (err, response, body) => resolve({ err, response, body })); 93 | }); 94 | } 95 | 96 | /** 97 | * Generates the anime reply message from AniList's data 98 | * @param {any} data The anime data 99 | * @param {DiscordClientMessageBase} message The reply message instance 100 | */ 101 | generateAnime(data, message) { 102 | BotNeckPresets.createBase(message, { 103 | Title: 'AniList Anime', 104 | Thumbnail: { 105 | Url: data.coverImage.medium 106 | }, 107 | Author: { 108 | Name: data.title.romaji, 109 | Url: data.siteUrl, 110 | IconUrl: 'https://anilist.co/img/icons/favicon-32x32.png' 111 | }, 112 | Description: this.stripHTML(data.description), 113 | Url: data.siteUrl, 114 | Fields: [ 115 | { 116 | Name: 'English Title', 117 | Value: (data.title.english == null ? 'None' : data.title.english), 118 | Inline: true 119 | }, 120 | { 121 | Name: 'Native Title', 122 | Value: (data.title.native == null ? 'None' : data.title.native), 123 | Inline: true 124 | }, 125 | { 126 | Name: 'Format', 127 | Value: (data.format == null ? 'None' : data.format), 128 | Inline: true 129 | }, 130 | { 131 | Name: 'Episodes', 132 | Value: (data.episodes == null ? 'None' : data.episodes.toString()), 133 | Inline: true 134 | }, 135 | { 136 | Name: 'Status', 137 | Value: (data.status == null ? 'None' : data.status), 138 | Inline: true 139 | }, 140 | { 141 | Name: 'Mean Score', 142 | Value: (data.meanScore == null ? 'None' : data.meanScore.toString()), 143 | Inline: true 144 | }, 145 | { 146 | Name: 'Average Score', 147 | Value: (data.averageScore == null ? 'None' : data.averageScore.toString()), 148 | Inline: true, 149 | }, 150 | { 151 | Name: 'Popularity', 152 | Value: (data.popularity == null ? 'None' : data.popularity.toString()), 153 | Inline: true, 154 | }, 155 | { 156 | Name: 'Aired', 157 | Value: (data.startDate.day == null ? 'None' : this.convertToDate(data.startDate)), 158 | Inline: true, 159 | }, 160 | { 161 | Name: 'Duration', 162 | Value: (data.duration == null ? 'None' : data.duration.toString() + ' min'), 163 | Inline: true, 164 | }, 165 | { 166 | Name: 'Adult', 167 | Value: (data.isAdult ? 'Yes' : 'No'), 168 | } 169 | ] 170 | }); 171 | } 172 | /** 173 | * Generates the manga reply message from AniList's data 174 | * @param {any} data The manga data 175 | * @param {DiscordClientMessageBase} message The reply message instance 176 | */ 177 | generateManga(data, message) { 178 | BotNeckPresets.createBase(message, { 179 | Title: 'AniList Manga', 180 | Thumbnail: { 181 | Url: data.coverImage.medium 182 | }, 183 | Author: { 184 | Name: data.title.romaji, 185 | Url: data.siteUrl, 186 | IconUrl: 'https://anilist.co/img/icons/favicon-32x32.png' 187 | }, 188 | Description: this.stripHTML(data.description), 189 | Url: data.siteUrl, 190 | Fields: [ 191 | { 192 | Name: 'English Title', 193 | Value: (data.title.english == null ? 'None' : data.title.english), 194 | Inline: true, 195 | }, 196 | { 197 | Name: 'Native Title', 198 | Value: (data.title.native == null ? 'None' : data.title.native), 199 | Inline: true, 200 | }, 201 | { 202 | Name: 'Format', 203 | Value: (data.format == null ? 'None' : data.format), 204 | Inline: true, 205 | }, 206 | { 207 | Name: 'Volumes', 208 | Value: (data.volumes == null ? 'None' : data.volumes.toString()), 209 | Inline: true, 210 | }, 211 | { 212 | Name: 'Chapters', 213 | Value: (data.chapters == null ? 'None' : data.chapters.toString()), 214 | Inline: true, 215 | }, 216 | { 217 | Name: 'Status', 218 | Value: (data.status == null ? 'None' : data.status), 219 | Inline: true, 220 | }, 221 | { 222 | Name: 'Mean Score', 223 | Value: (data.meanScore == null ? 'None' : data.meanScore.toString()), 224 | Inline: true, 225 | }, 226 | { 227 | Name: 'Average Score', 228 | Value: (data.averageScore == null ? 'None' : data.averageScore.toString()), 229 | Inline: true, 230 | }, 231 | { 232 | Name: 'Popularity', 233 | Value: (data.popularity == null ? 'None' : data.popularity.toString()), 234 | Inline: true, 235 | }, 236 | { 237 | Name: 'Published', 238 | Value: (data.startDate.day == null ? 'None' : this.convertToDate(data.startDate)), 239 | Inline: true, 240 | } 241 | ] 242 | }); 243 | } 244 | 245 | /** 246 | * This function gets executed when the command is called 247 | * @param {DiscordClientMessage} message The message the client is trying to send 248 | * @param {any} args The arguments of the command 249 | */ 250 | execute(message, args) { 251 | let type = args[0].toLowerCase(); 252 | let title = args[1]; 253 | 254 | if(!this.APITypes.includes(type)) 255 | return BotNeckPresets.createError(message, 'Only anime, manga, person and character allowed as type!'); 256 | 257 | BotNeckPresets.createInfo(message, 'Looking it up on AniList...'); 258 | BotNeckClient.runAfterMessage(this.getAniListInfo(this.APIUrl, type, title), ((dMessage, { err, response, body }) => { 259 | let replyMessage = new DiscordClientMessageBase(); 260 | 261 | if(err || response.statusCode != 200) { 262 | BotNeckPresets.createError(replyMessage, 'Failed to send search request to AniList!'); 263 | return dMessage.editMessage(replyMessage); 264 | } 265 | if(!body || !body.data || !body.data.Media) { 266 | BotNeckPresets.createError(replyMessage, 'Failed to parse AniList data!'); 267 | return dMessage.editMessage(replyMessage); 268 | } 269 | 270 | if(type === 'anime') this.generateAnime(body.data.Media, replyMessage); 271 | else if(type === 'manga') this.generateManga(body.data.Media, replyMessage); 272 | else BotNeckPresets.createError(replyMessage, 'Failed to use correct type'); 273 | dMessage.editMessage(replyMessage); 274 | }).bind(this)); 275 | } 276 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/avatar.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | BotNeckClient, 5 | BotNeckLog, 6 | DiscordAPI: { 7 | DiscordClientMessage, 8 | DiscordUser, 9 | DiscordClientMessageBase 10 | } 11 | } = require('../../BotNeckAPI'); 12 | 13 | module.exports = class AvatarCommand extends BotNeckCommand { 14 | get Command() { return 'avatar'; } 15 | get Description() { return 'Returns the profile picture of the target user'; } 16 | get Usage() { return 'avatar '; } 17 | get MinimumArguments() { return 1; } 18 | get Aliases() { return ['av']; } 19 | 20 | /** 21 | * This function gets executed when the command is called 22 | * @param {DiscordClientMessage} message The message the client is trying to send 23 | * @param {any} args The arguments of the command 24 | */ 25 | execute(message, args) { 26 | BotNeckPresets.createInfo(message, 'Loading avatar image...'); 27 | BotNeckClient.runAfterMessage(DiscordUser.getFromMention(args[0]), (dMesssage, userObj) => { 28 | if(!userObj) { 29 | let baseMessage = new DiscordClientMessageBase(); 30 | BotNeckPresets.createError(baseMessage, 'Failed to find selected user'); 31 | return dMesssage.editMessage(baseMessage); 32 | } 33 | 34 | /** 35 | * @type {DiscordUser} 36 | */ 37 | let user = userObj; 38 | 39 | // Figure out avatar url 40 | let avatarUrl = `https://cdn.discordapp.com/avatars/${user.Id}/${user.Avatar}`; 41 | let sizeSet = '?size=1024'; 42 | 43 | // Figure out image extension 44 | let imgExtension = 'png'; 45 | if(user.Avatar.startsWith('a_')) 46 | imgExtension = 'gif'; 47 | 48 | // Figure out links 49 | let links = `[png](${avatarUrl}.png${sizeSet}) | [jpg](${avatarUrl}.jpg${sizeSet})`; 50 | if(imgExtension === 'gif') 51 | links += ` | [webp](${avatarUrl}.webp${sizeSet})`; 52 | 53 | // Print out user information 54 | let baseMessage = new DiscordClientMessageBase(); 55 | BotNeckPresets.createBase(baseMessage, { 56 | Title: `Avatar for ${user.Username}#${user.Discriminator}`, 57 | Image: { 58 | Url: `https://cdn.discordapp.com/avatars/${user.Id}/${user.Avatar}.${imgExtension}${sizeSet}` 59 | } 60 | }); 61 | baseMessage.Embed.addField('Link as', links); 62 | dMesssage.editMessage(baseMessage); 63 | }); 64 | } 65 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/calculator.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class CalculatorCommand extends BotNeckCommand { 10 | get Command() { return 'calculate'; } 11 | get Description() { return 'This will calculate anything for you'; } 12 | get Usage() { return 'calculate '; } 13 | get MinimumArguments() { return 1; } 14 | get Aliases() { return ['calc']; } 15 | 16 | /** 17 | * This function gets executed when the command is called 18 | * @param {DiscordClientMessage} message The message the client is trying to send 19 | * @param {any} args The arguments of the command 20 | */ 21 | execute(message, args) { 22 | let input = BotNeckCommand.getArgumentsAsString(args); 23 | 24 | BotNeckPresets.createBase(message, { 25 | Title: 'Calculator' 26 | }); 27 | message.Embed.addField('Calculation', input); 28 | message.Embed.addField('Result', String(eval(input))); 29 | } 30 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/choose.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class ChooseCommand extends BotNeckCommand { 10 | get Command() { return 'choose'; } 11 | get Description() { return 'Randomly choose an answer'; } 12 | get Usage() { return 'choose '; } 13 | get MinimumArguments() { return 2; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | let argsSize = BotNeckCommand.getNumberOfArguments(args); 22 | 23 | BotNeckPresets.createBase(message, { 24 | Title: 'Random Chooser', 25 | Description: 'I choose: ' + args[Math.floor(Math.random() * argsSize)].trim() 26 | }); 27 | message.Embed.addField('Choices', BotNeckCommand.getArgumentsAsString(args)); 28 | } 29 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/coinflip.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class CoinflipCommand extends BotNeckCommand { 10 | get Command() { return 'coinflip'; } 11 | get Description() { return 'Flip a coin'; } 12 | get Usage() { return 'coinflip'; } 13 | 14 | /** 15 | * This function gets executed when the command is called 16 | * @param {DiscordClientMessage} message The message the client is trying to send 17 | * @param {any} args The arguments of the command 18 | */ 19 | execute(message, args) { 20 | BotNeckPresets.createBase(message, { 21 | Title: 'Coinflip', 22 | Description: (!!(Math.floor(Math.random() * 2)) ? 'Heads!' : 'Tails!') 23 | }); 24 | } 25 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/eff.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage, 6 | DiscordUser 7 | } 8 | } = require('../../BotNeckAPI'); 9 | 10 | module.exports = class EffCommand extends BotNeckCommand { 11 | get Command() { return 'f'; } 12 | get Description() { return 'Press F to pay respects'; } 13 | get Usage() { return 'f [person/thing]'; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | let user = DiscordUser.current; 22 | if(BotNeckCommand.getNumberOfArguments(args)) 23 | message.Content = `**${user.Username}** has paid their respect for **${BotNeckCommand.getArgumentsAsString(args)}**`; 24 | else 25 | message.Content = `**${user.Username}** has paid their respect`; 26 | } 27 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/embed.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckLog, 4 | BotNeckPresets, 5 | DiscordAPI: { 6 | DiscordEmbed, 7 | DiscordClientMessage 8 | } 9 | } = require('../../BotNeckAPI'); 10 | const Config = require('../config'); 11 | 12 | module.exports = class EmbedCommand extends BotNeckCommand { 13 | get Command() { return 'embed'; } 14 | get Description() { return 'Creates and sends an embed based on parameters'; } 15 | get Usage() { return 'embed json=\'{json code here}\' color=0x0061ff footer.text=\'Footer\' field.name1=\'value1\' [description]'; } 16 | 17 | get DefaultEmbed() { 18 | return { 19 | title: 'BotNeck Embed', 20 | type: 'rich', 21 | description: 'This message is from the BotNeck Bot!', 22 | color: 0x0061ff, 23 | url: 'https://github.com/AtiLion/BotNeck-Bot', 24 | timestamp: 'now', 25 | footer: { 26 | text: 'Powered by BotNeck Bot', 27 | icon_url: '', 28 | }, 29 | author: { 30 | name: 'AtiLion', 31 | url: 'https://github.com/AtiLion', 32 | icon_url: 'https://avatars3.githubusercontent.com/u/20825809?s=460&v=4', 33 | }, 34 | fields: [] 35 | }; 36 | } 37 | 38 | /** 39 | * This function gets executed when the command is called 40 | * @param {DiscordClientMessage} message The message the client is trying to send 41 | * @param {any} args The arguments of the command 42 | */ 43 | execute(message, args) { 44 | BotNeckLog.log('Executed embed command:', message.Content); 45 | message.Content = ''; 46 | 47 | let embed = this.DefaultEmbed; 48 | if(args['load'] && Config.Instance.SavedEmbeds[args['load']]) 49 | embed = JSON.parse(JSON.stringify(Config.Instance.SavedEmbeds[args['load']])); 50 | if(args['json']) { 51 | try { embed = JSON.parse(args['json']); } 52 | catch (err) { 53 | BotNeckLog.error(err, 'Failed to parse JSON!'); 54 | return BotNeckPresets.createError(message, 'Failed to parse JSON! Check console.'); 55 | } 56 | } 57 | 58 | let temp = BotNeckCommand.getArgumentsAsString(args); 59 | if(temp && temp.length) embed.description = temp; 60 | for(let key in args) { 61 | if(!isNaN(key)) continue; 62 | if(key === 'load' || key === 'save') continue; 63 | 64 | let keySteps = key.split('.'); 65 | 66 | if(keySteps[0] === 'fields') continue; 67 | if(keySteps[0] === 'type') continue; 68 | 69 | if(keySteps[0] !== 'field') { 70 | let lastObj = embed; 71 | for(let i = 0; i < keySteps.length; i++) { 72 | let keyStep = keySteps[i]; 73 | 74 | if(i + 1 >= keySteps.length) { 75 | if(args[key]) 76 | lastObj[keyStep] = args[key]; 77 | else 78 | delete lastObj[keyStep]; 79 | break; 80 | } 81 | 82 | let targetObj = lastObj[keyStep]; 83 | if(!targetObj) lastObj[keyStep] = targetObj = {}; 84 | lastObj = targetObj; 85 | } 86 | } 87 | else { 88 | let field = { 89 | name: keySteps[1], 90 | value: args[key], 91 | inline: true 92 | } 93 | 94 | embed.fields.push(field); 95 | } 96 | } 97 | if(embed.timestamp) { 98 | if(embed.timestamp == 'now') 99 | embed.timestamp = new Date().toISOString(); 100 | else if(embed.timestamp != '') 101 | embed.timestamp = new Date(embed.timestamp).toISOString(); 102 | } 103 | if(embed.color && !isNaN(embed.color)) embed.color = Number(embed.color); 104 | 105 | if(args['save']) { 106 | Config.Instance.SavedEmbeds[args['save']] = embed; 107 | Config.Instance.save(); 108 | } 109 | message.Embed = new DiscordEmbed(embed); 110 | } 111 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/flip.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | DiscordAPI: { 4 | DiscordClientMessage 5 | } 6 | } = require('../../BotNeckAPI'); 7 | 8 | module.exports = class FlipCommand extends BotNeckCommand { 9 | get Command() { return 'flip'; } 10 | get Description() { return 'Flip the text'; } 11 | get Usage() { return 'flip [text]'; } 12 | get MinimumArguments() { return 1; } 13 | 14 | get CharList() { return 'abcdefghijklmnpqrtuvwxyzABCDEFGHIJKLMNPQRTUVWYZ12345679!&*(),.\''; } 15 | get AltCharList() { return 'ɐqɔpǝɟƃɥᴉɾʞlɯudbɹʇnʌʍxʎz∀qƆpƎℲפHIſʞ˥WNԀQɹ┴∩ΛM⅄ZƖᄅƐㄣϛ9ㄥ6¡⅋*)(\'˙,'; } 16 | 17 | /** 18 | * This function gets executed when the command is called 19 | * @param {DiscordClientMessage} message The message the client is trying to send 20 | * @param {any} args The arguments of the command 21 | */ 22 | execute(message, args) { 23 | let input = BotNeckCommand.getArgumentsAsString(args); 24 | 25 | message.Content = ''; 26 | for(let i = 0; i < input.length; i++) { 27 | let index = this.CharList.indexOf(input[i]); 28 | 29 | if(index < 0) message.Content += input[i]; 30 | else message.Content += this.AltCharList[index]; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/index.js: -------------------------------------------------------------------------------- 1 | const EightBall = require('./8ball'); 2 | const Avatar = require('./avatar'); 3 | const Calculator = require('./calculator'); 4 | const Choose = require('./choose'); 5 | const Coinflip = require('./coinflip'); 6 | const Eff = require('./eff'); 7 | const Flip = require('./flip'); 8 | const Lenny = require('./lenny'); 9 | const Lmgtfy = require('./lmgtfy'); 10 | const Love = require('./love'); 11 | const Regional = require('./regional'); 12 | const Roll = require('./roll'); 13 | const Neko = require('./neko'); 14 | const Wallpaper = require('./wallpaper'); 15 | const AniList = require('./anilist'); 16 | const MAL = require('./mal'); 17 | const Embed = require('./embed'); 18 | const XKCD = require('./xkcd'); 19 | 20 | module.exports = { 21 | EightBall, 22 | Avatar, 23 | Calculator, 24 | Choose, 25 | Coinflip, 26 | Eff, 27 | Flip, 28 | Lenny, 29 | Lmgtfy, 30 | Love, 31 | Regional, 32 | Roll, 33 | Neko, 34 | Wallpaper, 35 | AniList, 36 | MAL, 37 | Embed, 38 | XKCD 39 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/lenny.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | DiscordAPI: { 4 | DiscordClientMessage 5 | } 6 | } = require('../../BotNeckAPI'); 7 | 8 | module.exports = class LennyCommand extends BotNeckCommand { 9 | get Command() { return 'lenny'; } 10 | get Description() { return 'Displays a lenny face'; } 11 | get Usage() { return 'lenny'; } 12 | 13 | /** 14 | * This function gets executed when the command is called 15 | * @param {DiscordClientMessage} message The message the client is trying to send 16 | * @param {any} args The arguments of the command 17 | */ 18 | execute(message, args) { 19 | message.Content = '( ͡° ͜ʖ ͡°)'; 20 | } 21 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/lmgtfy.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | DiscordAPI: { 4 | DiscordClientMessage 5 | } 6 | } = require('../../BotNeckAPI'); 7 | 8 | module.exports = class LmgtfyCommand extends BotNeckCommand { 9 | get Command() { return 'lmgtfy'; } 10 | get Description() { return 'Creates a lmgtfy link for people who can\'t google'; } 11 | get Usage() { return 'lmgtfy '; } 12 | get MinimumArguments() { return 1; } 13 | 14 | get Url() { return 'http://lmgtfy.com/?q='; } 15 | 16 | /** 17 | * This function gets executed when the command is called 18 | * @param {DiscordClientMessage} message The message the client is trying to send 19 | * @param {any} args The arguments of the command 20 | */ 21 | execute(message, args) { 22 | message.Content = this.Url + encodeURIComponent(BotNeckCommand.getArgumentsAsString(args)); 23 | } 24 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/love.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | BotNeckClient, 5 | DiscordAPI: { 6 | DiscordClientMessage, 7 | DiscordUser, 8 | DiscordClientMessageBase 9 | } 10 | } = require('../../BotNeckAPI'); 11 | 12 | module.exports = class LoveCommand extends BotNeckCommand { 13 | get Command() { return 'love'; } 14 | get Description() { return 'Calculates the love between 2 users <3'; } 15 | get Usage() { return 'love [user2]'; } 16 | get MinimumArguments() { return 1; } 17 | 18 | /** 19 | * Converts usernames of the selected users or own user to a text 20 | * @param {String} user1 The user's username or mentioned user 21 | * @param {String} user2 The user's username or mentioned user 22 | * @returns {Promise} The combined string of both user's usernames 23 | */ 24 | convertToText(user1, user2) { 25 | return new Promise((resolve, reject) => { 26 | let text = ''; 27 | let promise_land = []; 28 | 29 | if(user1.startsWith('<@!')) { 30 | promise_land.push(DiscordUser.getFromMention(user1) 31 | .then(user => { 32 | text += user.Username; 33 | })); 34 | } else text += user1; 35 | 36 | if(user2.startsWith('<@!')) { 37 | promise_land.push(DiscordUser.getFromMention(user2) 38 | .then(user => { 39 | text += user.Username; 40 | })); 41 | } else text += user2 42 | 43 | Promise.all(promise_land) 44 | .then(() => resolve(text)).catch(reject); 45 | }); 46 | } 47 | 48 | /** 49 | * This function gets executed when the command is called 50 | * @param {DiscordClientMessage} message The message the client is trying to send 51 | * @param {any} args The arguments of the command 52 | */ 53 | execute(message, args) { 54 | let user1 = args[0]; 55 | let user2 = (args[1] || DiscordUser.current.Username); 56 | 57 | BotNeckPresets.createInfo(message, 'Finding the love in the air...'); 58 | BotNeckClient.runAfterMessage(this.convertToText(user1, user2), (dMessage, text) => { 59 | let process_num = ''; 60 | let processed_num = ''; 61 | 62 | process_num = (text.match(/l/gi) || []).length.toString() + (text.match(/o/gi) || []).length.toString() + (text.match(/v/gi) || []).length.toString() + (text.match(/e/gi) || []).length.toString() + (text.match(/s/gi) || []).length.toString(); 63 | while(process_num.length > 2) 64 | { 65 | for(let i = 0; i < process_num.length - 1; i++) 66 | processed_num += (Number(process_num[i]) + Number(process_num[i + 1])).toString(); 67 | 68 | process_num = processed_num; 69 | processed_num = ''; 70 | } 71 | 72 | if(args['cheat']) 73 | process_num = args['cheat']; 74 | 75 | let baseMessage = new DiscordClientMessageBase(); 76 | BotNeckPresets.createBase(baseMessage, { 77 | Title: 'Love Calculator', 78 | Description: `How much do ${user1} and ${user2} love each other?` 79 | }); 80 | baseMessage.Embed.addField('Result', process_num + '%'); 81 | dMessage.editMessage(baseMessage); 82 | }); 83 | } 84 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/mal.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const { 3 | BotNeckCommand, 4 | BotNeckClient, 5 | BotNeckPresets, 6 | BotNeckLog, 7 | DiscordAPI: { 8 | DiscordClientMessage, 9 | DiscordClientMessageBase 10 | } 11 | } = require('../../BotNeckAPI'); 12 | 13 | module.exports = class MALCommand extends BotNeckCommand { 14 | get Command() { return 'mal'; } 15 | get Description() { return 'Looks up a specific anime/manga'; } 16 | get Usage() { return 'mal [index]'; } 17 | get MinimumArguments() { return 2; } 18 | 19 | get APIUrl() { return 'https://api.jikan.moe/v3/'; } 20 | get APITypes() { return ['anime', 'manga']; } 21 | 22 | /** 23 | * Gets a list of MAL anime/manga titles 24 | * @param {String} url The url to send the request to 25 | * @param {String} title The title of the specific anime/manga 26 | * @returns {Promise} Request output 27 | */ 28 | getMALList(url, title) { 29 | return new Promise((resolve) => { 30 | let options = { 31 | url: url, 32 | method: 'GET', 33 | qs: { 34 | q: title, 35 | page: 1 36 | }, 37 | json: true 38 | } 39 | request(options, (err, response, body) => resolve({ err, response, body })); 40 | }); 41 | } 42 | /** 43 | * Gets MAL information on anime/manga of a specific title 44 | * @param {String} url The url to send the request to 45 | * @returns {Promise} Request output 46 | */ 47 | getMALInfo(url) { 48 | return new Promise((resolve) => { 49 | let options = { 50 | url: url, 51 | method: 'GET', 52 | json: true 53 | } 54 | request(options, (err, response, body) => resolve({ err, response, body })); 55 | }); 56 | } 57 | 58 | /** 59 | * Generates the anime reply message from MAL's data 60 | * @param {any} data The anime data 61 | * @param {DiscordClientMessageBase} message The reply message instance 62 | */ 63 | generateAnime(data, message) { 64 | BotNeckPresets.createBase(message, { 65 | Title: 'MAL Anime', 66 | Thumbnail: { 67 | Url: data.image_url 68 | }, 69 | Author: { 70 | Name: data.title, 71 | Url: data.url, 72 | IconUrl: 'https://vignette.wikia.nocookie.net/central/images/9/9a/Mal-icon.png/revision/latest?cb=20170415235719' 73 | }, 74 | Description: data.synopsis, 75 | Url: data.url, 76 | Fields: [ 77 | { 78 | Name: 'English Title', 79 | Value: (data.title_english == null ? 'None' : data.title_english), 80 | Inline: true, 81 | }, 82 | { 83 | Name: 'Japanese Title', 84 | Value: (data.title_japanese == null ? 'None' : data.title_japanese), 85 | Inline: true, 86 | }, 87 | { 88 | Name: 'Type', 89 | Value: (data.type == null ? 'None' : data.type), 90 | Inline: true, 91 | }, 92 | { 93 | Name: 'Episodes', 94 | Value: (data.episodes == null ? 'None' : data.episodes), 95 | Inline: true, 96 | }, 97 | { 98 | Name: 'Status', 99 | Value: (data.status == null ? 'None' : data.status), 100 | Inline: true, 101 | }, 102 | { 103 | Name: 'Premiered', 104 | Value: (data.premiered == null ? 'None' : data.premiered), 105 | Inline: true, 106 | }, 107 | { 108 | Name: 'Score', 109 | Value: (data.score == null ? 'None' : data.score.toString()), 110 | Inline: true, 111 | }, 112 | { 113 | Name: 'Members', 114 | Value: (data.members == null ? 'None' : data.members.toString()), 115 | Inline: true, 116 | }, 117 | { 118 | Name: 'Aired', 119 | Value: (data.aired.string == null ? 'None' : data.aired.string), 120 | Inline: true, 121 | }, 122 | { 123 | Name: 'Duration', 124 | Value: (data.duration == null ? 'None' : data.duration), 125 | Inline: true, 126 | }, 127 | { 128 | Name: 'Rating', 129 | Value: (data.rating == null ? 'None' : data.rating), 130 | } 131 | ] 132 | }); 133 | } 134 | /** 135 | * Generates the manga reply message from MAL's data 136 | * @param {any} data The manga data 137 | * @param {DiscordClientMessageBase} message The reply message instance 138 | */ 139 | generateManga(data, message) { 140 | BotNeckPresets.createBase(message, { 141 | Title: 'MAL MAL', 142 | Thumbnail: { 143 | Url: data.image_url 144 | }, 145 | Author: { 146 | Name: data.title, 147 | Url: data.url, 148 | IconUrl: 'https://vignette.wikia.nocookie.net/central/images/9/9a/Mal-icon.png/revision/latest?cb=20170415235719' 149 | }, 150 | Description: data.synopsis, 151 | Url: data.url, 152 | Fields: [ 153 | { 154 | Name: 'English Title', 155 | Value: (data.title_english == null ? 'None' : data.title_english), 156 | Inline: true, 157 | }, 158 | { 159 | Name: 'Japanese Title', 160 | Value: (data.title_japanese == null ? 'None' : data.title_japanese), 161 | Inline: true, 162 | }, 163 | { 164 | Name: 'Type', 165 | Value: (data.type == null ? 'None' : data.type), 166 | Inline: true, 167 | }, 168 | { 169 | Name: 'Volumes', 170 | Value: (data.volumes == null ? 'None' : data.volumes), 171 | Inline: true, 172 | }, 173 | { 174 | Name: 'Chapters', 175 | Value: (data.chapters == null ? 'None' : data.chapters), 176 | Inline: true, 177 | }, 178 | { 179 | Name: 'Status', 180 | Value: (data.status == null ? 'None' : data.status), 181 | Inline: true, 182 | }, 183 | { 184 | Name: 'Score', 185 | Value: (data.score == null ? 'None' : data.score.toString()), 186 | Inline: true, 187 | }, 188 | { 189 | Name: 'Members', 190 | Value: (data.members == null ? 'None' : data.members.toString()), 191 | Inline: true, 192 | }, 193 | { 194 | Name: 'Published', 195 | Value: (data.published.string == null ? 'None' : data.published.string), 196 | Inline: true, 197 | } 198 | ] 199 | }); 200 | } 201 | 202 | /** 203 | * This function gets executed when the command is called 204 | * @param {DiscordClientMessage} message The message the client is trying to send 205 | * @param {any} args The arguments of the command 206 | */ 207 | execute(message, args) { 208 | let type = args[0].toLowerCase(); 209 | let title = args[1]; 210 | let index = 0; 211 | 212 | if(!this.APITypes.includes(type)) 213 | return BotNeckPresets.createError(message, 'Only anime, manga, person and character allowed as type!'); 214 | if(BotNeckCommand.getNumberOfArguments(args) > 2 && !isNaN(args[2])) 215 | index = Number(args[2]); 216 | 217 | BotNeckPresets.createInfo(message, 'Looking it up on MAL...'); 218 | BotNeckClient.runAfterMessage(this.getMALList(this.APIUrl + 'search/' + type, title), ((dMessage, { err, response, body }) => { 219 | let replyMessage = new DiscordClientMessageBase(); 220 | 221 | if(err || response.statusCode != 200) { 222 | BotNeckPresets.createError(replyMessage, 'Failed to send search request to MAL!'); 223 | return dMessage.editMessage(replyMessage); 224 | } 225 | if(!body) { 226 | BotNeckPresets.createError(replyMessage, 'Failed to parse MAL data!'); 227 | return dMessage.editMessage(replyMessage); 228 | } 229 | 230 | let id = body['results'][index]['mal_id']; 231 | this.getMALInfo(this.APIUrl + type + '/' + id) 232 | .then((({ err, response, body }) => { 233 | if(err || response.statusCode != 200) { 234 | BotNeckPresets.createError(replyMessage, 'Failed to get anime/manga data!'); 235 | return dMessage.editMessage(replyMessage); 236 | } 237 | if(!body) { 238 | BotNeckPresets.createError(replyMessage, 'Failed to parse MAL data!'); 239 | return dMessage.editMessage(replyMessage); 240 | } 241 | 242 | if(type === 'anime') this.generateAnime(body, replyMessage); 243 | else if(type === 'manga') this.generateManga(body, replyMessage); 244 | else BotNeckPresets.createError(replyMessage, 'Failed to use correct type'); 245 | dMessage.editMessage(replyMessage); 246 | }).bind(this)) 247 | .catch(err => { 248 | BotNeckPresets.createError(replyMessage, 'Failed to get anime/manga data!'); 249 | BotNeckLog.error(err, 'Failed to get anime/manga data!'); 250 | return dMessage.editMessage(replyMessage); 251 | }); 252 | }).bind(this)); 253 | } 254 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/neko.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const { 3 | BotNeckCommand, 4 | BotNeckClient, 5 | BotNeckPresets, 6 | DiscordAPI: { 7 | DiscordClientMessage, 8 | DiscordClientMessageBase 9 | } 10 | } = require('../../BotNeckAPI'); 11 | 12 | module.exports = class NekoCommand extends BotNeckCommand { 13 | get Command() { return 'neko'; } 14 | get Description() { return 'Sends a random neko image [NSFW]'; } 15 | get Usage() { return 'neko'; } 16 | 17 | /** 18 | * Gets a random neko image from nekos.life 19 | * @returns {Promise} Request output 20 | */ 21 | getImage() { 22 | return new Promise((resolve) => { 23 | request({ url: 'https://nekos.life/api/v2/img/neko', json: true }, (err, response, body) => resolve({ err, response, body })); 24 | }); 25 | } 26 | 27 | /** 28 | * This function gets executed when the command is called 29 | * @param {DiscordClientMessage} message The message the client is trying to send 30 | * @param {any} args The arguments of the command 31 | */ 32 | execute(message, args) { 33 | BotNeckPresets.createInfo(message, 'Finding you a neko...'); 34 | BotNeckClient.runAfterMessage(this.getImage(), (dMessage, { err, response, body }) => { 35 | let replyMessage = new DiscordClientMessageBase(); 36 | 37 | if(err || response.statusCode != 200 || !body.url) { 38 | BotNeckPresets.createError(replyMessage, 'Failed to get a proper response from nekos.life'); 39 | return dMessage.editMessage(replyMessage); 40 | } 41 | 42 | BotNeckPresets.createBase(replyMessage, { 43 | Title: 'Neko finder', 44 | Image: { 45 | Url: body.url 46 | } 47 | }); 48 | dMessage.editMessage(replyMessage); 49 | }); 50 | } 51 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/regional.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | DiscordAPI: { 4 | DiscordClientMessage 5 | } 6 | } = require('../../BotNeckAPI'); 7 | 8 | module.exports = class RegionalCommand extends BotNeckCommand { 9 | get Command() { return 'regional'; } 10 | get Description() { return 'Changes all letters into regionals'; } 11 | get Usage() { return 'regional '; } 12 | get MinimumArguments() { return 1; } 13 | 14 | get Regionals() { 15 | return {'a': ':regional_indicator_a:', 'b': ':regional_indicator_b:', 16 | 'c': ':regional_indicator_c:', 17 | 'd': ':regional_indicator_d:', 'e': ':regional_indicator_e:', 18 | 'f': ':regional_indicator_f:', 19 | 'g': ':regional_indicator_g:', 'h': ':regional_indicator_h:', 20 | 'i': ':regional_indicator_i:', 21 | 'j': ':regional_indicator_j:', 'k': ':regional_indicator_k:', 22 | 'l': ':regional_indicator_l:', 23 | 'm': ':regional_indicator_m:', 'n': ':regional_indicator_n:', 24 | 'o': ':regional_indicator_o:', 25 | 'p': ':regional_indicator_p:', 'q': ':regional_indicator_q:', 26 | 'r': ':regional_indicator_r:', 27 | 's': ':regional_indicator_s:', 't': ':regional_indicator_t:', 28 | 'u': ':regional_indicator_u:', 29 | 'v': ':regional_indicator_v:', 'w': ':regional_indicator_w:', 30 | 'x': ':regional_indicator_x:', 31 | 'y': ':regional_indicator_y:', 'z': ':regional_indicator_z:', 32 | '0': '0⃣', '1': '1⃣', '2': '2⃣', '3': '3⃣', 33 | '4': '4⃣', '5': '5⃣', '6': '6⃣', '7': '7⃣', '8': '8⃣', '9': '9⃣', '!': '\u2757', 34 | '?': '\u2753', ' ': ' '}; 35 | } 36 | 37 | /** 38 | * This function gets executed when the command is called 39 | * @param {DiscordClientMessage} message The message the client is trying to send 40 | * @param {any} args The arguments of the command 41 | */ 42 | execute(message, args) { 43 | let input = BotNeckCommand.getArgumentsAsString(args); 44 | 45 | message.Content = ''; 46 | for(let i = 0; i < input.length; i++) 47 | { 48 | if(this.Regionals[input[i].toLowerCase()]) 49 | message.Content += this.Regionals[input[i].toLowerCase()] + '\u200b'; 50 | else 51 | message.Content += input[i]; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/roll.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class RollCommand extends BotNeckCommand { 10 | get Command() { return 'roll'; } 11 | get Description() { return 'Rolls the dice'; } 12 | get Usage() { return 'roll [max value] dice=[number of dice to roll]'; } 13 | 14 | /** 15 | * This function gets executed when the command is called 16 | * @param {DiscordClientMessage} message The message the client is trying to send 17 | * @param {any} args The arguments of the command 18 | */ 19 | execute(message, args) { 20 | let maxValue = 6; 21 | if(args[0] && !isNaN(args[0])) 22 | maxValue = Number(args[0]); 23 | 24 | let rollAmount = 1; 25 | if(args['dice'] && !isNaN(args['dice'])) 26 | rollAmount = Number(args['dice']); 27 | 28 | let diceValues = []; 29 | let sum = 0; 30 | for(let i = 0; i < rollAmount; i++) { 31 | let num = Math.floor(Math.random() * maxValue + 1); 32 | 33 | diceValues.push(num); 34 | sum += num; 35 | } 36 | BotNeckPresets.createBase(message, { 37 | Title: 'Roll the dice', 38 | Description: `The dices have rolled: ${diceValues.join(', ')}`, 39 | Fields: [ 40 | { 41 | Name: 'Sum of dice', 42 | Value: String(sum), 43 | Inline: true 44 | }, 45 | { 46 | Name: 'Max roll value', 47 | Value: String(maxValue), 48 | Inline: true 49 | }, 50 | { 51 | Name: 'Number of dice', 52 | Value: String(rollAmount), 53 | Inline: true 54 | } 55 | ] 56 | }); 57 | } 58 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/wallpaper.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const { 3 | BotNeckCommand, 4 | BotNeckClient, 5 | BotNeckPresets, 6 | DiscordAPI: { 7 | DiscordClientMessage, 8 | DiscordClientMessageBase 9 | } 10 | } = require('../../BotNeckAPI'); 11 | 12 | module.exports = class WallpaperCommand extends BotNeckCommand { 13 | get Command() { return 'wallpaper'; } 14 | get Description() { return 'Find the perfect anime wallpaper [NSFW]'; } 15 | get Usage() { return 'wallpaper'; } 16 | 17 | /** 18 | * Gets a random wallpaper image from nekos.life 19 | * @returns {Promise} Request output 20 | */ 21 | getImage() { 22 | return new Promise((resolve) => { 23 | request({ url: 'https://nekos.life/api/v2/img/wallpaper', json: true }, (err, response, body) => resolve({ err, response, body })); 24 | }); 25 | } 26 | 27 | /** 28 | * This function gets executed when the command is called 29 | * @param {DiscordClientMessage} message The message the client is trying to send 30 | * @param {any} args The arguments of the command 31 | */ 32 | execute(message, args) { 33 | BotNeckPresets.createInfo(message, 'Finding you a wallpaper...'); 34 | BotNeckClient.runAfterMessage(this.getImage(), (dMessage, { err, response, body }) => { 35 | let replyMessage = new DiscordClientMessageBase(); 36 | 37 | if(err || response.statusCode != 200 || !body.url) { 38 | BotNeckPresets.createError(replyMessage, 'Failed to get a proper response from nekos.life'); 39 | return dMessage.editMessage(replyMessage); 40 | } 41 | 42 | BotNeckPresets.createBase(replyMessage, { 43 | Title: 'Wallpaper finder', 44 | Image: { 45 | Url: body.url 46 | } 47 | }); 48 | dMessage.editMessage(replyMessage); 49 | }); 50 | } 51 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/Commands/xkcd.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const { 3 | BotNeckCommand, 4 | BotNeckPresets, 5 | BotNeckClient, 6 | BotNeckLog, 7 | DiscordAPI: { 8 | DiscordClientMessage, 9 | DiscordClientMessageBase 10 | } 11 | } = require('../../BotNeckAPI'); 12 | 13 | module.exports = class xkcdCommand extends BotNeckCommand { 14 | get Command() { return 'xkcd'; } 15 | get Description() { return 'Gets a random comic from xkcd'; } 16 | get Usage() { return 'xkcd'; } 17 | 18 | /** 19 | * Gets the XKCD feed 20 | * @returns {Promise} Request output 21 | */ 22 | getFeed() { 23 | return new Promise((resolve) => { 24 | request({ url: 'https://xkcd.com/atom.xml' }, (err, response, body) => resolve({ err, response, body })); 25 | }); 26 | } 27 | 28 | /** 29 | * This function gets executed when the command is called 30 | * @param {DiscordClientMessage} message The message the client is trying to send 31 | * @param {any} args The arguments of the command 32 | */ 33 | execute(message, args) { 34 | BotNeckPresets.createInfo(message, 'Your XKCD comic is being loaded...'); 35 | BotNeckClient.runAfterMessage(this.getFeed(), (dMessage, { err, response, body }) => { 36 | let replyMessage = new DiscordClientMessageBase(); 37 | 38 | if(err || response.statusCode != 200 || !body) { 39 | BotNeckPresets.createError(replyMessage, 'Failed to get a proper response from xkcd feed!'); 40 | return dMessage.editMessage(replyMessage); 41 | } 42 | 43 | let parser = new DOMParser(); 44 | let parsedDom = parser.parseFromString(body, 'application/xml'); 45 | 46 | if(parsedDom.documentElement.nodeName === 'parsererror') { 47 | BotNeckPresets.createError(replyMessage, 'Failed to parse xkcd feed!'); 48 | return dMessage.editMessage(replyMessage); 49 | } 50 | 51 | let firstEntry = parsedDom.documentElement.querySelector('entry > id'); 52 | if(!firstEntry) { 53 | BotNeckPresets.createError(replyMessage, 'Failed to find xkcd entry'); 54 | return dMessage.editMessage(replyMessage); 55 | } 56 | 57 | let urlSplit = firstEntry.innerHTML.split('/'); 58 | if(urlSplit.length < 3) { 59 | BotNeckPresets.createError(replyMessage, 'Failed to parse xkcd id!'); 60 | return dMessage.editMessage(replyMessage); 61 | } 62 | 63 | let highestNumber = Number(urlSplit[urlSplit.length - 2]); 64 | if(isNaN(highestNumber)) { 65 | BotNeckPresets.createError(replyMessage, 'Failed to parse xkcd id!'); 66 | return dMessage.editMessage(replyMessage); 67 | } 68 | 69 | replyMessage.Content = `Here is your XKCD webcomic: https://xkcd.com/${Math.floor((Math.random() * highestNumber) + 1)}/`; 70 | dMessage.editMessage(replyMessage); 71 | }); 72 | } 73 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/config.js: -------------------------------------------------------------------------------- 1 | const { BotNeckConfig, BotNeckLog } = require('../BotNeckAPI'); 2 | 3 | let _instance = null; 4 | module.exports = class Config extends BotNeckConfig { 5 | constructor() { 6 | super('BotNeckFun'); 7 | 8 | if(_instance) { 9 | BotNeckLog.error('Config has already been loaded!'); 10 | return; 11 | } 12 | _instance = this; 13 | } 14 | 15 | /** 16 | * Returns the instance of the current module's config 17 | * @returns {Config} The instance of the current module's config 18 | */ 19 | static get Instance() { return _instance; } 20 | 21 | /** 22 | * The object of saved embeds to load for custom embed making 23 | * @returns {any} 24 | */ 25 | get SavedEmbeds() { return this.config.savedEmbeds = (this.config.savedEmbeds || {}); } 26 | set SavedEmbeds(savedEmbeds) { this.config.savedEmbeds = savedEmbeds; } 27 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckFun/index.js: -------------------------------------------------------------------------------- 1 | const { BotNeckModule, BotNeckClient, BotNeckCommand, BotNeckConfig, BotNeckLog } = require('../BotNeckAPI'); 2 | const Commands = require('./Commands'); 3 | const Config = require('./config'); 4 | 5 | module.exports = class BotNeckFun extends BotNeckModule { 6 | constructor() { 7 | super(); 8 | 9 | this.loadedCommands = []; 10 | 11 | BotNeckConfig.create(Config) 12 | .then(() => BotNeckLog.log('Successfully loaded BotNeckFun config!')) 13 | .catch(err => BotNeckLog.error(err, 'Failed to load BotNeckFun config!')); 14 | } 15 | 16 | get Name() { return 'BotNeck Fun'; } 17 | get Description() { return 'Fun commmands for BotNeck that are just fun to use'; } 18 | get Version() { return BotNeckClient.version; } 19 | get Author() { return 'AtiLion'; } 20 | 21 | onLoad() { 22 | for(let commandKey in Commands) { 23 | let command = new Commands[commandKey](); 24 | BotNeckCommand.registerCommand(command); 25 | this.loadedCommands.push(command); 26 | } 27 | } 28 | onUnload() { 29 | for(let command of this.loadedCommands) 30 | BotNeckCommand.unregisterCommand(command); 31 | this.loadedCommands = []; 32 | } 33 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/Help.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class HelpCommand extends BotNeckCommand { 10 | get Command() { return 'help'; } 11 | get Description() { return 'Displays all of the available commands and their descriptions'; } 12 | get Usage() { return 'help [command]'; } 13 | 14 | /** 15 | * This function gets executed when the command is called 16 | * @param {DiscordClientMessage} message The message the client is trying to send 17 | * @param {any} args The arguments of the command 18 | */ 19 | execute(message, args) { 20 | // Display all commands 21 | if(!args[0]) { 22 | BotNeckPresets.createInfo(message, 'A list of commands within BotNeck Bot'); 23 | 24 | for(let command of BotNeckCommand.commandList) 25 | message.Embed.addField(command.Command, command.Description, false); 26 | return; 27 | } 28 | 29 | // Show specified command information 30 | let potentialCommands = BotNeckCommand.commandList.filter(command => command.Command.toLowerCase() === args[0].toLowerCase()); 31 | if(potentialCommands.length < 1) 32 | return BotNeckPresets.createError(message, 'Specified command not found!'); 33 | 34 | if(potentialCommands.length === 1) { 35 | BotNeckPresets.createInfo(message, potentialCommands[0].Description); 36 | message.Embed.addField('Usage', potentialCommands[0].Usage, false); 37 | return; 38 | } 39 | BotNeckPresets.createInfo(message, 'List of descriptions and commands that match the search'); 40 | for(let command of potentialCommands) 41 | message.Embed.addField(command.Command, command.Description, false); 42 | } 43 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/Prefix.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | const { BotNeckParser } = require('../../../core/configParsers'); 9 | 10 | module.exports = class PrefixCommand extends BotNeckCommand { 11 | get Command() { return 'prefix'; } 12 | get Description() { return 'Sets or gets the current prefix for commands'; } 13 | get Usage() { return 'prefix [new prefix]'; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | if(BotNeckCommand.getNumberOfArguments(args) > 0) { 22 | BotNeckParser.Instance.Prefix = args[0]; 23 | BotNeckParser.Instance.save(); 24 | return BotNeckPresets.createInfo(message, 'Prefix change to: ' + BotNeckParser.Instance.Prefix); 25 | } 26 | 27 | BotNeckPresets.createInfo(message, 'The current prefix is: ' + BotNeckParser.Instance.Prefix); 28 | } 29 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/Reload.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckModule, 4 | BotNeckPresets, 5 | DiscordAPI: { 6 | DiscordClientMessage 7 | } 8 | } = require('../../BotNeckAPI'); 9 | 10 | module.exports = class ReloadCommand extends BotNeckCommand { 11 | get Command() { return 'reload'; } 12 | get Description() { return 'Reloads all modules'; } 13 | get Usage() { return 'reload'; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | BotNeckPresets.createInfo(message, 'All commands reloaded!'); 22 | BotNeckModule.reloadModules(); 23 | } 24 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/ReloadConfig.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | const { BotNeckParser } = require('../../../core/configParsers'); 9 | 10 | module.exports = class ReloadConfigCommand extends BotNeckCommand { 11 | get Command() { return 'reloadConfig'; } 12 | get Description() { return 'Reload BotNeck configuration'; } 13 | get Usage() { return 'reloadConfig'; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | if(!BotNeckParser.Instance) return BotNeckPresets.createError(message, 'Failed to find BotNeck configuration instance'); 22 | 23 | BotNeckParser.Instance.reload(); 24 | BotNeckPresets.createInfo(message, 'BotNeck configuration reloaded!'); 25 | /*BotNeckPresets.createInfo(message, 'List of BotNeck configurations'); 26 | message.Embed.addField('Prefix', 'The prefix used by the bot to determine what is a command'); 27 | message.Embed.addField('ErrorOnCommandNotFound', 'Should an error embed be displayed if the given command is not found'); 28 | message.Embed.addField('ErrorOnNotEnoughArguments', 'Should an error embed be displayed if the given command was not given enough arguments'); 29 | message.Embed.addField('IsMaster', 'Is the current Discord client the master (used for handling remote commands)');*/ 30 | } 31 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/Remote.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | const { BotNeckParser } = require('../../../core/configParsers'); 9 | 10 | module.exports = class RemoteCommand extends BotNeckCommand { 11 | get Command() { return 'remote'; } 12 | get Description() { return 'Enables or disables remote commands for the current client'; } 13 | get Usage() { return 'remote [enable/disable]'; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | if(BotNeckCommand.getNumberOfArguments(args) > 0) { 22 | let arg = args[0].toLowerCase(); 23 | 24 | if(arg === 'enable') { 25 | BotNeckParser.Instance.IsMaster = true; 26 | BotNeckParser.Instance.save(); 27 | return BotNeckPresets.createInfo(message, 'Enabled remote commands on the current client'); 28 | } 29 | else if(arg === 'disable') { 30 | BotNeckParser.Instance.IsMaster = false; 31 | BotNeckParser.Instance.save(); 32 | return BotNeckPresets.createInfo(message, 'Disabled remote commands on the current client'); 33 | } 34 | else return BotNeckPresets.createInfo(message, 'Unknown remote command option'); 35 | } 36 | 37 | if(BotNeckParser.Instance.IsMaster) 38 | BotNeckPresets.createInfo(message, 'Remote commands are enabled on the current client'); 39 | else 40 | BotNeckPresets.createInfo(message, 'Remote commands are disabled on the current client'); 41 | } 42 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/TextOnly.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | const { BotNeckParser } = require('../../../core/configParsers'); 9 | 10 | module.exports = class TextOnlyCommand extends BotNeckCommand { 11 | get Command() { return 'textOnly'; } 12 | get Description() { return 'Enables or disables text only mode for the current client'; } 13 | get Usage() { return 'textOnly [enable/disable]'; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | if(BotNeckCommand.getNumberOfArguments(args) > 0) { 22 | let arg = args[0].toLowerCase(); 23 | 24 | if(arg === 'enable') { 25 | BotNeckParser.Instance.TextOnly = true; 26 | BotNeckParser.Instance.save(); 27 | return BotNeckPresets.createInfo(message, 'Enabled text only mode for the current client'); 28 | } 29 | else if(arg === 'disable') { 30 | BotNeckParser.Instance.TextOnly = false; 31 | BotNeckParser.Instance.save(); 32 | return BotNeckPresets.createInfo(message, 'Disabled text only mode for the current client'); 33 | } 34 | else return BotNeckPresets.createInfo(message, 'Unknown text only command option'); 35 | } 36 | 37 | if(BotNeckParser.Instance.TextOnly) 38 | BotNeckPresets.createInfo(message, 'Text only mode is enabled on the current client'); 39 | else 40 | BotNeckPresets.createInfo(message, 'Text only mode is disabled on the current client'); 41 | } 42 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/Usage.js: -------------------------------------------------------------------------------- 1 | const { 2 | BotNeckCommand, 3 | BotNeckPresets, 4 | DiscordAPI: { 5 | DiscordClientMessage 6 | } 7 | } = require('../../BotNeckAPI'); 8 | 9 | module.exports = class UsageCommand extends BotNeckCommand { 10 | get Command() { return 'usage'; } 11 | get Description() { return 'Displays the specified command usage'; } 12 | get Usage() { return 'usage '; } 13 | get MinimumArguments() { return 1; } 14 | 15 | /** 16 | * This function gets executed when the command is called 17 | * @param {DiscordClientMessage} message The message the client is trying to send 18 | * @param {any} args The arguments of the command 19 | */ 20 | execute(message, args) { 21 | let potentialCommands = BotNeckCommand.commandList.filter(command => command.Command.toLowerCase() === args[0].toLowerCase()); 22 | if(potentialCommands.length < 1) 23 | return BotNeckPresets.createError(message, 'Specified command not found!'); 24 | 25 | if(potentialCommands.length === 1) 26 | return BotNeckPresets.createInfo(message, potentialCommands[0].Usage); 27 | BotNeckPresets.createInfo(message, 'List of usages and commands that match the search'); 28 | for(let command of potentialCommands) 29 | message.Embed.addField(command.Command, command.Usage, false); 30 | } 31 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/Commands/index.js: -------------------------------------------------------------------------------- 1 | const Help = require('./Help'); 2 | const Usage = require('./Usage'); 3 | const Reload = require('./Reload'); 4 | const ReloadConfig = require('./ReloadConfig'); 5 | const Prefix = require('./Prefix'); 6 | //const Remote = require('./Remote'); 7 | const TextOnly = require('./TextOnly'); 8 | 9 | module.exports = { 10 | Help, 11 | Usage, 12 | Reload, 13 | ReloadConfig, 14 | Prefix, 15 | //Remote, 16 | TextOnly 17 | } -------------------------------------------------------------------------------- /BotNeckBot/modules/BotNeckUtils/index.js: -------------------------------------------------------------------------------- 1 | const { BotNeckModule, BotNeckClient, BotNeckCommand } = require('../BotNeckAPI'); 2 | const Commands = require('./Commands'); 3 | 4 | module.exports = class BotNeckUtils extends BotNeckModule { 5 | constructor() { 6 | super(); 7 | 8 | this.loadedCommands = []; 9 | } 10 | 11 | get Name() { return 'BotNeck Utils'; } 12 | get Description() { return 'Utilities/Commands for BotNeck that are essential to functionality'; } 13 | get Version() { return BotNeckClient.version; } 14 | get Author() { return 'AtiLion'; } 15 | 16 | onLoad() { 17 | for(let commandKey in Commands) { 18 | let command = new Commands[commandKey](); 19 | BotNeckCommand.registerCommand(command); 20 | this.loadedCommands.push(command); 21 | } 22 | } 23 | onUnload() { 24 | for(let command of this.loadedCommands) 25 | BotNeckCommand.unregisterCommand(command); 26 | this.loadedCommands = []; 27 | } 28 | } -------------------------------------------------------------------------------- /BotNeckBot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botneckbot", 3 | "version": "3.0.0", 4 | "description": "Adds selfbot commands to the Discord client.", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/AtiLion/BotNeck-Bot.git" 10 | }, 11 | "keywords": [ 12 | "Discord", 13 | "BotNeck", 14 | "SelfBot" 15 | ], 16 | "author": "AtiLion", 17 | "license": "GPL-3.0", 18 | "bugs": { 19 | "url": "https://github.com/AtiLion/BotNeck-Bot/issues" 20 | }, 21 | "homepage": "https://github.com/AtiLion/BotNeck-Bot#readme", 22 | "dependencies": { 23 | "@types/node": "^14.14.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BotNeck Bot 2 | ========== 3 | BotNeck Bot is a self-bot that is implemented into discord as a **BandagedDiscord** plugin. It is designed to be dynamic and easy to use. That means it is extremely easy to add your own commands to the bot without having to modify the code! 4 | 5 | To use the bot you need to use the prefix "->" if you want to change the prefix you can change the configuration located it "BotNeckBot/config/BotNeck.json" file. You can also change it by using the prefix command. 6 | 7 | ---------- 8 | 9 | About 10 | -------- 11 | BotNeck bot offers a unqiue approach to a self-bot as it has multiple modes it can handle. 12 | 13 | The first mode is the "hook" mode, this mode allows you to use and execute commands before they are ever sent to Discord servers, this in turn prevents API spam or "automated message detection" and so it makes the self-bot less detectable by Discord. 14 | 15 | The second mode is the standard mode, this acts equal to how normal self-bots work. When a command is sent the message is gotten by the client, the command is executed and the message is replaced by the command's output. This mode is only used when remote commands are enabled and the command is sent from another client that does not use BotNeck. Also keep in mind this is experimental, so it might not always work correctly. 16 | 17 | BotNeck also contains lots of commands, and more are being added as time goes on, currently the best way to check for commands is with the "help" command. However I will make a list of all the commands soon. 18 | 19 | ---------- 20 | 21 | API 22 | --- 23 | The API is easy to use, it uses JSDoc so you should be able to figure it out easily. I will be including documentation as soon as I can. But for now you can use the already made modules and the examples below. 24 | 25 | **Example of module:** 26 | ```js 27 | const { BotNeckModule, BotNeckClient, BotNeckCommand } = require('../BotNeckAPI'); 28 | 29 | module.exports = class ExampleModule extends BotNeckModule { 30 | constructor() { 31 | super(); 32 | } 33 | 34 | get Name() { return 'Example Module'; } 35 | get Description() { return 'Example description'; } 36 | get Version() { return '1.0.0'; } 37 | get Author() { return 'AtiLion'; } 38 | 39 | onLoad() {} 40 | onUnload() {} 41 | } 42 | ``` 43 | 44 | **Example of command:** 45 | ```js 46 | const { 47 | BotNeckCommand, 48 | DiscordAPI: { 49 | DiscordClientMessage 50 | } 51 | } = require('../../BotNeckAPI'); 52 | 53 | module.exports = class ExampleCommand extends BotNeckCommand { 54 | get Command() { return 'example'; } // The command they need to type in to execute 55 | get Description() { return 'Example command description'; } 56 | get Usage() { return 'example [example optional argument]'; } 57 | 58 | /** 59 | * This function gets executed when the command is called 60 | * @param {DiscordClientMessage} message The message the client is trying to send 61 | * @param {any} args The arguments of the command 62 | */ 63 | execute(message, args) {} 64 | } 65 | ``` 66 | 67 | ---------- 68 | 69 | How to install 70 | -------------- 71 | 1. Download the ZIP file from the Releases section. 72 | 2. Extract all folders and files from the ZIP file into your BetterDiscord plugins. 73 | 3. Restart discord if you have it open. 74 | 4. You are done! 75 | 76 | ---------- 77 | 78 | How to install a module 79 | ----------------------- 80 | > **Warning** 81 | > Some modules may do malicious things without your knowledge. It is highly recommended that you only use modules from a trusted source! 82 | 83 | 1. Go to your BetterDiscord plugins folder. 84 | 2. Inside the folder open the BotNeckBot/modules folder. 85 | 3. Put the module folder/file into the directory 86 | 4. Run the reload command 87 | 5. And you are done! 88 | 89 | ---------- 90 | 91 | Credits 92 | ----------------------- 93 | - [rauenzi](https://github.com/rauenzi) - [BDPluginLibrary](https://github.com/rauenzi/BDPluginLibrary) 94 | -------------------------------------------------------------------------------- /old/backupChat.botneck.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | class backupChat { 4 | constructor() { 5 | this.permissions = [ 'authorized_request' ]; 6 | this.command = 'backupChat'; 7 | this.description = 'Backs up the specified amount of chat into a file'; 8 | this.usage = 'backup [amount of messages] [file name]'; 9 | } 10 | 11 | execute(message, args) { 12 | // Init 13 | delete message['content']; 14 | 15 | // Validate number of args 16 | if(BotNeckAPI.getArgumentNumber(args) < 2) 17 | return message['embed'] = BotNeckAPI.generateError('You need at least 2 arguments for this command!'); 18 | if(isNaN(args[0])) 19 | return message['embed'] = BotNeckAPI.generateError('Specified argument is not a number!'); 20 | if(Number(args[0]) < 0 || Number(args[0]) > 100) 21 | return message['embed'] = BotNeckAPI.generateError('Message number can\'t be lower than 0 and higher than 100!'); 22 | 23 | // Backup 24 | $.ajax({ 25 | type: 'GET', 26 | url: 'https://discordapp.com/api/v6/channels/' + BotNeckAPI.getCurrentChannelId() + '/messages?limit=' + args[0], 27 | dataType: 'json', 28 | contentType: 'application/json', 29 | beforeSend: function (xhr) { BotNeckAPI.setAuthHeader(xhr, APIKey); }, 30 | success: function(data) 31 | { 32 | let chatBackup = path.join(window.bdConfig.dataPath, 'Chat_Backup'); 33 | 34 | if(!fs.existsSync(chatBackup)) 35 | fs.mkdirSync(chatBackup); 36 | 37 | let save = ''; 38 | for(let i in data) 39 | { 40 | if(!data[i].hasOwnProperty('content') || data[i].content == '') 41 | continue; 42 | save += data[i].author.username + '#' + data[i].author.discriminator + ': ' + data[i].content + '\n'; 43 | } 44 | 45 | if(fs.existsSync(chatBackup + '/' + args[1] + '.log')) { 46 | fs.unlink(chatBackup + '/' + args[1] + '.log', () => { fs.writeFile(chatBackup + '/' + args[1] + '.log', save, () => {}); }); 47 | return; 48 | } 49 | fs.writeFile(chatBackup + '/' + args[1] + '.log', save, () => {}); 50 | } 51 | }); 52 | 53 | message['embed'] = { 54 | title: 'Chat Backup', 55 | type: 'rich', 56 | description: 'Chat backed up successfully!', 57 | color: 0x0061ff 58 | } 59 | } 60 | } 61 | --------------------------------------------------------------------------------