<commit_before>/******************************************************************************
 *                                   ~boo~                                    *
 *                                 ‾‾‾‾‾‾‾‾‾                                  *
 * Provides prototypal utilities for OO.                                      *
 *     _________________________________________________________________      *
 *        Copyright (c) 2011 Quildreen Motta // Licenced under MIT/X11        *
 ******************************************************************************/

/// Module boo /////////////////////////////////////////////////////////////////
void function (root) {

    var slice    = [].slice
      , keys     = Object.keys
      , make_obj = Object.create

    //// Function nplugin //////////////////////////////////////////////////////
    //
    //   (object:Obj, mixins:Array) → Obj
    //
    // Copies the own enumerable properties from the mixins over to the
    // ~object~.
    //
    // Only /own/ and /enumerable/ properties present on the ~mixins~
    // will be copied, this ensures the function works the same even in
    // environments that don't support ECMAScript 5 object additions for
    // inspecting properties.
    //
    // This copying is done in order, from left to right, with
    // properties of latter mixins taking priority over the properties
    // of earlier ones.
    //
    // :warning: side-effects
    //   The function will modify ~object~ in-place. For a pure version,
    //   see [[fn:plugin]].
    //
    function nplugin(object, mixins) { var i, len, mixin
        for (i = 0, len = mixins.length; i < len; ++i) {
            mixin = mixins[i]
            keys(mixin).forEach(function(key) {
                object[key] = mixin[key] })}

        return object
    }

    //// Function plugin ///////////////////////////////////////////////////////
    //
    //   (object:Obj, mixins:Array) → Obj
    //
    // Copies the own enumerable properties from the mixin over to the
    // ~object~.
    //
    // This is done without modifying the ~object~ (if you want in-place
    // modification for performance or something, use
    // [[!nplugin]]). Only the /own enumerable/ properties, alongside
    // with the prototype chain of the ~object~ will be carried over to
    // the clone.
    //
    // :see-also:
    //   - [[fn:nplugin]] — the destructive (more reliable) version.
    //   - [[fn:clone]]   — the object cloning routine used for purity.
    //
    function plugin(object, mixins) {
        return nplugin(clone(object), mixins)
    }

    //// Function merge ////////////////////////////////////////////////////////
    //
    //   (mixins:Obj...) → Obj
    //
    // Merges all given mixins into a single, fresh object.
    //
    // Only the own enumerable properties of each mixin are carried over
    // to the fresh object.
    //
    // This copying is done in order, from left to right. If a previous
    // mixin defines a property ~foo~, and a latter mixin also defines a
    // property ~foo~, the latter mixin's property value will overwrite
    // the previous value.
    //
    function merge() {
        return nplugin({}, slice.call(arguments))
    }

    //// Function clone ////////////////////////////////////////////////////////
    //
    //   (object:Obj) → Obj
    //
    // Returns a deep clone of the ~object~, but carrying over only the
    // /own enumerable/ properties and the prototype chain.
    //
    // This is done in order to make it work on ancient environments
    // which don't implement the ECMAScript 5 methods for inspecting an
    // object's properties.
    //
    function clone(object) {
        return object // evilness
    }

    //// Function inherit //////////////////////////////////////////////////////
    //
    //   (proto:Obj, mixins:Obj...) → Obj
    //
    // Creates a new object using ~proto~ as the ~[[Prototype]]~, and
    // extending it with the given mixins.
    //
    // Mixin extension is done from left to right, as described in
    // [[fn:merge]].
    //
    function inherit(proto) {
        return nplugin(make_obj(proto), slice.call(arguments, 1))
    }

    //// Function receiver /////////////////////////////////////////////////////
    //
    //   (base:Obj, message:String[, allow_mixins:Bool = true]) → Obj
    //
    // Finds the closest ancestor of ~base~ which can handle the given
    // ~message~.
    //
    // Sometimes you want to know which ancestor exactly implements a
    // given message (~property~), and in this case a simple ~in~ check
    // will not do. This method will search up over the ancestor chain
    // of a base object, optionally including the mixins that were set
    // on it, and find the first ancestor that can receive that
    // message.
    //
    // The prototype is searched first, then, if the functionality is
    // not found, we move on to searching the mixin list (in the reverse
    // order they were plugged in).
    //
    // If no immediate ancestor implements the message, the function
    // will keep searching up the prototype chain to try to find the
    // requested functionality. If it definitely can't find the any
    // receiver, ~null~ is returned.
    //
    // :note:
    //   the mixin's prototypes and the base object itself are
    //   *not* included in this search chain. Mixins are expected to be
    //   parent-less anyways.
    //

    //// Function receivers ////////////////////////////////////////////////////
    //
    //   (base:Obj, message:String[, allow_mixins:Bool = true]) → Obj
    //
    // Finds all the ancestors of ~base~ who can handle the given
    // ~message~.
    //
    // The algorithm works similarly to [[fn:receiver]], except that it
    // does not stop on the first receiver.
    //
    // :see-also:
    //   - [[fn:receiver]] — finds only the first receiver for the
    //                       message.
    //

    ///// Exports //////////////////////////////////////////////////////////////
    var old, boo

    if (typeof exports == 'undefined') {
        old = root.boo
        boo = root.boo = {}

        ///// Method boo.make_local ////////////////////////////////////////////
        boo.make_local = function() {
            root.boo = old
            return boo }}
    else
        boo = exports

    ///// -Properties under boo ////////////////////////////////////////////////
    boo.nplugin = nplugin
    boo.plugin  = plugin
    boo.merge   = merge
    boo.clone   = clone
    boo.inherit = inherit

}(this)
<commit_msg>Make `plugin' destructive.<commit_after>/******************************************************************************
 *                                   ~boo~                                    *
 *                                 ‾‾‾‾‾‾‾‾‾                                  *
 * Provides prototypal utilities for OO.                                      *
 *     _________________________________________________________________      *
 *        Copyright (c) 2011 Quildreen Motta // Licenced under MIT/X11        *
 ******************************************************************************/

/// Module boo /////////////////////////////////////////////////////////////////
void function (root) {

    var slice    = [].slice
      , keys     = Object.keys
      , make_obj = Object.create
      , proto    = Object.getPrototypeOf

    //// Function plugin ///////////////////////////////////////////////////////
    //
    //   (object:Obj, mixins:Array) → Obj
    //
    // Copies the own enumerable properties from the mixins over to the
    // ~object~.
    //
    // Only /own/ and /enumerable/ properties present on the ~mixins~
    // will be copied, this ensures the function works the same even in
    // environments that don't support ECMAScript 5 object additions for
    // inspecting properties.
    //
    // This copying is done in order, from left to right, with
    // properties of latter mixins taking priority over the properties
    // of earlier ones.
    //
    // :warning: side-effects
    //   The function will modify ~object~ in-place.
    //
    function plugin(object, mixins) { var i, len, mixin
        for (i = 0, len = mixins.length; i < len; ++i) {
            mixin = mixins[i]
            keys(mixin).forEach(function(key) {
                object[key] = mixin[key] })}

        return object
    }

    //// Function merge ////////////////////////////////////////////////////////
    //
    //   (mixins:Obj...) → Obj
    //
    // Merges all given mixins into a single, fresh object.
    //
    // Only the own enumerable properties of each mixin are carried over
    // to the fresh object.
    //
    // This copying is done in order, from left to right. If a previous
    // mixin defines a property ~foo~, and a latter mixin also defines a
    // property ~foo~, the latter mixin's property value will overwrite
    // the previous value.
    //
    function merge() {
        return plugin({}, arguments)
    }

    //// Function inherit //////////////////////////////////////////////////////
    //
    //   (proto:Obj, mixins:Obj...) → Obj
    //
    // Creates a new object using ~proto~ as the ~[[Prototype]]~, and
    // extending it with the given mixins.
    //
    // Mixin extension is done from left to right, as described in
    // [[fn:plugin]].
    //
    function inherit(proto) {
        return plugin(make_obj(proto), slice.call(arguments, 1))
    }

    //// Function receiver /////////////////////////////////////////////////////
    //
    //   (base:Obj, message:String[, allow_mixins:Bool = true]) → Obj
    //
    // Finds the closest ancestor of ~base~ which can handle the given
    // ~message~.
    //
    // Sometimes you want to know which ancestor exactly implements a
    // given message (~property~), and in this case a simple ~in~ check
    // will not do. This method will search up over the ancestor chain
    // of a base object, optionally including the mixins that were set
    // on it, and find the first ancestor that can receive that
    // message.
    //
    // The prototype is searched first, then, if the functionality is
    // not found, we move on to searching the mixin list (in the reverse
    // order they were plugged in).
    //
    // If no immediate ancestor implements the message, the function
    // will keep searching up the prototype chain to try to find the
    // requested functionality. If it definitely can't find the any
    // receiver, ~null~ is returned.
    //
    // :note:
    //   the mixin's prototypes and the base object itself are
    //   *not* included in this search chain. Mixins are expected to be
    //   parent-less anyways.
    //

    //// Function receivers ////////////////////////////////////////////////////
    //
    //   (base:Obj, message:String[, allow_mixins:Bool = true]) → Obj
    //
    // Finds all the ancestors of ~base~ who can handle the given
    // ~message~.
    //
    // The algorithm works similarly to [[fn:receiver]], except that it
    // does not stop on the first receiver.
    //
    // :see-also:
    //   - [[fn:receiver]] — finds only the first receiver for the
    //                       message.
    //

    ///// Exports //////////////////////////////////////////////////////////////
    var old, boo

    if (typeof exports == 'undefined') {
        old = root.boo
        boo = root.boo = {}

        ///// Method boo.make_local ////////////////////////////////////////////
        boo.make_local = function() {
            root.boo = old
            return boo }}
    else
        boo = exports

    ///// -Properties under boo ////////////////////////////////////////////////
    boo.plugin  = plugin
    boo.merge   = merge
    boo.inherit = inherit

}(this)
<|endoftext|>


<commit_before>'use strict';

const Discord = require('discord.js');
const fse = require('fs-extra');
const path = require('path');
const didYouMean = require('didyoumean2');
const XPDB = require('xpdb');
const stripIndents = require('common-tags').stripIndents;
const Managers = require('./managers');

const bot = exports.client = new Discord.Client();
Managers.Migrator.migrate(bot, __dirname);

const configManager = bot.configManager = new Managers.Config(bot, __dirname);
const config = bot.config = configManager.load();

const logger = bot.logger = new Managers.Logger(bot);
const commands = bot.commands = new Managers.CommandManager(bot);
const stats = bot.stats = new Managers.Stats(bot);

logger.inject();

let dataFolder = path.join(__dirname, '../data/');
if (!fse.existsSync(dataFolder)) fse.mkdirSync(dataFolder);
const db = bot.db = new XPDB(path.join(dataFolder, 'db'));

bot.on('ready', () => {
    bot.utils = require('./utils');

    commands.loadCommands(path.join(__dirname, 'commands'));

    logger.info(stripIndents`Stats:
        - User: ${bot.user.username}#${bot.user.discriminator} <ID: ${bot.user.id}>
        - Users: ${bot.users.filter(user => !user.bot).size}
        - Bots: ${bot.users.filter(user => user.bot).size}
        - Channels: ${bot.channels.size}
        - Guilds: ${bot.guilds.size}`
    );

    stats.set('start-time', process.hrtime());

    delete bot.user.email;
    delete bot.user.verified;
    bot.user.setStatus('invisible');

    logger.info('Bot loaded');
});

bot.on('message', msg => {
    stats.increment(`messages-${bot.user.id === msg.author.id ? 'sent' : 'received'}`);
    if (msg.isMentioned(bot.user)) {
        stats.increment('mentions');
    }

    if (msg.author.id !== bot.user.id) return;

    if (msg.guild && config.blacklistedServers && config.blacklistedServers.indexOf(msg.guild.id.toString()) > -1) {
        return;
    }

    if (msg.isMentioned(bot.user.id)) {
        console.log(`[MENTION] ${msg.author.username} | ${msg.guild ? msg.guild.name : '(DM)'} | #${msg.channel.name || 'N/A'}:\n${msg.cleanContent}`);
    }

    if (!msg.content.startsWith(config.prefix)) return;

    let split = msg.content.split(' ');
    let base = split[0].substr(config.prefix.length).toLowerCase();
    let args = split.slice(1);

    let command = commands.get(base);

    if (command) {
        commands.execute(msg, command, args);
    } else {
        db.get(`shortcuts.${base}`).then(sc => {
            if (!sc) {
                let maybe = didYouMean(base, commands.all().map(c => c.info.name), {
                    threshold: 5,
                    thresholdType: 'edit-distance'
                });

                if (maybe) {
                    msg.edit(`:question: Did you mean \`${config.prefix}${maybe}\`?`).then(m => m.delete(5000));
                } else {
                    msg.edit(`:no_entry_sign: No commands were found that were similar to \`${config.prefix}${base}\``)
                        .then(m => m.delete(5000));
                }
            } else {
                base = sc.command.split(' ')[0].toLowerCase();
                args = sc.command.split(' ').splice(1).concat(args);

                command = commands.get(base);

                if (command) {
                    commands.execute(msg, command, args);
                } else {
                    return msg.edit(`:no_entry_sign: The shortcut \`${sc.name}\` is improperly set up!`).then(m => m.delete(2000));
                }
            }
        });
    }
});

process.on('exit', () => {
    bot.db.unwrap().close();
});

bot.on('error', console.error);
bot.on('warn', console.warn);
bot.on('disconnect', console.warn);

process.on('uncaughtException', (err) => {
    let errorMsg = (err.stack || err || '').toString().replace(new RegExp(`${__dirname}\/`, 'g'), './');
    logger.severe(errorMsg);
});

process.on('unhandledRejection', err => {
    logger.severe('Uncaught Promise error: \n' + err.stack);
});

config && bot.login(config.botToken);
<commit_msg>Change Title of Console<commit_after>'use strict';

const Discord = require('discord.js');
const fse = require('fs-extra');
const path = require('path');
const didYouMean = require('didyoumean2');
const XPDB = require('xpdb');
const stripIndents = require('common-tags').stripIndents;
const Managers = require('./managers');

const bot = exports.client = new Discord.Client();
Managers.Migrator.migrate(bot, __dirname);

const configManager = bot.configManager = new Managers.Config(bot, __dirname);
const config = bot.config = configManager.load();

const logger = bot.logger = new Managers.Logger(bot);
const commands = bot.commands = new Managers.CommandManager(bot);
const stats = bot.stats = new Managers.Stats(bot);

logger.inject();

let dataFolder = path.join(__dirname, '../data/');
if (!fse.existsSync(dataFolder)) fse.mkdirSync(dataFolder);
const db = bot.db = new XPDB(path.join(dataFolder, 'db'));

bot.on('ready', () => {
    bot.utils = require('./utils');

    commands.loadCommands(path.join(__dirname, 'commands'));

    process.title = `SharpBot - ${bot.user.username}`;

    logger.info(stripIndents`Stats:
        - User: ${bot.user.username}#${bot.user.discriminator} <ID: ${bot.user.id}>
        - Users: ${bot.users.filter(user => !user.bot).size}
        - Bots: ${bot.users.filter(user => user.bot).size}
        - Channels: ${bot.channels.size}
        - Guilds: ${bot.guilds.size}`
    );

    stats.set('start-time', process.hrtime());

    delete bot.user.email;
    delete bot.user.verified;
    bot.user.setStatus('invisible');

    logger.info('Bot loaded');
});

bot.on('message', msg => {
    stats.increment(`messages-${bot.user.id === msg.author.id ? 'sent' : 'received'}`);
    if (msg.isMentioned(bot.user)) {
        stats.increment('mentions');
    }

    if (msg.author.id !== bot.user.id) return;

    if (msg.guild && config.blacklistedServers && config.blacklistedServers.indexOf(msg.guild.id.toString()) > -1) {
        return;
    }

    if (msg.isMentioned(bot.user.id)) {
        console.log(`[MENTION] ${msg.author.username} | ${msg.guild ? msg.guild.name : '(DM)'} | #${msg.channel.name || 'N/A'}:\n${msg.cleanContent}`);
    }

    if (!msg.content.startsWith(config.prefix)) return;

    let split = msg.content.split(' ');
    let base = split[0].substr(config.prefix.length).toLowerCase();
    let args = split.slice(1);

    let command = commands.get(base);

    if (command) {
        commands.execute(msg, command, args);
    } else {
        db.get(`shortcuts.${base}`).then(sc => {
            if (!sc) {
                let maybe = didYouMean(base, commands.all().map(c => c.info.name), {
                    threshold: 5,
                    thresholdType: 'edit-distance'
                });

                if (maybe) {
                    msg.edit(`:question: Did you mean \`${config.prefix}${maybe}\`?`).then(m => m.delete(5000));
                } else {
                    msg.edit(`:no_entry_sign: No commands were found that were similar to \`${config.prefix}${base}\``)
                        .then(m => m.delete(5000));
                }
            } else {
                base = sc.command.split(' ')[0].toLowerCase();
                args = sc.command.split(' ').splice(1).concat(args);

                command = commands.get(base);

                if (command) {
                    commands.execute(msg, command, args);
                } else {
                    return msg.edit(`:no_entry_sign: The shortcut \`${sc.name}\` is improperly set up!`).then(m => m.delete(2000));
                }
            }
        });
    }
});

process.on('exit', () => {
    bot.db.unwrap().close();
});

bot.on('error', console.error);
bot.on('warn', console.warn);
bot.on('disconnect', console.warn);

process.on('uncaughtException', (err) => {
    let errorMsg = (err.stack || err || '').toString().replace(new RegExp(`${__dirname}\/`, 'g'), './');
    logger.severe(errorMsg);
});

process.on('unhandledRejection', err => {
    logger.severe('Uncaught Promise error: \n' + err.stack);
});

config && bot.login(config.botToken);
<|endoftext|>


<commit_before>// Import Requirements
const DiscordJS = require('discord.js')
const fs = require('fs')
const path = require('path')
const ytdl = require('ytdl-core')
const YouTubeAPIHandler = require('./youtubeapihandler')
const mh = require('./messagehandler')

// Load Files
try {
  var cfg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'config.json')), 'utf8')
  var helpfile = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'help.json')), 'utf8')
  var blacklist = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'blacklist.json')), 'utf8')
  var radiolist = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'radio_playlists.json')), 'utf8')
} catch (err) {
  if (err) throw err
}

// Object Construction
const bot = new DiscordJS.Client()
const yth = new YouTubeAPIHandler(cfg.youtube_api_key)

// Variable Declaration
let pf = '$'
let voiceConnection
let voiceChannel
let dispatcher
let songQueue = []
let volume = 0.15
let mchannel
let radioMode = false
let stream
let lastMsgTimestamp

// Init Bot
bot.login(cfg.bot_token)

/*
// Bot Events
*/

// On: Bot ready
bot.on('ready', () => {
  console.log('BOT >> Music Bot started')
  bot.user.setGame('v1.1.0 - By CF12')
})

// On: Message Creation
bot.on('message', (msg) => {
  /*
   * TODO: Radio Functionality
   * TODO: Repeats and Shuffles
   * TODO: User and Song Blacklists
   * TODO: Temporary DJ's
   * TODO: Confirm proper durations on videos
   * TODO: Command cooldowns
   */

  // Cancels messages without pf or user is a bot
  if (msg.author.bot || !msg.content.startsWith(pf)) return

  // Command variables
  mchannel = msg.channel
  let member = msg.member
  let fullMsgArray = msg.content.split(' ')
  let cmd = fullMsgArray[0].slice(1, fullMsgArray[0].length).toUpperCase()
  let args = fullMsgArray.slice(1, fullMsgArray.length)

  // Command: Help
  if (cmd === 'HELP') {
    let msg = '__Manual page for: **Everything**__\n'
    for (let key in helpfile) {
      msg += '**' + pf + key + '**' + ' - ' + helpfile[key].info.description + '\n'
    }

    mchannel.sendMessage(msg)
    return
  }

  // Command: Ping
  if (cmd === 'PING') return mh.logChannel(mchannel, 'info', 'Pong!')
  if (cmd === 'XMAS') return mchannel.sendMessage(':snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake:\n\n                   __**MERRY CHRISTMAS FAGS**__ \n\n:snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake: ')

  // Command: DB
  if (cmd === 'DB') {
    console.log(msg.createdTimestamp)
    return
  }

  // Command: Play from YouTube Link
  if (cmd === 'PLAY') {
    let sourceID
    if (!member.voiceChannel) return mh.logChannel(mchannel, 'err', 'User is not in a voice channel!')
    if (args.length === 0) return mh.logChannel(mchannel, 'info', 'Adds a YouTube link to the playlist. Usage: *' + pf + 'play [url]*')
    if (args.length > 1) return mh.logChannel(mchannel, 'err', 'Invalid usage! Usage: ' + pf + 'play [url]')
    if (blacklist.users.includes(member.id)) return mh.logChannel(mchannel, 'bl', 'User is blacklisted!')
    if (radioMode) return mh.logChannel(mchannel, 'err', 'Songs cannot be queued while the bot is in radio mode!')
    if (msg.createdTimestamp - lastMsgTimestamp <= cfg.command_cooldown * 1000) return mh.logChannel(mchannel, 'err', member.toString() + 'Please wait **' + (cfg.command_cooldown - Math.floor((msg.createdTimestamp - lastMsgTimestamp) * 0.001)) + '** second(s) before you use this command again.')
    lastMsgTimestamp = msg.createdTimestamp

    try {
      sourceID = parseYTUrl(args[0])
    } catch (err) {
      return mh.logChannel(mchannel, 'err', 'Error while parsing URL. Please make sure the URL is a valid YouTube link.')
    }

    if (sourceID.includes('p:')) {
      let playlistID = sourceID.substring(2)
      addPlaylist(playlistID, member, () => {
        mh.logChannel(mchannel, 'musinf', 'Playlist successfully added!')
        if (!voiceConnection) voiceConnect(member.voiceChannel)
      })
      return
    }

    addSong(sourceID, msg.member, false, () => {
      if (!voiceConnection) voiceConnect(member.voiceChannel)
    })

    return
  }

  // Command: List Song Queue
  if (cmd === 'QUEUE') {
    if (songQueue.length === 0) return mh.logChannel(mchannel, 'info', 'Song Queue:\n```' + 'No songs have been queued yet. Use ' + pf + 'play [YouTube URL] to queue a song.' + '```')

    let firstVideoTitle = songQueue[0].title
    let firstVideoDuration = songQueue[0].duration
    if (firstVideoTitle.length >= 60) firstVideoTitle = firstVideoTitle.slice(0, 55) + ' ...'
    let queue = firstVideoTitle + ' '.repeat(60 - firstVideoTitle.length) + '|' + ' ' + firstVideoDuration + '\n'

    for (let i = 1; i < songQueue.length; i++) {
      let videoTitle = songQueue[i].title
      let videoDuration = songQueue[i].duration

      if (videoTitle.length >= 60) videoTitle = videoTitle.slice(0, 55) + ' ...'
      if (queue.length > 1800) {
        queue = queue + '...and ' + (songQueue.length - i) + ' more'
        break
      }

      queue = queue + videoTitle + ' '.repeat(60 - videoTitle.length) + '|' + ' ' + videoDuration + '\n'
    }

    mh.logChannel(mchannel, 'info', 'Song Queue:\n```' + queue + '```')
    return
  }

  // Command: Skip Song
  if (cmd === 'SKIP') {
    if (!voiceConnection) return mh.logChannel(mchannel, 'err', 'The bot is not playing anything currently! Use **' + pf + 'play [url]** to queue a song.')
    if (radioMode) return mh.logChannel(mchannel, 'err', 'Skip is unavailable in radio mode.')
    dispatcher.end()
    return
  }

  // Command: Shows the currently playing song
  if (cmd === 'NP' || cmd === 'NOWPLAYING') {
    if (!voiceConnection) return mh.logChannel(mchannel, 'err', 'The bot is not playing anything currently! Use **' + pf + 'play [url]** to queue a song.')
    mh.logChannel(mchannel, 'musinf', 'NOW PLAYING: **' + songQueue[0].title + ' - [' + songQueue[0].duration + ']** - requested by ' + songQueue[0].requester)
    return
  }

  // Command: Leave Voice Channel
  if (cmd === 'LEAVE') {
    if (!voiceConnection) return mh.logChannel(mchannel, 'err', 'The bot is not in a voice channel!')
    if (radioMode) {
      radioMode = false
      mh.logChannel(mchannel, 'musinf', 'Radio Mode has been toggled to: **OFF**')
    }

    songQueue = []
    dispatcher.end()
    voiceConnection = undefined
    return
  }

  // Command: Volume Control
  if (cmd === 'VOLUME') {
    if (args.length === 0) return mh.logChannel(mchannel, 'info', 'Sets the volume of music. Usage: ' + pf + 'volume [1-100]')
    if (args.length === 1 && args[0] <= 100 && args[0] >= 1) {
      volume = args[0] * 0.002
      if (dispatcher) dispatcher.setVolume(volume)
      mh.logChannel(mchannel, 'vol', 'Volume set to: ' + args[0])
    } else mh.logChannel(mchannel, 'err', 'Invalid usage! Usage: ' + pf + 'volume [1-100]')
    return
  }

  if (cmd === 'RADIO') {
    if (args.length === 0) return mh.logChannel(mchannel, 'info', 'Controls the radio features of the bot. For more info, do: **' + pf + 'radio help**')
    if (args[0].toUpperCase() === 'HELP') return mchannel.sendMessage('__Manual page for: **' + pf + 'radio**__\n**' + pf + 'radio help** - Shows this manual page\n**' + pf + 'radio list** - Displays a list of radio stations\n**' + pf + 'radio set [station]** - Sets the radio to the specified station\n**' + pf + 'radio off** - Deactivates the radio')
    if (args[0].toUpperCase() === 'LIST') return mh.logChannel(mchannel, 'radioinf', '**Available Radio Stations:** ' + Object.keys(radiolist))

    if (args[0].toUpperCase() === 'SET') {
      if (args.length === 2) {
        if (voiceConnection) return mh.logChannel(mchannel, 'err', 'Bot cannot be in a voice channel while activating radio mode. Please disconnect the bot by using ' + pf + 'leave.')
        if (!member.voiceChannel) return mh.logChannel(mchannel, 'err', 'User is not in a voice channel!')
        if (!radiolist.hasOwnProperty(args[1].toUpperCase())) return mh.logChannel(mchannel, 'err', 'Invalid station! Use **' + pf + 'radio list** to see a list of all the stations')

        radioMode = true
        mh.logChannel(mchannel, 'radioinf', 'NOW PLAYING: Radio Station - **' + args[1] + '**')
        addPlaylist(radiolist[args[1].toUpperCase()], msg.member, () => { voiceConnect(member.voiceChannel) })
        return
      }

      return mh.logChannel(mchannel, 'err', 'Invalid arguments! Usage: **' + pf + 'radio set [station name]**')
    }

    if (args[0].toUpperCase() === 'OFF') {
      if (args.length === 1) {
        radioMode = false
        mh.logChannel(mchannel, 'radioinf', 'Ending radio stream.')

        songQueue = []
        dispatcher.end()
        voiceConnection = undefined
        return
      }

      return mh.logChannel(mchannel, 'err', 'Invalid arguments! Usage: **' + pf + 'radio off**')
    }

    return mh.logChannel(mchannel, 'err', 'Invalid usage! For help, use **' + pf + 'radio help.** ')
  }

  return mh.logChannel(mchannel, 'err', 'Invalid command! For a list of commands, do: **' + pf + 'help**')
})

/*
// Functions
*/

// Function: Parse YT Url
function parseYTUrl (url, callback) {
  let videoID

  if (url.includes('youtube.com') && url.includes('watch?v=')) videoID = url.split('watch?v=')[1].split('#')[0].split('&')[0]
  else if (url.includes('youtu.be')) videoID = url.split('be/')[1].split('?')[0]
  else if (url.includes('youtube.com') && url.includes('playlist?list=')) videoID = 'p:' + url.split('playlist?list=')[1]
  else throw Error('Not a YouTube Link')

  if (callback) return callback(videoID)
  return videoID
}

// Function: Adds song to queue
function addSong (videoID, member, suppress, callback) {
  yth.getVideo(videoID, (err, info) => {
    if (err) return mh.logChannel(mchannel, 'err', 'Error while parsing video(es). Please make sure the URL is valid.')
    if (!suppress) mh.logChannel(mchannel, 'info', 'Song successfully added to queue.')
    let video = info.items[0]

    songQueue.push({
      video_ID: videoID,
      link: String('http://youtube.com/?v=' + videoID),
      requester: member.toString(),
      title: video.snippet.title,
      duration: convertDuration(video.contentDetails.duration)
    })

    if (callback) callback()
  })
}

// Function: Queues an entire playlist
function addPlaylist (playlistID, member, callback) {
  if (!radioMode) mh.logChannel(mchannel, 'musinf', 'Fetching playlist information...')
  yth.getPlaylist(playlistID, (err, playlist) => {
    if (err) return mh.logChannel(mchannel, 'err', 'Error while parsing playlist URL. Please make sure the URL is valid.')

    for (const index in playlist) {
      addSong(playlist[index].snippet.resourceId.videoId, member, true, () => {
        if ((playlist[playlist.length - 1] === playlist[index]) && (typeof callback === 'function')) callback()
      })
    }
  })
}

// Function: Plays next song
function nextSong () {
  let song
  if (radioMode) song = songQueue[Math.floor(Math.random() * songQueue.length)]
  else song = songQueue[0]

  if (!radioMode) {
    mh.logChannel(mchannel, 'musinf', 'NOW PLAYING: **' + song.title + ' - [' + song.duration + ']** - requested by ' + song.requester)
    mh.logConsole('info', 'Now playing: ' + song.title)
  }

  stream = ytdl(song.link)
  return voiceConnection.playStream(stream, {
    'volume': volume
  })
  .on('end', () => {
    if (!radioMode) {
      songQueue.shift()
      if (songQueue.length === 0) return voiceDisconnect()
    }
    stream.destroy()
    dispatcher = nextSong()
  })
}

// Function Connects to Voice Channel
function voiceConnect (channel) {
  voiceChannel = channel
  channel.join()
  .then((connection) => {
    mh.logConsole('info', 'Joining Voice Channel <' + voiceChannel.id + '>')
    voiceConnection = connection
    dispatcher = nextSong()
  })
}

// Function: Disconnect from Voice Channel
function voiceDisconnect () {
  mh.logConsole('info', 'Leaving Voice Channel <' + voiceChannel.id + '>')
  mh.logChannel(mchannel, 'musinf', 'Queue ended. Disconnecting...')

  voiceConnection.disconnect()
  voiceConnection = undefined
}

// Function: Convert Durations from ISO 8601
function convertDuration (duration) {
  let reptms = /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/
  let hours = 0
  let minutes = 0
  let seconds = 0

  if (reptms.test(duration)) {
    let matches = reptms.exec(duration)
    if (matches[1]) hours = Number(matches[1])
    if (matches[2]) minutes = Number(matches[2])
    if (matches[3]) seconds = Number(matches[3]) - 1
  }

  if (hours >= 1) return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds)
  if (minutes >= 1) return (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds)
  else return '00:' + (seconds < 10 ? '0' + seconds : seconds)
}
<commit_msg>Work on stuff<commit_after>// Import Requirements
const DiscordJS = require('discord.js')
const fs = require('fs')
const path = require('path')
const ytdl = require('ytdl-core')
const YouTubeAPIHandler = require('./youtubeapihandler')
const mh = require('./messagehandler')

// Load Files
try {
  var cfg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'config.json')), 'utf8')
  var helpfile = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'help.json')), 'utf8')
  var blacklist = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'blacklist.json')), 'utf8')
  var radiolist = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'config', 'radio_playlists.json')), 'utf8')
} catch (err) {
  if (err) throw err
}

// Object Construction
const bot = new DiscordJS.Client()
const yth = new YouTubeAPIHandler(cfg.youtube_api_key)

// Variable Declaration
let pf = '$'
let voiceConnection
let voiceChannel
let dispatcher
let songQueue = []
let volume = 0.15
let mchannel
let radioMode = false
let stream
let lastMsgTimestamp

// Init Bot
bot.login(cfg.bot_token)

/*
// Bot Events
*/

// On: Bot ready
bot.on('ready', () => {
  console.log('BOT >> Music Bot started')
  bot.user.setGame('v1.1.0 - By CF12')
})

// On: Message Creation
bot.on('message', (msg) => {
  /*
   * TODO: Radio Functionality
   * TODO: Repeats and Shuffles
   * TODO: User and Song Blacklists
   * TODO: Temporary DJ's
   * TODO: Confirm proper durations on videos
   * TODO: Command cooldowns
   */

  // Cancels messages without pf or user is a bot
  if (msg.author.bot || !msg.content.startsWith(pf)) return

  // Command variables
  mchannel = msg.channel
  let member = msg.member
  let fullMsgArray = msg.content.split(' ')
  let cmd = fullMsgArray[0].slice(1, fullMsgArray[0].length).toUpperCase()
  let args = fullMsgArray.slice(1, fullMsgArray.length)

  // Command: Help
  if (cmd === 'HELP') {
    let msg = '__Manual page for: **Everything**__\n'
    for (let key in helpfile) {
      msg += '**' + pf + key + '**' + ' - ' + helpfile[key].info.description + '\n'
    }

    mchannel.sendMessage(msg)
    return
  }

  // Command: Ping
  if (cmd === 'PING') return mh.logChannel(mchannel, 'info', 'Pong!')
  if (cmd === 'XMAS') return mchannel.sendMessage(':snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake:\n\n                   __**MERRY CHRISTMAS FAGS**__ \n\n:snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake::snowflake: ')

  // Command: DB
  if (cmd === 'DB') {
    console.log(msg.createdTimestamp)
    return
  }

  // Command: Play from YouTube Link
  if (cmd === 'PLAY') {
    let sourceID
    if (!member.voiceChannel) return mh.logChannel(mchannel, 'err', 'User is not in a voice channel!')
    if (args.length === 0) return mh.logChannel(mchannel, 'info', 'Adds a YouTube link to the playlist. Usage: *' + pf + 'play [url]*')
    if (args.length > 1) return mh.logChannel(mchannel, 'err', 'Invalid usage! Usage: ' + pf + 'play [url]')
    if (blacklist.users.includes(member.id)) return mh.logChannel(mchannel, 'bl', 'User is blacklisted!')
    if (radioMode) return mh.logChannel(mchannel, 'err', 'Songs cannot be queued while the bot is in radio mode!')
    if (msg.createdTimestamp - lastMsgTimestamp <= cfg.command_cooldown * 1000) return mh.logChannel(mchannel, 'delay', member.toString() + 'Please wait **' + (cfg.command_cooldown - Math.floor((msg.createdTimestamp - lastMsgTimestamp) * 0.001)) + '** second(s) before you use this command again.')
    lastMsgTimestamp = msg.createdTimestamp

    try {
      sourceID = parseYTUrl(args[0])
    } catch (err) {
      return mh.logChannel(mchannel, 'err', 'Error while parsing URL. Please make sure the URL is a valid YouTube link.')
    }

    if (sourceID.includes('p:')) {
      let playlistID = sourceID.substring(2)
      addPlaylist(playlistID, member, () => {
        mh.logChannel(mchannel, 'musinf', 'Playlist successfully added!')
        if (!voiceConnection) voiceConnect(member.voiceChannel)
      })
      return
    }

    addSong(sourceID, msg.member, false, () => {
      if (!voiceConnection) voiceConnect(member.voiceChannel)
    })

    return
  }

  // Command: List Song Queue
  if (cmd === 'QUEUE') {
    if (songQueue.length === 0) return mh.logChannel(mchannel, 'info', 'Song Queue:\n```' + 'No songs have been queued yet. Use ' + pf + 'play [YouTube URL] to queue a song.' + '```')

    let firstVideoTitle = songQueue[0].title
    let firstVideoDuration = songQueue[0].duration
    if (firstVideoTitle.length >= 60) firstVideoTitle = firstVideoTitle.slice(0, 55) + ' ...'
    let queue = firstVideoTitle + ' '.repeat(60 - firstVideoTitle.length) + '|' + ' ' + firstVideoDuration + '\n'

    for (let i = 1; i < songQueue.length; i++) {
      let videoTitle = songQueue[i].title
      let videoDuration = songQueue[i].duration

      if (videoTitle.length >= 60) videoTitle = videoTitle.slice(0, 55) + ' ...'
      if (queue.length > 1800) {
        queue = queue + '...and ' + (songQueue.length - i) + ' more'
        break
      }

      queue = queue + videoTitle + ' '.repeat(60 - videoTitle.length) + '|' + ' ' + videoDuration + '\n'
    }

    mh.logChannel(mchannel, 'info', 'Song Queue:\n```' + queue + '```')
    return
  }

  // Command: Skip Song
  if (cmd === 'SKIP') {
    if (!voiceConnection) return mh.logChannel(mchannel, 'err', 'The bot is not playing anything currently! Use **' + pf + 'play [url]** to queue a song.')
    if (radioMode) return mh.logChannel(mchannel, 'err', 'Skip is unavailable in radio mode.')
    dispatcher.end()
    return
  }

  // Command: Shows the currently playing song
  if (cmd === 'NP' || cmd === 'NOWPLAYING') {
    if (!voiceConnection) return mh.logChannel(mchannel, 'err', 'The bot is not playing anything currently! Use **' + pf + 'play [url]** to queue a song.')
    mh.logChannel(mchannel, 'musinf', 'NOW PLAYING: **' + songQueue[0].title + ' - [' + songQueue[0].duration + ']** - requested by ' + songQueue[0].requester)
    return
  }

  // Command: Leave Voice Channel
  if (cmd === 'LEAVE') {
    if (!voiceConnection) return mh.logChannel(mchannel, 'err', 'The bot is not in a voice channel!')
    if (radioMode) {
      radioMode = false
      mh.logChannel(mchannel, 'musinf', 'Radio Mode has been toggled to: **OFF**')
    }

    songQueue = []
    dispatcher.end()
    voiceConnection = undefined
    return
  }

  // Command: Volume Control
  if (cmd === 'VOLUME') {
    if (args.length === 0) return mh.logChannel(mchannel, 'info', 'Sets the volume of music. Usage: ' + pf + 'volume [1-100]')
    if (args.length === 1 && args[0] <= 100 && args[0] >= 1) {
      volume = args[0] * 0.002
      if (dispatcher) dispatcher.setVolume(volume)
      mh.logChannel(mchannel, 'vol', 'Volume set to: ' + args[0])
    } else mh.logChannel(mchannel, 'err', 'Invalid usage! Usage: ' + pf + 'volume [1-100]')
    return
  }

  if (cmd === 'RADIO') {
    if (args.length === 0) return mh.logChannel(mchannel, 'info', 'Controls the radio features of the bot. For more info, do: **' + pf + 'radio help**')
    if (args[0].toUpperCase() === 'HELP') return mchannel.sendMessage('__Manual page for: **' + pf + 'radio**__\n**' + pf + 'radio help** - Shows this manual page\n**' + pf + 'radio list** - Displays a list of radio stations\n**' + pf + 'radio set [station]** - Sets the radio to the specified station\n**' + pf + 'radio off** - Deactivates the radio')
    if (args[0].toUpperCase() === 'LIST') return mh.logChannel(mchannel, 'radioinf', '**Available Radio Stations:** ' + Object.keys(radiolist))

    if (args[0].toUpperCase() === 'SET') {
      if (args.length === 2) {
        if (voiceConnection) return mh.logChannel(mchannel, 'err', 'Bot cannot be in a voice channel while activating radio mode. Please disconnect the bot by using ' + pf + 'leave.')
        if (!member.voiceChannel) return mh.logChannel(mchannel, 'err', 'User is not in a voice channel!')
        if (!radiolist.hasOwnProperty(args[1].toUpperCase())) return mh.logChannel(mchannel, 'err', 'Invalid station! Use **' + pf + 'radio list** to see a list of all the stations')

        radioMode = true
        mh.logChannel(mchannel, 'radioinf', 'NOW PLAYING: Radio Station - **' + args[1] + '**')
        addPlaylist(radiolist[args[1].toUpperCase()], msg.member, () => { voiceConnect(member.voiceChannel) })
        return
      }

      return mh.logChannel(mchannel, 'err', 'Invalid arguments! Usage: **' + pf + 'radio set [station name]**')
    }

    if (args[0].toUpperCase() === 'OFF') {
      if (args.length === 1) {
        radioMode = false
        mh.logChannel(mchannel, 'radioinf', 'Ending radio stream.')

        songQueue = []
        dispatcher.end()
        voiceConnection = undefined
        return
      }

      return mh.logChannel(mchannel, 'err', 'Invalid arguments! Usage: **' + pf + 'radio off**')
    }

    return mh.logChannel(mchannel, 'err', 'Invalid usage! For help, use **' + pf + 'radio help.** ')
  }

  return mh.logChannel(mchannel, 'err', 'Invalid command! For a list of commands, do: **' + pf + 'help**')
})

/*
// Functions
*/

// Function: Parse YT Url
function parseYTUrl (url, callback) {
  let videoID

  if (url.includes('youtube.com') && url.includes('watch?v=')) videoID = url.split('watch?v=')[1].split('#')[0].split('&')[0]
  else if (url.includes('youtu.be')) videoID = url.split('be/')[1].split('?')[0]
  else if (url.includes('youtube.com') && url.includes('playlist?list=')) videoID = 'p:' + url.split('playlist?list=')[1]
  else throw Error('Not a YouTube Link')

  if (callback) return callback(videoID)
  return videoID
}

// Function: Adds song to queue
function addSong (videoID, member, suppress, callback) {
  yth.getVideo(videoID, (err, info) => {
    if (err) return mh.logChannel(mchannel, 'err', 'Error while parsing video(es). Please make sure the URL is valid.')
    if (!suppress) mh.logChannel(mchannel, 'info', 'Song successfully added to queue.')
    let video = info.items[0]

    songQueue.push({
      video_ID: videoID,
      link: String('http://youtube.com/?v=' + videoID),
      requester: member.toString(),
      title: video.snippet.title,
      duration: convertDuration(video.contentDetails.duration)
    })

    if (callback) callback()
  })
}

// Function: Queues an entire playlist
function addPlaylist (playlistID, member, callback) {
  if (!radioMode) mh.logChannel(mchannel, 'musinf', 'Fetching playlist information...')
  yth.getPlaylist(playlistID, (err, playlist) => {
    if (err) return mh.logChannel(mchannel, 'err', 'Error while parsing playlist URL. Please make sure the URL is valid.')

    for (const index in playlist) {
      addSong(playlist[index].snippet.resourceId.videoId, member, true, () => {
        if ((playlist[playlist.length - 1] === playlist[index]) && (typeof callback === 'function')) callback()
      })
    }
  })
}

// Function: Plays next song
function nextSong () {
  let song
  if (radioMode) song = songQueue[Math.floor(Math.random() * songQueue.length)]
  else song = songQueue[0]

  if (!radioMode) {
    mh.logChannel(mchannel, 'musinf', 'NOW PLAYING: **' + song.title + ' - [' + song.duration + ']** - requested by ' + song.requester)
    mh.logConsole('info', 'Now playing: ' + song.title)
  }

  stream = ytdl(song.link)
  return voiceConnection.playStream(stream, {
    'volume': volume
  })
  .on('end', () => {
    if (!radioMode) {
      songQueue.shift()
      if (songQueue.length === 0) return voiceDisconnect()
    }
    stream.destroy()
    dispatcher = nextSong()
  })
}

// Function Connects to Voice Channel
function voiceConnect (channel) {
  voiceChannel = channel
  channel.join()
  .then((connection) => {
    mh.logConsole('info', 'Joining Voice Channel <' + voiceChannel.id + '>')
    voiceConnection = connection
    dispatcher = nextSong()
  })
}

// Function: Disconnect from Voice Channel
function voiceDisconnect () {
  mh.logConsole('info', 'Leaving Voice Channel <' + voiceChannel.id + '>')
  mh.logChannel(mchannel, 'musinf', 'Queue ended. Disconnecting...')

  voiceConnection.disconnect()
  voiceConnection = undefined
}

// Function: Convert Durations from ISO 8601
function convertDuration (duration) {
  let reptms = /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/
  let hours = 0
  let minutes = 0
  let seconds = 0

  if (reptms.test(duration)) {
    let matches = reptms.exec(duration)
    if (matches[1]) hours = Number(matches[1])
    if (matches[2]) minutes = Number(matches[2])
    if (matches[3]) seconds = Number(matches[3]) - 1
  }

  if (hours >= 1) return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds)
  if (minutes >= 1) return (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds)
  else return '00:' + (seconds < 10 ? '0' + seconds : seconds)
}
<|endoftext|>


<commit_before>/*
 * owo-whats this - Core file
 *
 * Contributed by:
 * | Capuccino
 * | Ovyerus
 *
 * Licensed under MIT. Copyright (c) 2017 Capuccino, Ovyerus and the repository contributors.
 */

// Framework imports
const Eris = require('eris');
const fs = require('fs');

//globals w

global.__baseDir = __dirname;
global.Promise = require('bluebird');
global.logger = require(`${__dirname}/lib/logger`);

// Custom modules
const logger = require(`${__dirname}/lib/logger`);
const CommandHolder = require(`${__dirname}/lib/commands`);
const utils = require(`${__dirname}/lib/utils`);

// Setup stuff
const config = require(`${__dirname}/config.json`);
const bot = new Eris(config.token, {
    autoreconnect: true,
    seedVoiceConnections: true,
    maxShards: config.maxShards || 1,
    disableEvents: {
        TYPING_START: true
    }
});
var loadCommands = true;
var allowCommandUse = false;

// Create data files
try {
    require.resolve(`${__dirname}/data/data.json`);
} catch (err) {
    fs.mkdirSync(`${__dirname}/data/`);
    fs.writeFile(`${__dirname}/data/data.json`, JSON.stringify({ admins: [], blacklist: [] }), e => {
        if (e) {
            throw e;
        } else {
            logger.info('Created data.json.');
        }
    });
}

try {
    require.resolve(`${__dirname}/data/prefixes.json`);
} catch (err) {
    fs.writeFile(`${__dirname}/data/prefixes.json`, JSON.stringify([]), e => {
        if (e) {
            throw e;
        } else {
            logger.info('Created prefixes.json.');
        }
    });
}

exports.bot = bot;

bot.logger = logger;
bot.commands = new CommandHolder();
bot.config = config;
bot.music = {
    skips: new Eris.Collection(Object),
    queues: new Eris.Collection(Object),
    connections: new Eris.Collection(Eris.VoiceConnection),
    streams: new Eris.Collection(Object),
    stopped: []
};

// Init
bot.on('ready', () => {
    if (loadCommands) {
        require(`${__dirname}/lib/commandLoader.js`).init().then(() => {
            var altPrefixes = JSON.parse(fs.readFileSync(`${__dirname}/data/prefixes.json`));
            logger.info(`Loaded ${bot.commands.length} ${bot.commands.length === 1 ? 'command' : 'commands'}.`);
            logger.info(`${bot.user.username} is connected to Discord and ready to use.`);
            logger.info(`Main prefix is '${config.mainPrefix}', can also use @mention.`);
            logger.info(`${altPrefixes.length > 0 ? `Alternative prefixes: '${altPrefixes.join("', ")}'` : 'No alternative prefixes.'}`);
            loadCommands = false;
            allowCommandUse = true;
        }).catch(err => {
            console.error(`Experienced error while loading commands:\n${config.debug ? err.stack : err}`);
        });
    } else {
        logger.info('Reconnected to Discord from disconnect.');
    }

    bot.editStatus('online', {name: `${config.gameName || `${config.mainPrefix}help for commands!`} | ${bot.guilds.size} ${bot.guilds.size === 1 ? 'server' : 'servers'}`, type: config.gameURL ? 1 : 0, url: `${config.gameURL || null}`});
});

bot.on('shardReady', shard => {
    logger.custom('blue', 'shard/shardInfo', `shard ${shard} is ready!`);
});

bot.on('shardResume', shard => {
    logger.custom('blue', 'shard/shardInfo', `shard ${shard} has resumed.`);
});

bot.on('guildCreate', g => {
    if (g.members.filter(m => m.bot).length >= Math.ceil(g.memberCount / 2)) {
        logger.info(`Detected bot collection guild. Autoleaving.... (${g.name} [${g.id}])`);
        g.leave();
    } else {
        bot.editStatus('online', {name: `${config.gameName || `${config.mainPrefix}help for commands!`} | ${bot.guilds.size} ${bot.guilds.size === 1 ? 'server' : 'servers'}`, type: config.gameURL ? 1 : 0, url: `${config.gameURL || null}`});
    }
});

bot.on('guildDelete', () => {
    bot.editStatus('online', {name: `${config.gameName || `${config.mainPrefix}help for commands!`} | ${bot.guilds.size} ${bot.guilds.size === 1 ? 'server' : 'servers'}`, type: config.gameURL ? 1 : 0, url: `${config.gameURL || null}`});
});

const prefixParser = require(`${__dirname}/lib/prefixParser.js`);

// Command handler
bot.on('messageCreate', msg => {
    if (!allowCommandUse || msg.author.id === bot.user.id || msg.author.bot || utils.isBlacklisted(msg.author.id)) return;
    if (!msg.channel.guild) {
        logger.custom('cyan', 'dm', loggerPrefix(msg) + msg.cleanContent);
        return;
    }

    prefixParser(msg.content).then(content => {
        if (content === undefined) return;

        var args = content.split(' ');
        var cmd = args.shift();
        var suffix = args.join(' ');
        var cleanSuffix = msg.cleanContent.split(' ');
        cleanSuffix.splice(0, 1);
        cleanSuffix = cleanSuffix.join(' ');
        var guildBot = msg.channel.guild.members.get(bot.user.id);

        var ctx = {msg, args, cmd, suffix, cleanSuffix, guildBot};

        if (bot.commands.getCommand(cmd)) {
            logger.cmd(loggerPrefix(msg) + msg.cleanContent);

            if (bot.commands.getCommand(cmd).adminOnly && (utils.isOwner(msg.author.id) || utils.isAdmin(msg.author.id))) {
                bot.commands.runCommand(cmd, bot, ctx).then(() => {
                    logger.cmd(loggerPrefix(msg) + `Successfully ran owner command '${cmd}'`);
                }).catch(err => {
                    handleCmdErr(msg, cmd, err);
                });
            } else if (bot.commands.getCommand(cmd).adminOnly) {
                return;
            } else {
                bot.commands.runCommand(cmd, bot, ctx).then(() => {
                    logger.cmd(loggerPrefix(msg) + `Successfully ran command '${cmd}'`);
                }).catch(err => {
                    handleCmdErr(msg, cmd, err);
                });
            }
        }
    }).catch(err => {
        logger.customError('prefixParser', `Failed to parse message for prefix: ${err}${config.debug ? `\n${err.stack}` : ''}`);
    });
});

// Handle disconnect
bot.on('disconnect', () => {
    logger.warn('Disconnected from Discord.');
});
bot.on('shardDisconnect', (err, shard) => {
    if (err) logger.customError('shard/shardStatus', `Shard ${shard} disconnected. Reason ${err}`);
});

// Music Shit
bot.on('voiceChannelLeave', (mem, chan) => {
    /*if (chan.voiceMembers.filter(m => m.id !== bot.user.id && !m.bot).length === 0 && chan.voiceMembers.get(bot.user.id)) {
        setTimeout(() => {
            let c = bot.guilds.get(chan.guild.id).channels.get(chan.id);
            if (c.voiceMembers.filter(m => m.id !== bot.user.id && !m.bot).length === 0) {
                if (bot.music.connections.get(chan.guild.id).playing) bot.music.connetions.get(chan.guild.id).stopPlaying();
                bot.music.connections.get(chan.guild.id).leave();
            }
        }, 60000);
    }*/

    if (mem.id !== bot.user.id) return;
    if (bot.music.connections.get(chan.guild.id)) bot.music.connections.delete(chan.guild.id);
    if (bot.music.queues.get(chan.guild.id)) bot.music.queues.delete(chan.guild.id);
    if (bot.music.skips.get(chan.guild.id)) bot.music.skips.delete(chan.guild.id);
    if (bot.music.streams.get(chan.guild.id)) {
        bot.music.streams.delete(chan.guild.id);
    }
});

bot.on('voiceChannelSwitch', (mem, chan, old) => {
    if (mem.id !== bot.user.id) return;
    if (!bot.music.connections.get(old.id)) {
        bot.music.connections.add(chan);
    } else {
        bot.music.connections.delete(old);
        bot.music.connects.add(old);
    }
});

bot.connect();

// Functions
function loggerPrefix(msg) {
    return msg.channel.guild ? `${msg.channel.guild.name} | ${msg.channel.name} > ${utils.formatUsername(msg.author)} (${msg.author.id}): ` : `Direct Message > ${utils.formatUsername(msg.author)} (${msg.author.id}): `;
}

function handleCmdErr(msg, cmd, err) {
    if (err.response) var resp = JSON.parse(err.response);
    if (resp && resp.code === 50013) {
        logger.warn(`Can't send message in '#${msg.channel.name}' (${msg.channel.id}), cmd from user '${utils.formatUsername(msg.author)}' (${msg.author.id})`);
        msg.author.getDMChannel().then(dm => {
            console.log(dm.id);
            return dm.createMessage(`It appears I was unable to send a message in \`#${msg.channel.name}\` on the server \`${msg.channel.guild.name}\`. Please give me the Send Messages permission or notify a mod or admin if you cannot do this.`);
        }).catch(() => logger.warn(`Couldn't get DM channel for/send DM to ${utils.formatUsername(msg.author)} (${msg.author.id})`));
    } else if (resp && /\{'code':.+, 'message':.+\}/.test(err.response) && resp.code !== 50013) {
        logger.warn(loggerPrefix(msg) + `Discord error running command "${cmd}":\n:${config.debug ? err.stack : err}`);
        let m = `Discord error while trying to execute \`${cmd}\`\n`;
        m += '```js\n';
        m += `Code: ${resp.code}. Message: ${resp.message}\n`;
        m += "``` If you feel this shouldn't be happening, join my support server. Invite can be found in the `invite` command.";
        msg.channel.createMessage(m);
    } else {
        logger.error(loggerPrefix(msg) + `Error running command "${cmd}":\n${config.debug ? err.stack || err : err}`);
        let m = `Experienced error while executing \`${cmd}\`\n`;
        m += '```js\n';
        m += err + '\n';
        m += "``` If you feel this shouldn't be happening, join my support server. Invite can be found in the `invite` command.";
        msg.channel.createMessage(m);
    }
}

bot.awaitMessage = (channelID, userID, filter = function () { return true; }, timeout = 15000) => {
    return new Promise((resolve, reject) => {
        if (!channelID || typeof channelID !== 'string') {
            reject(new Error(`Unwanted type of channelID: got "${typeof channelID}" expected "string"`));
        } else if (!userID || typeof userID !== 'string') {
            reject(new Error(`Unwanted type of userID: got "${typeof userID}" expected "string"`));
        } else {
            var responded, rmvTimeout;

            var onCrt = (msg) => {
                if (msg.channel.id === channelID && msg.author.id === userID && filter(msg)) {
                    responded = true;
                    return msg;
                }
            };

            var onCrtWrap = (msg) => {
                var res = onCrt(msg);
                if (responded) {
                    bot.removeListener('messageCreate', onCrtWrap);
                    clearInterval(rmvTimeout);
                    resolve(res);
                }
            };

            bot.on('messageCreate', onCrtWrap);

            rmvTimeout = setTimeout(() => {
                if (!responded) {
                    bot.removeListener('messageCreate', onCrtWrap);
                    reject(new Error('Message await expired.'));
                }
            }, timeout);
        }
    });
};<commit_msg>Return in command promise because bluebird complaining<commit_after>/*
 * owo-whats this - Core file
 *
 * Contributed by:
 * | Capuccino
 * | Ovyerus
 *
 * Licensed under MIT. Copyright (c) 2017 Capuccino, Ovyerus and the repository contributors.
 */

// Framework imports
const Eris = require('eris');
const fs = require('fs');

//globals w

global.__baseDir = __dirname;
global.Promise = require('bluebird');
global.logger = require(`${__dirname}/lib/logger`);

// Custom modules
const logger = require(`${__dirname}/lib/logger`);
const CommandHolder = require(`${__dirname}/lib/commands`);
const utils = require(`${__dirname}/lib/utils`);

// Setup stuff
const config = require(`${__dirname}/config.json`);
const bot = new Eris(config.token, {
    autoreconnect: true,
    seedVoiceConnections: true,
    maxShards: config.maxShards || 1,
    disableEvents: {
        TYPING_START: true
    }
});
var loadCommands = true;
var allowCommandUse = false;

// Create data files
try {
    require.resolve(`${__dirname}/data/data.json`);
} catch (err) {
    fs.mkdirSync(`${__dirname}/data/`);
    fs.writeFile(`${__dirname}/data/data.json`, JSON.stringify({ admins: [], blacklist: [] }), e => {
        if (e) {
            throw e;
        } else {
            logger.info('Created data.json.');
        }
    });
}

try {
    require.resolve(`${__dirname}/data/prefixes.json`);
} catch (err) {
    fs.writeFile(`${__dirname}/data/prefixes.json`, JSON.stringify([]), e => {
        if (e) {
            throw e;
        } else {
            logger.info('Created prefixes.json.');
        }
    });
}

exports.bot = bot;

bot.logger = logger;
bot.commands = new CommandHolder();
bot.config = config;
bot.music = {
    skips: new Eris.Collection(Object),
    queues: new Eris.Collection(Object),
    connections: new Eris.Collection(Eris.VoiceConnection),
    streams: new Eris.Collection(Object),
    stopped: []
};

// Init
bot.on('ready', () => {
    if (loadCommands) {
        require(`${__dirname}/lib/commandLoader.js`).init().then(() => {
            var altPrefixes = JSON.parse(fs.readFileSync(`${__dirname}/data/prefixes.json`));
            logger.info(`Loaded ${bot.commands.length} ${bot.commands.length === 1 ? 'command' : 'commands'}.`);
            logger.info(`${bot.user.username} is connected to Discord and ready to use.`);
            logger.info(`Main prefix is '${config.mainPrefix}', can also use @mention.`);
            logger.info(`${altPrefixes.length > 0 ? `Alternative prefixes: '${altPrefixes.join("', ")}'` : 'No alternative prefixes.'}`);
            loadCommands = false;
            allowCommandUse = true;
        }).catch(err => {
            console.error(`Experienced error while loading commands:\n${config.debug ? err.stack : err}`);
        });
    } else {
        logger.info('Reconnected to Discord from disconnect.');
    }

    bot.editStatus('online', {name: `${config.gameName || `${config.mainPrefix}help for commands!`} | ${bot.guilds.size} ${bot.guilds.size === 1 ? 'server' : 'servers'}`, type: config.gameURL ? 1 : 0, url: `${config.gameURL || null}`});
});

bot.on('shardReady', shard => {
    logger.custom('blue', 'shard/shardInfo', `shard ${shard} is ready!`);
});

bot.on('shardResume', shard => {
    logger.custom('blue', 'shard/shardInfo', `shard ${shard} has resumed.`);
});

bot.on('guildCreate', g => {
    if (g.members.filter(m => m.bot).length >= Math.ceil(g.memberCount / 2)) {
        logger.info(`Detected bot collection guild. Autoleaving.... (${g.name} [${g.id}])`);
        g.leave();
    } else {
        bot.editStatus('online', {name: `${config.gameName || `${config.mainPrefix}help for commands!`} | ${bot.guilds.size} ${bot.guilds.size === 1 ? 'server' : 'servers'}`, type: config.gameURL ? 1 : 0, url: `${config.gameURL || null}`});
    }
});

bot.on('guildDelete', () => {
    bot.editStatus('online', {name: `${config.gameName || `${config.mainPrefix}help for commands!`} | ${bot.guilds.size} ${bot.guilds.size === 1 ? 'server' : 'servers'}`, type: config.gameURL ? 1 : 0, url: `${config.gameURL || null}`});
});

const prefixParser = require(`${__dirname}/lib/prefixParser.js`);

// Command handler
bot.on('messageCreate', msg => {
    if (!allowCommandUse || msg.author.id === bot.user.id || msg.author.bot || utils.isBlacklisted(msg.author.id)) return;
    if (!msg.channel.guild) {
        logger.custom('cyan', 'dm', loggerPrefix(msg) + msg.cleanContent);
        return;
    }

    prefixParser(msg.content).then(content => {
        if (content === undefined) return;

        var args = content.split(' ');
        var cmd = args.shift();
        var suffix = args.join(' ');
        var cleanSuffix = msg.cleanContent.split(' ');
        cleanSuffix.splice(0, 1);
        cleanSuffix = cleanSuffix.join(' ');
        var guildBot = msg.channel.guild.members.get(bot.user.id);

        var ctx = {msg, args, cmd, suffix, cleanSuffix, guildBot};

        if (bot.commands.getCommand(cmd)) {
            logger.cmd(loggerPrefix(msg) + msg.cleanContent);

            if (bot.commands.getCommand(cmd).adminOnly && (utils.isOwner(msg.author.id) || utils.isAdmin(msg.author.id))) {
                bot.commands.runCommand(cmd, bot, ctx).then(() => {
                    logger.cmd(loggerPrefix(msg) + `Successfully ran owner command '${cmd}'`);
                }).catch(err => {
                    handleCmdErr(msg, cmd, err);
                });
            } else if (bot.commands.getCommand(cmd).adminOnly) {
                return;
            } else {
                bot.commands.runCommand(cmd, bot, ctx).then(() => {
                    logger.cmd(loggerPrefix(msg) + `Successfully ran command '${cmd}'`);
                    return;
                }).catch(err => {
                    handleCmdErr(msg, cmd, err);
                });
            }
        }
    }).catch(err => {
        logger.customError('prefixParser', `Failed to parse message for prefix: ${err}${config.debug ? `\n${err.stack}` : ''}`);
    });
});

// Handle disconnect
bot.on('disconnect', () => {
    logger.warn('Disconnected from Discord.');
});
bot.on('shardDisconnect', (err, shard) => {
    if (err) logger.customError('shard/shardStatus', `Shard ${shard} disconnected. Reason ${err}`);
});

// Music Shit
bot.on('voiceChannelLeave', (mem, chan) => {
    /*if (chan.voiceMembers.filter(m => m.id !== bot.user.id && !m.bot).length === 0 && chan.voiceMembers.get(bot.user.id)) {
        setTimeout(() => {
            let c = bot.guilds.get(chan.guild.id).channels.get(chan.id);
            if (c.voiceMembers.filter(m => m.id !== bot.user.id && !m.bot).length === 0) {
                if (bot.music.connections.get(chan.guild.id).playing) bot.music.connetions.get(chan.guild.id).stopPlaying();
                bot.music.connections.get(chan.guild.id).leave();
            }
        }, 60000);
    }*/

    if (mem.id !== bot.user.id) return;
    if (bot.music.connections.get(chan.guild.id)) bot.music.connections.delete(chan.guild.id);
    if (bot.music.queues.get(chan.guild.id)) bot.music.queues.delete(chan.guild.id);
    if (bot.music.skips.get(chan.guild.id)) bot.music.skips.delete(chan.guild.id);
    if (bot.music.streams.get(chan.guild.id)) {
        bot.music.streams.delete(chan.guild.id);
    }
});

bot.on('voiceChannelSwitch', (mem, chan, old) => {
    if (mem.id !== bot.user.id) return;
    if (!bot.music.connections.get(old.id)) {
        bot.music.connections.add(chan);
    } else {
        bot.music.connections.delete(old);
        bot.music.connects.add(old);
    }
});

bot.connect();

// Functions
function loggerPrefix(msg) {
    return msg.channel.guild ? `${msg.channel.guild.name} | ${msg.channel.name} > ${utils.formatUsername(msg.author)} (${msg.author.id}): ` : `Direct Message > ${utils.formatUsername(msg.author)} (${msg.author.id}): `;
}

function handleCmdErr(msg, cmd, err) {
    if (err.response) var resp = JSON.parse(err.response);
    if (resp && resp.code === 50013) {
        logger.warn(`Can't send message in '#${msg.channel.name}' (${msg.channel.id}), cmd from user '${utils.formatUsername(msg.author)}' (${msg.author.id})`);
        msg.author.getDMChannel().then(dm => {
            console.log(dm.id);
            return dm.createMessage(`It appears I was unable to send a message in \`#${msg.channel.name}\` on the server \`${msg.channel.guild.name}\`. Please give me the Send Messages permission or notify a mod or admin if you cannot do this.`);
        }).catch(() => logger.warn(`Couldn't get DM channel for/send DM to ${utils.formatUsername(msg.author)} (${msg.author.id})`));
    } else if (resp && /\{'code':.+, 'message':.+\}/.test(err.response) && resp.code !== 50013) {
        logger.warn(loggerPrefix(msg) + `Discord error running command "${cmd}":\n:${config.debug ? err.stack : err}`);
        let m = `Discord error while trying to execute \`${cmd}\`\n`;
        m += '```js\n';
        m += `Code: ${resp.code}. Message: ${resp.message}\n`;
        m += "``` If you feel this shouldn't be happening, join my support server. Invite can be found in the `invite` command.";
        msg.channel.createMessage(m);
    } else {
        logger.error(loggerPrefix(msg) + `Error running command "${cmd}":\n${config.debug ? err.stack || err : err}`);
        let m = `Experienced error while executing \`${cmd}\`\n`;
        m += '```js\n';
        m += err + '\n';
        m += "``` If you feel this shouldn't be happening, join my support server. Invite can be found in the `invite` command.";
        msg.channel.createMessage(m);
    }
}

bot.awaitMessage = (channelID, userID, filter = function () { return true; }, timeout = 15000) => {
    return new Promise((resolve, reject) => {
        if (!channelID || typeof channelID !== 'string') {
            reject(new Error(`Unwanted type of channelID: got "${typeof channelID}" expected "string"`));
        } else if (!userID || typeof userID !== 'string') {
            reject(new Error(`Unwanted type of userID: got "${typeof userID}" expected "string"`));
        } else {
            var responded, rmvTimeout;

            var onCrt = (msg) => {
                if (msg.channel.id === channelID && msg.author.id === userID && filter(msg)) {
                    responded = true;
                    return msg;
                }
            };

            var onCrtWrap = (msg) => {
                var res = onCrt(msg);
                if (responded) {
                    bot.removeListener('messageCreate', onCrtWrap);
                    clearInterval(rmvTimeout);
                    resolve(res);
                }
            };

            bot.on('messageCreate', onCrtWrap);

            rmvTimeout = setTimeout(() => {
                if (!responded) {
                    bot.removeListener('messageCreate', onCrtWrap);
                    reject(new Error('Message await expired.'));
                }
            }, timeout);
        }
    });
};<|endoftext|>


<commit_before>const Discord = require('discord.js');
const utils = require('./utils');
const actions = require('./botActions');
const log = require('./logger').getLogger();
const config = require('./configManager').getConfigManager();

class Bot {
  constructor(commands, token, guildSettings, guildLevels) {
    this.commands = commands.commands;
    this.prefix = commands.prefix;
    this.token = token;
    this.guildSettings = guildSettings;
    this.guildLevels = guildLevels;
    this.configureBot();
    this.resetBot();
  }

  // sets all the handlers for bot actions
  configureBot() {
    this.bot = new Discord.Client();
    this.bot.on('ready', () => {
      log.info('Mari bot ready for combat!');
    });

    this.bot.on('message', (msg) => {
      this.handleMessage(msg);
    });

    this.bot.on('guildMemberAdd', (member) => {
      const guildSettings = this.guildSettings[member.guild.name];
      if (guildSettings && guildSettings.welcomeMessage && guildSettings.welcomeChannel) {
        actions.welcomeMember(member, guildSettings);
      }
    });

    this.bot.on('voiceStateUpdate', (newMember, oldMember) => {
      this.newPhoneWhoDis(newMember, oldMember);
    });

    this.bot.on('messageReactionAdd', (reaction) => {
      this.handleReactionAdded(reaction);
    });

    this.bot.on('messageReactionRemove', (reaction) => {
      this.handleReactionRemoved(reaction);
    });

    this.bot.on('error', (e) => {
      log.error('ERROR bot crashed!: ');
      for (const prop of e) {
        log.error(prop);
      }
      try {
        this.resetBot();
      } catch (error) {
        log.error(`Could not reset the bot: ${error}`);
        process.exit(1);
      }
    });

    this.bot.on('warn', (warning) => {
      log.warn(`warning: ${warning}`);
    });

    this.bot.on('disconnect', (closeEvent) => {
      log.warn(`Websocket connection failed with error code ${closeEvent.code}! attempting to restart bot`);
      log.warn(closeEvent.reason);
      this.resetBot();
    });

    this.bot.on('debug', (info) => {
      // ignore heartbeats
      if (info.toLowerCase().includes('heartbeat')) {
        return;
      }
      log.info(`debug: ${info}`);
    });
  }

  // send 'blocked' as a reaction to the message
  blocked(msg) {
    // 📢🇧🇱🇴🇨🇰🇪🇩
    msg.react('🇧').then(() => {
      return msg.react('🇱');
    }).then(() => {
      return msg.react('🇴');
    }).then(() => {
      return msg.react('🇨');
    }).then(() => {
      return msg.react('🇰');
    }).then(() => {
      return msg.react('🇪');
    }).then(() => {
      return msg.react('🇩');
    }).then(() => {
      log.info('sent \'blocked\' reaction');
    }).catch((e) => {
      log.error(`failed to send blocked: ${e}`);
    });
  }

  handleReactionAdded(reaction) {
    // this means the bot has already added this reaction
    if (reaction.me) {
      return;
    }
    if (reaction.emoji.name === '📢') {
      this.blocked(reaction.message);
    }
    const { channel } = reaction.message;
    if (!channel.guild || this.guildSettings[channel.guild.name].react) {
      reaction.message.react(reaction.emoji).then(() => {
        log.info(`Reacted with ${reaction.emoji}`);
      }).catch((e) => {
        log.error(`Failed to react to message: ${e}`);
      });
    }
  }

  handleReactionRemoved(reaction) {
    // don't remove until the bot is the only one left who's reacted
    if (reaction.users.length > 1) {
      return;
    }
    const { channel } = reaction.message;
    if (!channel.guild || this.guildSettings[channel.guild.name].react) {
      if (reaction.users.get(this.bot.user.id)) {
        reaction.remove(this.bot.user).then(() => {
          log.info(`Removed reaction ${reaction.emoji}`);
        }).catch((e) => {
          log.error(`Failed to remove reaction: ${e}`);
        });
      }
    }
  }

  // NEW PHONE WHO DIS
  // play new phone audio clip when a new user comes into the same channel as the bot
  newPhoneWhoDis(oldMember, newMember) {
    if (this.bot.voiceConnections.size === 0) {
      return;
    }
    if (oldMember && newMember) {
      if (oldMember.mute !== newMember.mute || oldMember.deaf !== newMember.deaf) {
        return;
      }
    }
    if (newMember && newMember.voiceChannelID) {
      const voiceConnection = actions.getChannelVoice(newMember.voiceChannelID, this.bot.voiceConnections);
      if (voiceConnection) {
        actions.playAudioCommand(this.bot.voiceConnections, this.commands.newphone, newMember.voiceChannelID, voiceConnection);
      }
    }
  }

  // handling of normal commands
  // check if this channel allows the bot and if this user can use the bot in this server.
  // Then, if the message contains a command the bot can understand, handle it.
  // then pass along to the handler function
  handleMessage(msg) {
    // don't respond to your own messages
    if (msg.author.username === 'mari-bot') {
      return;
    }

    // storing memes for later use
    if (msg.channel && msg.channel.name && msg.channel.name.includes('memes')) {
      this.saveMeme(msg);
    }

    // load data for current guild setup and user levels
    // no guild means message is PM, so guild restrictions are not applicable
    const currentGuildSettings = msg.channel.guild ? this.guildSettings[msg.channel.guild.name] : null;
    const currentGuildLevels = msg.channel.guild ? this.guildLevels[msg.channel.guild.name] : null;

    // check if we should level up a user
    if (currentGuildSettings && currentGuildLevels) {
      actions.levelUpUser(currentGuildLevels, currentGuildSettings, msg.author, msg.channel);
    }

    // ignore if message doesn't start with the prefix
    if (!msg.content.startsWith(this.prefix)) {
      return;
    }

    // check if this is a channel that the bot should read
    // if no registered settings assume no restrictions
    if (currentGuildSettings) {
      if (!this.botCanPostToChannel(currentGuildSettings.channels, msg.channel)) { // make sure this channel is ok to use
        msg.author.send(`Sorry, you can't use mari-bot from ${msg.channel.name} in ${msg.channel.guild.name}`);
        return;
      }
      if (!this.userAllowedToUseBot(currentGuildSettings.roles, msg.member.roles)) { // make sure user is allowed to use bot
        msg.author.send(`Sorry, you don't have permission to use mari-bot in ${msg.channel.guild.name}`);
        return;
      }
    }
    // commands can't have spaces, remove anything beyond the first space
    let [importantBit] = msg.content.split(' ');
    // cut out the prefix
    importantBit = importantBit.toLowerCase().slice(1);
    log.info(`Received: ${importantBit}`);
    for (const command in this.commands) {
      if (!this.commands.hasOwnProperty(command)) {
        continue;
      }
      if (importantBit === command) {
        this.handleCommand(this.commands[command], msg, currentGuildLevels, currentGuildSettings);
      }
    }
  }

  userAllowedToUseBot(allowedRoles, userRoles) {
    // if no options, assume all are allowed
    if (!allowedRoles) {
      return true;
    }
    for (const role of allowedRoles) {
      if (userRoles.filter(userRole => userRole.name.toLowerCase() === role.toLowerCase()).size) {
        return true;
      }
    }
    return false;
  }

  botCanPostToChannel(allowedChannels, channel) {
    // if no options, assume all are allowed
    if (!allowedChannels) {
      return true;
    }
    return !!allowedChannels.filter(channelName => channel.name === channelName).length;
  }

  // Reset the bot (or start it if it's not already running)
  // if channel is given, notify that channel when reset is complete
  resetBot(channel) {
    if (this.bot && this.bot.readyTimestamp) {
      this.bot.destroy().then(() => {
        this.bot.login(this.token).then(() => {
          log.info('Reset complete!');
        });
      }).catch((reason) => {
        log.info(`Failed to logout: ${reason}`);
      });
    } else {
      this.bot.login(this.token).then(() => {
        if (channel) {
          channel.send('Reset complete!');
        }
      });
    }
  }

  // breakout command types into their handler functions
  handleCommand(command, msg, guildLevels, guildSettings) {
    const type = command.type.toLowerCase();
    switch (type) {
      case 'audio': {
        actions.handleAudioCommand(command, msg, this.bot.voiceConnections, this.bot.guilds);
        break;
      }
      case 'stop': {
        actions.stopTalkingInGuild(msg.guild, this.bot.voiceConnections);
        break;
      }
      case 'text': {
        if (command.response) {
          actions.sendMessage(command.response, msg.channel);
        } else {
          const beginning = command.beginning ? command.beginning : '';
          const response = command.responses[Math.floor(Math.random() * command.responses.length)];
          const ending = command.ending ? command.ending : '';
          actions.sendMessage(beginning + response + ending, msg.channel);
        }
        break;
      }
      case 'move': {
        actions.joinChannel(this.bot.guilds, msg);
        break;
      }
      case 'leave': {
        const connection = actions.getVoiceInGuild(this.bot.voiceConnections, msg.guild);
        if (connection) {
          connection.disconnect();
        }
        break;
      }
      case 'go': {
        const channelName = msg.content.substring(4);
        log.info(`Moving to: ${channelName}`);
        actions.joinChannel(this.bot.guilds, null, channelName);
        break;
      }
      case 'meme': {
        const { urls } = command;
        const url = urls[Math.floor(Math.random() * urls.length)];
        actions.sendMessage(url, msg.channel);
        break;
      }
      case 'memeAdd': {
        this.saveMeme(msg);
        break;
      }
      case 'help': {
        actions.sendHelpMessage(msg, this.commands);
        break;
      }
      case 'level': {
        const userName = msg.content.substring(7);
        const level = actions.getLevelOfUser(guildLevels, guildSettings, msg.channel.guild.members, userName);
        if (level) {
          actions.sendMessage(`${userName} is level ${level}`, msg.channel);
        } else {
          actions.sendMessage(`${userName} is not a user in this discord server`, msg.channel);
        }
        break;
      }
      case 'reset': {
        config.readCommands().then((commands) => {
          this.setConfigAndReset(commands, msg.channel);
        });
        break;
      }
      default: {
        actions.sendMessage('Something\'s fucked. Yell at Taylor to fix it.', msg.channel);
        break;
      }
    }
  }

  saveMeme(msg) {
    const words = msg.content.split(' ');
    for (let i = 0; i < words.length; i += 1) {
      const meme = words[i];
      if (utils.isURL(meme)) {
        if (this.commands.meme.urls.indexOf(meme) === -1) {
          this.commands.meme.urls.push(meme);
          this.commandsUpdated = true;
          log.info(`Added a new meme to my collection: \n${meme}`);
        } else {
          log.info(`Avoided duplicate meme: ${meme}`);
        }
      }
    }
  }

  sendSubMessage(streamer) {
    const msg = (streamer.message ? streamer.message : `${streamer.name} is now live on twitch! https://www.twitch.tv/${streamer.name}`);
    for (const server of streamer.servers) {
      actions.sendMessage(msg, this.bot.guilds.get(server.id).channels.get(server.channel));
    }
  }

  setConfigAndReset(data, channel, memes) {
    this.prefix = data.prefix;
    const tempMemes = this.commands.meme;
    this.commands = data.commands;
    if (memes) {
      this.commands.meme = memes;
    } else {
      this.commands.meme = tempMemes;
    }
    // start your engines!
    this.resetBot(channel);
  }
}

module.exports = Bot;
<commit_msg>Remove reactions during blocking reaction<commit_after>const Discord = require('discord.js');
const utils = require('./utils');
const actions = require('./botActions');
const log = require('./logger').getLogger();
const config = require('./configManager').getConfigManager();

class Bot {
  constructor(commands, token, guildSettings, guildLevels) {
    this.commands = commands.commands;
    this.prefix = commands.prefix;
    this.token = token;
    this.guildSettings = guildSettings;
    this.guildLevels = guildLevels;
    this.blocking = false;
    this.configureBot();
    this.resetBot();
  }

  // sets all the handlers for bot actions
  configureBot() {
    this.bot = new Discord.Client();
    this.bot.on('ready', () => {
      log.info('Mari bot ready for combat!');
    });

    this.bot.on('message', (msg) => {
      this.handleMessage(msg);
    });

    this.bot.on('guildMemberAdd', (member) => {
      const guildSettings = this.guildSettings[member.guild.name];
      if (guildSettings && guildSettings.welcomeMessage && guildSettings.welcomeChannel) {
        actions.welcomeMember(member, guildSettings);
      }
    });

    this.bot.on('voiceStateUpdate', (newMember, oldMember) => {
      this.newPhoneWhoDis(newMember, oldMember);
    });

    this.bot.on('messageReactionAdd', (reaction) => {
      this.handleReactionAdded(reaction);
    });

    this.bot.on('messageReactionRemove', (reaction) => {
      this.handleReactionRemoved(reaction);
    });

    this.bot.on('error', (e) => {
      log.error('ERROR bot crashed!: ');
      for (const prop of e) {
        log.error(prop);
      }
      try {
        this.resetBot();
      } catch (error) {
        log.error(`Could not reset the bot: ${error}`);
        process.exit(1);
      }
    });

    this.bot.on('warn', (warning) => {
      log.warn(`warning: ${warning}`);
    });

    this.bot.on('disconnect', (closeEvent) => {
      log.warn(`Websocket connection failed with error code ${closeEvent.code}! attempting to restart bot`);
      log.warn(closeEvent.reason);
      this.resetBot();
    });

    this.bot.on('debug', (info) => {
      // ignore heartbeats
      if (info.toLowerCase().includes('heartbeat')) {
        return;
      }
      log.info(`debug: ${info}`);
    });
  }

  // send 'blocked' as a reaction to the message
  blocked(msg) {
    // 📢🇧🇱🇴🇨🇰🇪🇩
    this.blocking = true;
    msg.react('🇧').then(() => {
      return msg.react('🇱');
    }).then(() => {
      return msg.react('🇴');
    }).then(() => {
      return msg.react('🇨');
    }).then(() => {
      return msg.react('🇰');
    }).then(() => {
      return msg.react('🇪');
    }).then(() => {
      return msg.react('🇩');
    }).then(() => {
      log.info('sent \'blocked\' reaction');
      this.blocking = false;
    }).catch((e) => {
      log.error(`failed to send blocked: ${e}`);
      this.blocking = false;
    });
  }

  handleReactionAdded(reaction) {
    // this means the bot has already added this reaction
    if (reaction.me) {
      return;
    }
    const { channel } = reaction.message;
    if (!channel.guild || this.guildSettings[channel.guild.name].react) {
      if (reaction.emoji.name === '📢') {
        this.blocked(reaction.message);
      }
      if (this.blocking) {
        reaction.remove();
        reaction.message.author.send('Shhh I\'m working');
        return;
      }
      reaction.message.react(reaction.emoji).then(() => {
        log.info(`Reacted with ${reaction.emoji}`);
      }).catch((e) => {
        log.error(`Failed to react to message: ${e}`);
      });
    }
  }

  handleReactionRemoved(reaction) {
    // don't remove until the bot is the only one left who's reacted
    if (reaction.users.length > 1) {
      return;
    }
    const { channel } = reaction.message;
    if (!channel.guild || this.guildSettings[channel.guild.name].react) {
      if (reaction.users.get(this.bot.user.id)) {
        reaction.remove(this.bot.user).then(() => {
          log.info(`Removed reaction ${reaction.emoji}`);
        }).catch((e) => {
          log.error(`Failed to remove reaction: ${e}`);
        });
      }
    }
  }

  // NEW PHONE WHO DIS
  // play new phone audio clip when a new user comes into the same channel as the bot
  newPhoneWhoDis(oldMember, newMember) {
    if (this.bot.voiceConnections.size === 0) {
      return;
    }
    if (oldMember && newMember) {
      if (oldMember.mute !== newMember.mute || oldMember.deaf !== newMember.deaf) {
        return;
      }
    }
    if (newMember && newMember.voiceChannelID) {
      const voiceConnection = actions.getChannelVoice(newMember.voiceChannelID, this.bot.voiceConnections);
      if (voiceConnection) {
        actions.playAudioCommand(this.bot.voiceConnections, this.commands.newphone, newMember.voiceChannelID, voiceConnection);
      }
    }
  }

  // handling of normal commands
  // check if this channel allows the bot and if this user can use the bot in this server.
  // Then, if the message contains a command the bot can understand, handle it.
  // then pass along to the handler function
  handleMessage(msg) {
    // don't respond to your own messages
    if (msg.author.username === 'mari-bot') {
      return;
    }

    // storing memes for later use
    if (msg.channel && msg.channel.name && msg.channel.name.includes('memes')) {
      this.saveMeme(msg);
    }

    // load data for current guild setup and user levels
    // no guild means message is PM, so guild restrictions are not applicable
    const currentGuildSettings = msg.channel.guild ? this.guildSettings[msg.channel.guild.name] : null;
    const currentGuildLevels = msg.channel.guild ? this.guildLevels[msg.channel.guild.name] : null;

    // check if we should level up a user
    if (currentGuildSettings && currentGuildLevels) {
      actions.levelUpUser(currentGuildLevels, currentGuildSettings, msg.author, msg.channel);
    }

    // ignore if message doesn't start with the prefix
    if (!msg.content.startsWith(this.prefix)) {
      return;
    }

    // check if this is a channel that the bot should read
    // if no registered settings assume no restrictions
    if (currentGuildSettings) {
      if (!this.botCanPostToChannel(currentGuildSettings.channels, msg.channel)) { // make sure this channel is ok to use
        msg.author.send(`Sorry, you can't use mari-bot from ${msg.channel.name} in ${msg.channel.guild.name}`);
        return;
      }
      if (!this.userAllowedToUseBot(currentGuildSettings.roles, msg.member.roles)) { // make sure user is allowed to use bot
        msg.author.send(`Sorry, you don't have permission to use mari-bot in ${msg.channel.guild.name}`);
        return;
      }
    }
    // commands can't have spaces, remove anything beyond the first space
    let [importantBit] = msg.content.split(' ');
    // cut out the prefix
    importantBit = importantBit.toLowerCase().slice(1);
    log.info(`Received: ${importantBit}`);
    for (const command in this.commands) {
      if (!this.commands.hasOwnProperty(command)) {
        continue;
      }
      if (importantBit === command) {
        this.handleCommand(this.commands[command], msg, currentGuildLevels, currentGuildSettings);
      }
    }
  }

  userAllowedToUseBot(allowedRoles, userRoles) {
    // if no options, assume all are allowed
    if (!allowedRoles) {
      return true;
    }
    for (const role of allowedRoles) {
      if (userRoles.filter(userRole => userRole.name.toLowerCase() === role.toLowerCase()).size) {
        return true;
      }
    }
    return false;
  }

  botCanPostToChannel(allowedChannels, channel) {
    // if no options, assume all are allowed
    if (!allowedChannels) {
      return true;
    }
    return !!allowedChannels.filter(channelName => channel.name === channelName).length;
  }

  // Reset the bot (or start it if it's not already running)
  // if channel is given, notify that channel when reset is complete
  resetBot(channel) {
    if (this.bot && this.bot.readyTimestamp) {
      this.bot.destroy().then(() => {
        this.bot.login(this.token).then(() => {
          log.info('Reset complete!');
        });
      }).catch((reason) => {
        log.info(`Failed to logout: ${reason}`);
      });
    } else {
      this.bot.login(this.token).then(() => {
        if (channel) {
          channel.send('Reset complete!');
        }
      });
    }
  }

  // breakout command types into their handler functions
  handleCommand(command, msg, guildLevels, guildSettings) {
    const type = command.type.toLowerCase();
    switch (type) {
      case 'audio': {
        actions.handleAudioCommand(command, msg, this.bot.voiceConnections, this.bot.guilds);
        break;
      }
      case 'stop': {
        actions.stopTalkingInGuild(msg.guild, this.bot.voiceConnections);
        break;
      }
      case 'text': {
        if (command.response) {
          actions.sendMessage(command.response, msg.channel);
        } else {
          const beginning = command.beginning ? command.beginning : '';
          const response = command.responses[Math.floor(Math.random() * command.responses.length)];
          const ending = command.ending ? command.ending : '';
          actions.sendMessage(beginning + response + ending, msg.channel);
        }
        break;
      }
      case 'move': {
        actions.joinChannel(this.bot.guilds, msg);
        break;
      }
      case 'leave': {
        const connection = actions.getVoiceInGuild(this.bot.voiceConnections, msg.guild);
        if (connection) {
          connection.disconnect();
        }
        break;
      }
      case 'go': {
        const channelName = msg.content.substring(4);
        log.info(`Moving to: ${channelName}`);
        actions.joinChannel(this.bot.guilds, null, channelName);
        break;
      }
      case 'meme': {
        const { urls } = command;
        const url = urls[Math.floor(Math.random() * urls.length)];
        actions.sendMessage(url, msg.channel);
        break;
      }
      case 'memeAdd': {
        this.saveMeme(msg);
        break;
      }
      case 'help': {
        actions.sendHelpMessage(msg, this.commands);
        break;
      }
      case 'level': {
        const userName = msg.content.substring(7);
        const level = actions.getLevelOfUser(guildLevels, guildSettings, msg.channel.guild.members, userName);
        if (level) {
          actions.sendMessage(`${userName} is level ${level}`, msg.channel);
        } else {
          actions.sendMessage(`${userName} is not a user in this discord server`, msg.channel);
        }
        break;
      }
      case 'reset': {
        config.readCommands().then((commands) => {
          this.setConfigAndReset(commands, msg.channel);
        });
        break;
      }
      default: {
        actions.sendMessage('Something\'s fucked. Yell at Taylor to fix it.', msg.channel);
        break;
      }
    }
  }

  saveMeme(msg) {
    const words = msg.content.split(' ');
    for (let i = 0; i < words.length; i += 1) {
      const meme = words[i];
      if (utils.isURL(meme)) {
        if (this.commands.meme.urls.indexOf(meme) === -1) {
          this.commands.meme.urls.push(meme);
          this.commandsUpdated = true;
          log.info(`Added a new meme to my collection: \n${meme}`);
        } else {
          log.info(`Avoided duplicate meme: ${meme}`);
        }
      }
    }
  }

  sendSubMessage(streamer) {
    const msg = (streamer.message ? streamer.message : `${streamer.name} is now live on twitch! https://www.twitch.tv/${streamer.name}`);
    for (const server of streamer.servers) {
      actions.sendMessage(msg, this.bot.guilds.get(server.id).channels.get(server.channel));
    }
  }

  setConfigAndReset(data, channel, memes) {
    this.prefix = data.prefix;
    const tempMemes = this.commands.meme;
    this.commands = data.commands;
    if (memes) {
      this.commands.meme = memes;
    } else {
      this.commands.meme = tempMemes;
    }
    // start your engines!
    this.resetBot(channel);
  }
}

module.exports = Bot;
<|endoftext|>


<commit_before>define([
    'amber/devel',
    './deploy',
    // --- packages used only during development begin here ---
    '{%= namespace %}/{%= name %}-Tests',
    'amber-attic/IDE'
    // --- packages used only during development end here ---
], function (amber) {
    return amber;
});
<commit_msg>Load Benchfib and Examples from attic as well<commit_after>define([
    'amber/devel',
    './deploy',
    // --- packages used only during development begin here ---
    '{%= namespace %}/{%= name %}-Tests',
    'amber-attic/Benchfib',
    'amber-attic/Examples',
    'amber-attic/IDE'
    // --- packages used only during development end here ---
], function (amber) {
    return amber;
});
<|endoftext|>


<commit_before>JQ = jQuery.noConflict();

JQ(document).ready(function() {
	// Find the select box, named differently on the update and add forms
	var sel = JQ('select[id="DataObjectManager_Popup_DetailForm_LinkType"]');

	if (sel.attr('id') == undefined) {
		sel = JQ('#DataObjectManager_Popup_AddForm_LinkType');
	}

	// hide either the internal or external link editing box
	if (sel.val() == 'Internal') {
		JQ('#URL').toggle();
	}  else {
		JQ('.autocomplete_holder').toggle();
	};

	// toggle boxes on drop down change
	sel.change(function(e) {
  		JQ('#URL').toggle();
  		JQ('.autocomplete_holder').toggle();
	});

});<commit_msg>BUGFIX: Wrap jquery properly so as not to clash with prototype<commit_after>JQ = jQuery.noConflict();

/*jslint white: true */
(function($) {

	$(document).ready(function() {
		// Find the select box, named differently on the update and add forms
		var sel = $('select[id="DataObjectManager_Popup_DetailForm_LinkType"]');

		if (sel.attr('id') == undefined) {
			sel = $('#DataObjectManager_Popup_AddForm_LinkType');
		}

		// hide either the internal or external link editing box
		if (sel.val() == 'Internal') {
			$('#URL').toggle();
		}  else {
			$('.autocomplete_holder').toggle();
		};

		// toggle boxes on drop down change
		sel.change(function(e) {
	  		$('#URL').toggle();
	  		$('.autocomplete_holder').toggle();
		});

	});

})(jQuery);
<|endoftext|>