Beispiel #1
0
class Games:
    def __init__(self, bot):
        self.bot = bot
        self.conf = Config('configs/games.json')

    @perms.is_owner()
    @commands.command(pass_context=True, aliases=['faa'])
    async def fake_artist_add(self, ctx, *, themes):
        self.conf['fake_artist']['themes'].extend(themes.strip().split('\n'))
        self.conf.save()
        await self.bot.say(formatter.ok())

    @commands.command(pass_context=True, aliases=['fa'])
    async def fake_artist(self, ctx, number: int):
        conf = self.conf.get('fake_artist', {})
        themes = conf.get('themes', [])
        themes = random.sample(themes, len(themes) - len(themes) % number)
        output = [[] for i in range(number)]
        fakes = list(range(number)) * (len(themes) // number)
        random.shuffle(fakes)
        say = 'here are the links:'

        # generate
        for theme, fake in zip(themes, fakes):
            for i in range(len(output)):
                output[i].append(theme if i != fake else 'YOU ARE THE FAKE')

        # generate master file
        with open(os.path.join(conf.get('path', ''), 'master.html'), 'w') as f:
            f.write(conf.get('rules'))
            for i, theme in enumerate(themes):
                f.write(f'''<li><input class="spoilerbutton" type="button"'''+ \
                        f'''value="show" onclick="this.value=this.value=='show'''+ \
                        f'''\'?'{html.escape(theme)}':'show';"></li>''')
            f.write(conf.get('out'))

        # generate player files
        for i in range(len(output)):
            filename = os.path.join(conf.get('path', ''), f'{i+1}.html')
            with open(filename, 'w') as f:
                f.write(conf.get('rules'))
                for theme in output[i]:
                    f.write(f'<li>{html.escape(theme)}</li>')
                f.write(conf.get('out'))
            say += f'\nhttps://andy29485.tk/files/{i+1}.html'

        await self.bot.say(formatter.ok(say))
Beispiel #2
0
class HeapCog:
    def __init__(self, bot):
        self.bot = bot
        self.conf = Config('configs/heap.json')
        if 'heap' not in self.conf:
            self.conf['heap'] = heap.Heap()

        bot.loop.create_task(self.check_heap())

    async def check_heap(self):
        while self == self.bot.get_cog('HeapCog'):
            heap_popped = False
            # if there are valid items that expired/expire soon, process them
            while self.conf['heap'].time_left < 2:
                item = self.conf['heap'].pop()  # remove item from heap
                await item.end(self.bot)  # perform its task
                heap_popped = True  # signify that a save is needed

            # only save heap to disk if an item was pop
            if heap_popped:
                self.conf.save()

            # wait a bit and check again
            await asyncio.sleep(min(self.conf['heap'].time_left, 30) + 0.5)
Beispiel #3
0
import hashlib
import asyncio
from cogs.utils.config import Config
from embypy import Emby as EmbyPy

colours = [
    0x1f8b4c, 0xc27c0e, 0x3498db, 0x206694, 0x9b59b6, 0x71368a, 0xe91e63,
    0xe67e22, 0xf1c40f, 0x1abc9c, 0x2ecc71, 0xa84300, 0xe74c3c, 0xad1457,
    0x11806a
]

conf = Config('configs/emby.json')

if 'address' not in conf or not conf['address']:
    conf['address'] = input('Enter emby url: ')
    conf.save()
if 'watching' not in conf or 'last' not in conf['watching']:
    conf['watching'] = {'last': None}
    conf.save()
if 'auth' not in conf or not conf['auth']:
    conf['auth'] = {}
    conf['auth']['api_key'] = input('Enter emby api key: ')
    conf['auth']['userid'] = input('Enter emby user id: ')
    conf['auth']['device_id'] = input('Enter emby device id: ')
    conf.save()

conn = EmbyPy(conf['address'], **conf['auth'], ws=False)


async def makeEmbed(item, message=''):
    loop = asyncio.get_event_loop()
Beispiel #4
0
class Regex:
    def __init__(self, bot):
        self.bot = bot
        self.replacements = Config('configs/replace.json')
        self.permissions = Config('configs/perms.json')
        if 'rep-blacklist' not in self.permissions:
            self.permissions['rep-blacklist'] = []

    @commands.group(pass_context=True)
    async def rep(self, ctx):
        """Manage replacements
    Uses a regex to replace text from users
    """
        if ctx.invoked_subcommand is None:
            await self.bot.say(
                formatter.error(
                    'Error, {0.subcommand_passed} is not a valid command'.
                    format(ctx)))

    @rep.command(name='add', pass_context=True)
    async def _add(self, ctx, *, regex):
        """adds a new replacement
    Format `s/old/new/`
    """

        if ctx.message.author.id in self.permissions['rep-blacklist']:
            await self.bot.say(formatter.error('No ') + ':poi:')
            return

        #Find requested replacement
        rep = get_match(regex)

        #ensure that replace was found before proceeding
        if not rep:
            await self.bot.say(formatter.error('Could not find valid regex'))
            return

        p1 = formatter.escape_mentions(rep.group(2))
        p2 = formatter.escape_mentions(rep.group(4))

        #check regex for validity
        if not comp(p1, p2):
            await self.bot.say(formatter.error('regex is invalid'))
            return

        #make sure that there are no similar regexes in db
        for i in self.replacements:
            if similar(p1, i):
                r = '\"{}\" -> \"{}\"'.format(i, self.replacements[i][0])
                message = 'Similar regex already exists, delete or edit it\n{}'.format(
                    formatter.inline(r))
                await self.bot.say(formatter.error(message))
                return

        #make sure regex is not too broad
        if bad_re(p1):
            await self.bot.say(formatter.error('regex is too broad'))
            return

        #check that regex does not already exist
        if p1 in self.replacements:
            await self.bot.say(formatter.error('regex already exists'))
            return

        self.replacements[p1] = [p2, ctx.message.author.id]
        await self.bot.say(formatter.ok())

    @rep.command(name='edit', pass_context=True)
    async def _edit(self, ctx, *, regex):
        """edits an existing replacement
    Format `s/old/new/`
    """

        #Find requested replacement
        rep = get_match(regex)

        #ensure that replace was found before proceeding
        if not rep:
            await self.bot.say(formatter.error('Could not find valid regex'))
            return

        p1 = formatter.escape_mentions(rep.group(2))
        p2 = formatter.escape_mentions(rep.group(4))

        #check regex for validity
        if not comp(p1, p2):
            await self.bot.say(formatter.error('regex is invalid'))
            return

        #make sure regex is not too broad
        if bad_re(p1):
            await self.bot.say(formatter.error('regex is too broad'))
            return

        #ensure that replace was found before proceeding
        if p1 not in self.replacements:
            await self.bot.say(formatter.error('Regex not in replacements.'))
            return

        #check if they have correct permissions
        if ctx.message.author.id != self.replacements[p1][1] \
           and not perms.is_owner_check(ctx.message):
            #will uncomment next line when reps are a per server thing
            #and not perms.check_permissions(ctx.message, manage_messages=True):
            raise commands.errors.CheckFailure('Cannot edit')

        self.replacements[p1] = [p2, ctx.message.author.id]
        await self.bot.say(formatter.ok())

    @rep.command(name='remove', aliases=['rm'], pass_context=True)
    async def _rm(self, ctx, *, pattern):
        """remove an existing replacement"""

        #pattern = re.sub('^(`)?\\(\\?[^\\)]*\\)', '\\1', pattern)
        pattern = formatter.escape_mentions(pattern)

        #ensure that replace was found before proceeding
        if pattern not in self.replacements:
            if re.search('^`.*`$',
                         pattern) and pattern[1:-1] in self.replacements:
                pattern = pattern[1:-1]
            else:
                await self.bot.say(
                    formatter.error('Regex not in replacements.'))
                return

        #check if they have correct permissions
        if ctx.message.author.id != self.replacements[pattern][1] \
           and not perms.is_owner_check(ctx.message):
            raise commands.errors.CheckFailure('Cannot delete')

        self.replacements.pop(pattern)
        self.replacements.save()
        await self.bot.say(formatter.ok())

    @rep.command(name='list', aliases=['ls'])
    async def _ls(self):
        """list existing replacements"""
        msg = ''

        for rep in self.replacements:
            msg += '\"{}\" -> \"{}\"\n'.format(rep, self.replacements[rep][0])
        msg = msg[:-1]

        await self.bot.say(formatter.code(msg))

    async def replace(self, message):
        if message.author.bot:
            return
        if len(message.content.strip()) < 2:
            return
        if message.content.strip()[0] in self.bot.command_prefix + ['?', '$']:
            return

        m = message.content
        for i in self.replacements:
            m = re.sub(r'(?i)\b{}\b'.format(i), self.replacements[i][0], m)

        if m.lower() != message.content.lower():
            await self.bot.send_message(message.channel, '*' + m)
Beispiel #5
0
    msg = ctx.message
    chan = None
    if ctx.message.channel.is_private:
        chan = 'PM'
    else:
        chan = '#{0.channel.name} ({0.server.name})'.format(msg)

    logger.info('{0.author.name} in {1}: {0.content}'.format(msg, chan))


@bot.async_event
async def on_message(message):
    if message.author.bot:
        return

    perms = Config('configs/perms.json')
    if message.author.id in perms.get('ignore', []):
        return

    if not re.search('^[\\.!\\?\\$]{2,}', message.content):
        await bot.process_commands(message)


auth = Config('configs/auth.json')
while len(auth.get('token', '')) < 30:
    auth['token'] = input('Please enter bot\'s token: ')
    auth.save()

#start bot
bot.run(auth['token'])
Beispiel #6
0
class GroupMe:
    def __init__(self, bot):
        self.conf = Config('configs/groupme.json')
        self.bot = bot
        self.loop = bot.loop
        self.l_bots = []
        self.g_bots = {}
        self.g_groups = {}
        self.d_chans = {}

        if 'g_old' not in self.conf:
            self.conf['g_old'] = {}
        if 'links' not in self.conf:
            self.conf['links'] = {}
        if 'key' not in self.conf or not self.conf['key']:
            self.conf['key'] = input(
                'Please enter your GroupMe api key: ').strip()

        if not self.conf['key']:
            raise RuntimeError('No groupme key provied')

        self.conf.save()

        groupy.config.API_KEY = self.conf['key']

        for discord_chan_id in self.conf['links']:
            for g_id in self.conf['links'][discord_chan_id]:
                group, g_bot = self.get_group_bot(g_id)

                #print('linked discord({}) to groupme({})'.format(discord_chan_id,g_id))

                if not group:
                    #print('could not find group')
                    continue

                channel = self.bot.get_channel(discord_chan_id)
                if not channel:
                    #print('error chan')
                    continue

                if g_id not in self.g_groups:
                    #print('new g_groups: {} -> {}'.format(g_id, str(group)))
                    self.l_bots.append(g_bot)
                    self.g_groups[g_id] = group

                if channel.id in self.g_bots:
                    self.g_bots[channel.id].append(g_bot)
                    #print('append g_bots: {}'.format(str(self.g_bots)))
                else:
                    self.g_bots[channel.id] = [g_bot]
                    #print('new g_bots: {}'.format(str(self.g_bots)))

                if g_id in self.d_chans:
                    self.d_chans[g_id].append(channel)
                else:
                    self.d_chans[g_id] = [channel]

        self.loop.create_task(self.poll())

    @commands.command(pass_context=True)
    async def add_groupme_link(self, ctx, g_id: str):
        channel = ctx.message.channel
        group, g_bot = self.get_group_bot(g_id)

        if not group:
            await self.bot.say(
                formatter.error("I am not in a group with that id"))
            return

        if g_id not in self.g_groups:
            self.l_bots.append(g_bot)
            self.g_groups[g_id] = group

        if channel.id in self.g_bots:
            self.g_bots[channel.id].append(g_bot)
            self.conf['links'][channel.id].append(g_id)
        else:
            self.g_bots[channel.id] = [g_bot]
            self.conf['links'][channel.id] = [g_id]

        if g_id in self.d_chans:
            self.d_chans[g_id].append(channel)
        else:
            self.d_chans[g_id] = [channel]

        if g_id not in self.g_groups:
            self.conf['g_old'][g_id] = None

        self.conf.save()

        await self.bot.say(formatter.ok())

    async def link_from_discord(self, message):
        if message.author.bot:
            return

        if message.content.startswith('.add_groupme_link'):
            return

        try:
            g_bots = self.g_bots[message.channel.id]
            text = u'<\u200b{}> {}'.format(message.author.name,
                                           message.content)
            for a in message.attachments:
                text += '\n{}'.format(
                    str(a))  #TODO - I *think* attachments are strs
            for g_bot in g_bots:
                await self.loop.run_in_executor(None, g_bot.post, text)
        except:
            #print(self.g_bots)
            pass

    async def link_from_groupme(self, message, channels):
        try:
            #print('      send g->d - get text')
            text = message.text if message.text else ''

            name_hash = hashlib.md5()
            name_hash.update(str(message.name).strip().encode())
            name_hash = int(name_hash.hexdigest(), 16)
            #print('      send g->d - get color (\"{}\" -> {} % {} = {:02X})'.format(\
            #         str(message.name).strip(),
            #         name_hash,
            #         len(colours),
            #         colours[name_hash % len(colours)]
            #))
            c = colours[name_hash % len(colours)]

            #print('      send g->d - create embed')
            em = Embed(colour=c)

            #print('      send g->d - get attach')
            for a in message.attachments:
                #print('        attach process')
                if type(a) == groupy.object.attachments.Location:
                    text += '\n[{} - ({}, {})]'.format(a.name, a.lat, a.lng)
                elif type(a) == groupy.object.attachments.Image:
                    #print('        image: {}'.format(str(a.url)))
                    for channel in channels:
                        await self.bot.send_message(
                            channel, '<{}> {}'.format(str(message.name),
                                                      a.url))
                    #em.set_image(str(a.url))
                elif type(a) == groupy.object.attachments.Mentions:
                    pass  #TODO at some point?
                elif type(a) == groupy.object.attachments.Emoji:
                    pass  #TODO maybe when their doc explain how this works

            #print('      send g->d - set author: {} [{}]'.format(str(message.name),
            #                                               str(message.avatar_url)
            #))
            if text:
                if message.avatar_url:
                    em.set_author(name=str(message.name),
                                  icon_url=str(message.avatar_url))
                else:
                    em.set_author(name=str(message.name))

                em.description = text

                #print('      send g->d - send embed to channel(s)')
                for channel in channels:
                    #print('        sending {} to {}'.format(str(em), str(channel)))
                    await self.bot.send_message(channel, embed=em)
                #print('      send g->d - all ok')
        except Error as err:
            #print(err)
            pass

    async def poll(self):
        #print('polling')
        while self == self.bot.get_cog('GroupMe'):
            messages = []
            for bot in self.l_bots:
                #print('  group: {}'.format(str(self.g_groups[bot.group_id])))
                messages = []
                channels = self.d_chans[bot.group_id]

                try:
                    #print('    p refresh')
                    self.g_groups[bot.group_id].refresh()
                    all_messages = self.g_groups[bot.group_id].messages()

                    #print('    p splice')
                    for message in all_messages:
                        #print('      check 1')
                        if message.id == self.conf['g_old'][bot.group_id]:
                            break
                        #print('      check 2')
                        if not message.text or not message.text.startswith(
                                u'<\u200b'):
                            messages.append(message)

                    #print('    p save progress')
                    if len(all_messages) > 0:
                        self.conf['g_old'][
                            bot.group_id] = all_messages.newest.id
                        self.conf.save()

                    #print('    p send')
                    for message in reversed(messages):
                        await self.link_from_groupme(message, channels)
                except:
                    #print('    polling failed')
                    pass

            #print('    p wait')
            await asyncio.sleep(5 if messages else 25)
            #print('    p queue')

    def get_group_bot(self, g_id):
        group = None
        g_bot = None

        for g in groupy.Group.list():
            if str(g.id) == str(g_id):
                group = g
                #break

        if not group:
            return None, None

        for bot in groupy.Bot.list():
            if str(bot.group_id) == str(g_id):
                g_bot = bot
                break

        if not g_bot:
            g_bot = groupy.Bot.create('Navi',
                                      group,
                                      avatar_url=self.bot.user.avatar_url)
        return group, g_bot
Beispiel #7
0
class Quote:
    def __init__(self, bot):
        self.bot = bot
        self.quotes_dict = Config('configs/quotes.json')
        if 'quotes' not in self.quotes_dict:
            self.quotes_dict['quotes'] = []

    @commands.group(pass_context=True, aliases=['q', 'quote'])
    async def quotes(self, ctx):
        """Manage quotes"""

        if ctx.invoked_subcommand is None:
            message = ctx.message.content
            try:
                index = int(ctx.subcommand_passed)
                if index >= len(self.quotes_dict['quotes']):
                    await self.bot.say(
                        formatter.error(
                            'Quote {} does not exist'.format(index)))
                else:
                    quote = self.quotes_dict['quotes'][index]
                    message = 'On {}:\n{}'.format(
                        quote['date'], formatter.code(quote['quote']))
                    await self.bot.say(message)
            except:
                await self.bot.say(self._random(message))

    @quotes.command(name='add', pass_context=True)
    async def _add(self, ctx, *, quote):
        """adds a quote"""

        for i in self.quotes_dict['quotes']:
            if quote.lower() == i['quote'].lower():
                await self.bot.say(formatter.error('Quote already exists'))
                return

        index = len(self.quotes_dict['quotes'])
        date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.quotes_dict['quotes'].append({
            'date': date,
            'quote': quote,
            'id': ctx.message.author.id
        })
        self.quotes_dict.save()

        await self.bot.say(formatter.ok('quote added, index {}'.format(index)))

    @quotes.command(pass_context=True)
    async def random(self, ctx):
        message = ' '.join(ctx.message.content.split()[1:])
        await self.bot.say(self._random(message))

    def _random(self, message):
        quotes = self.quotes_dict['quotes']

        message = message.split()[1:]

        if message:
            quotes = []
            for quote in self.quotes_dict['quotes']:
                ok = True
                for w in message:
                    if w.lower() not in quote['quote'].lower():
                        ok = False
                        break
                if ok:
                    quotes.append(quote)

        if not quotes:
            return formatter.error('No quotes found')
        quote = random.choice(quotes)
        return 'On {}:\n{}'.format(quote['date'],
                                   formatter.code(quote['quote']))

    @quotes.command(name='remove', aliases=['rm'], pass_context=True)
    async def _rm(self, ctx, index: int):
        """remove an existing replacement by index"""

        if index >= len(self.quotes_dict['quotes']):
            await self.bot.say(
                formatter.error('Quote {} does not exist'.format(index)))
            return

        if ctx.message.author.id != self.quotes_dict['quotes'][index]['id'] \
           and not perms.check_permissions(ctx.message, manage_messages=True):
            raise commands.errors.CheckFailure('Cannot delete')

        self.quotes_dict['quotes'].pop(index)
        self.quotes_dict.save()

        await self.bot.say(formatter.ok())
Beispiel #8
0
class Osu:
  breatmap_sets_url_patterns=[re.compile('https?://osu.ppy.sh/s/(\\d+)')]
  breatmap_url_patterns     =[re.compile('https?://osu.ppy.sh/b/(\\d+)')]
  user_url_patterns         =[re.compile('https?://osu.ppy.sh/u/(\\d+)')]

  def __init__(self, bot):
    self.bot           = bot
    self.loop          = bot.loop
    self.conf          = Config('configs/osu.json')

    if 'api-key' not in self.conf:
      self.conf['api-key'] = input('enter osu api key: ')
    if 'watched-users' not in self.conf:
      self.conf['watched-users'] = {}
    self.conf.save()

    self.api=osuapi.OsuApi(self.conf['api-key'],connector=osuapi.AHConnector())

    self.loop.create_task(self.check_scores())

  async def osu_info(self, message):
    if message.author.bot:
      return

    chan = message.channel

    for pattern in Osu.breatmap_sets_url_patterns:
      for bid in pattern.findall(message.content):
        beatmap = await self.api.get_beatmaps(beatmapset_id=bid)
        em = await self.osu_embed(beatmap)
        if not em:
          self.bot.send_message(chan, 'could not find beatmap')
        else:
          await self.bot.send_message(chan, embed=em)
      else:
        continue
      break

    for pattern in Osu.breatmap_url_patterns:
      for bid in pattern.findall(message.content):
        beatmap = await self.api.get_beatmaps(beatmap_id=bid)
        em = await self.osu_embed(beatmap[0])
        if not em:
          self.bot.send_message(chan, 'could not find beatmap')
        else:
          await self.bot.send_message(chan, embed=em)
      else:
        continue
      break

    for pattern in Osu.user_url_patterns:
      for uid in pattern.findall(message.content):
        user = await self.api.get_user(int(uid))
        em = await self.osu_embed(user[0])
        if not em:
          self.bot.send_message(chan, 'could not find user')
        else:
          await self.bot.send_message(chan, embed=em)
      else:
        continue
      break

  @commands.group(name='osu', aliases=["o"], pass_context=True)
  async def _osu(self, ctx):
    """
    manages osu stuff
    """
    if ctx.invoked_subcommand is None:
      await self.bot.say(formatter.error("Please specify valid subcommand"))

  @_osu.command(name='recent', aliases=['r', 'last'], pass_context=True)
  async def _recent(self, ctx, osu_username : str =''):
    """
    shows last played map for a user
    if no user is specified, shows your last played item
    """
    auth = ctx.message.author.id
    if osu_username:
      user = osu_username
    elif auth in self.conf['watched-users']:
      user = self.conf['watched-users'][auth]['uid']
    else:
      await self.bot.say('No user specified, nor are you linked to an OSU user')
      return

    user = await self.api.get_user(user)
    if not user:
      await self.bot.say('Could not find user with matching name')
      return
    user = user[0]

    last = await self.api.get_user_recent(user.user_id, limit=50)
    last = [l for l in last if (not l.perfect) or (l.rank in 'SX')]
    if not last:
      await self.bot.say(f'No recent play history for {user.username}')
      return

    em = await self.osu_embed(last[0])
    await self.bot.say(embed=em)

  @_osu.command(name='disconnect', aliases=['u', 'unlink'], pass_context=True)
  async def _osu_delink(self, ctx):
    """
    Unlinks a channel from watching an osu account
    """
    auth = ctx.message.author.id
    chan = ctx.message.channel.id

    if auth not in self.conf['watched-users']:
      await self.bot.say("You arn't linked to an OSU account yet")
      return

    if chan not in self.conf['watched-users'][auth]['chans']:
      await self.bot.say('This channel is not linked to your account')
      return

    self.conf['watched-users'][auth]['chans'].remove(chan)
    self.conf.save()

    await self.bot.say(formatter.ok("Channel is now unlinked"))

  @_osu.command(name='connect', aliases=['c', 'link'], pass_context=True)
  async def _osu_link(self, ctx, osu_username = '', top_scores : int = 50):
    """
    Links a discord user account to an osu account
    osu_username - the username that you go by on https://osu.ppy.sh
    top_scores   - the top scores to report from latest [0, 100]
    """
    auth = ctx.message.author.id
    chan = ctx.message.channel.id

    user = self.conf['watched-users'].get(auth, {}).get('uid', None)
    user = await self.api.get_user(osu_username or user)
    if not user:
      await self.bot.say('could not find user')
      return

    top_scores = max(0, min(100, top_scores))
    name       = user[0].username
    user       = user[0].user_id
    if top_scores:
      best     = await self.api.get_user_best(user, limit=top_scores)
      best     = [(i.beatmap_id, i.score) for i in best]
    else:
      best = []

    if auth in self.conf['watched-users']:
      self.conf['watched-users'][auth]['uid'] = user
      if chan not in self.conf['watched-users'][auth]['chans']:
        self.conf['watched-users'][auth]['chans'].append(chan)
      self.conf['watched-users'][auth]['last'] = best

    else:
      self.conf['watched-users'][auth] = {
              'uid':   user,
              'num':   top_scores,
              'chans': [chan],
              'last':  best
      }
    self.conf.save()
    await self.bot.say(
        formatter.ok(f'you are now linked to: {name}')
    )

  async def check_scores(self):
    while self == self.bot.get_cog('Osu'):
      try:
        for duid in self.conf['watched-users']:
          ouid  = self.conf['watched-users'][duid]['uid']
          num   = self.conf['watched-users'][duid]['num']
          chans = self.conf['watched-users'][duid]['chans']
          last  = self.conf['watched-users'][duid]['last']
          name  = await self.api.get_user(ouid)
          name  = name[0].username
          best  = await self.api.get_user_best(ouid, limit=num)

          for i, old, new in itertools.zip_longest(range(num), last, best):
            if not new:
              break
            elif not old or new.beatmap_id != old[0] or new.score > old[1]:
              em = await self.osu_embed(new)
              em.title = f'New best #{i+1} for {name} - {em.title}'
              for chan_id in chans:
                try:
                  chan = self.bot.get_channel(chan_id)
                  await self.bot.send_message(chan, embed=em)
                except:
                  logger.exception('issue with send %s', str(em.to_dict()))
              break
          else:
            continue
          best = [(i.beatmap_id, i.score) for i in best]
          self.conf['watched-users'][duid]['last'] = best
          self.conf.save()
        await asyncio.sleep(30)
      except:
        #logger.exception("osu cog couldn't connect it seems")
        pass

  async def osu_embed(self, osu_obj):
    em = Embed()

    if type(osu_obj) == osuapi.model.Beatmap:
      length = osu_obj.total_length
      length = f'{length//3600:02}:{length//60:02}:{length%60:02}'
      diff   = f'{osu_obj.difficultyrating:.2}'

      em.title = osu_obj.title
      em.url   = f'https://osu.ppy.sh/b/{osu_obj.beatmap_id}'
      em.add_field(name='Artist',    value=osu_obj.artist)
      em.add_field(name='Creator',   value=osu_obj.creator)
      em.add_field(name='Difficulty',value=diff)
      em.add_field(name='BPM',       value=str(osu_obj.bpm))
      em.add_field(name='Source',    value=osu_obj.source)
      em.add_field(name='Max Combo', value=str(osu_obj.max_combo))
      em.add_field(name='Length',    value=length)

    elif type(osu_obj) == list:
      if len(osu_obj) == 0:
        return None
      diff     = ', '.join([f'{i.difficultyrating:.2}' for i in osu_obj])
      em       = await self.osu_embed(osu_obj[0])
      em.url   = f'https://osu.ppy.sh/s/{osu_obj[0].beatmapset_id}'
      em.set_field_at(2, name='Difficulty', value=diff)
      em.remove_field(3) # remove BPM
      em.remove_field(4) # remove Max Combo
      return em

    elif type(osu_obj) == osuapi.model.User:
      rank  = '#{0.pp_rank} ({0.country} #{0.pp_country_rank})'.format(osu_obj)
      level = int(osu_obj.level)
      nextl = osu_obj.level * 100 % 100

      em.title = osu_obj.username
      em.url   = f'https://osu.ppy.sh/u/{osu_obj.user_id}'
      em.add_field(name='Rank',      value=rank)
      em.add_field(name='Accuracy',  value=f'{osu_obj.accuracy:02.4}%')
      em.add_field(name='Level',     value=f'{level} ({nextl:02.4}%)')
      em.add_field(name='Total PP',  value=str(osu_obj.pp_raw))
      em.add_field(name='Play Count',value=str(osu_obj.playcount))

    elif type(osu_obj) in (osuapi.model.SoloScore, osuapi.model.RecentScore):
      beatmap = await self.api.get_beatmaps(beatmap_id=osu_obj.beatmap_id)
      beatmap = beatmap[0]
      em      = await self.osu_embed(beatmap)
      rank    = osu_obj.rank.replace('X', 'SS')
      if type(osu_obj) == osuapi.model.SoloScore:
        score   = f'{osu_obj.score:,} ({rank} - {osu_obj.pp}pp)'
      else:
        score   = f'{osu_obj.score:,} ({rank})'
      combo   = f'{osu_obj.maxcombo}/{beatmap.max_combo}'

      em.add_field(name='Score',    value=score)
      em.add_field(name='Combo',    value=combo)
      em.remove_field(5) # remove Max Combo

    if not em.thumbnail.url:
      em.set_thumbnail(url=await self.get_thumb_url(osu_obj))
    return em

  async def get_thumb_url(self, osu_obj):
    if type(osu_obj) == osuapi.model.Beatmap:
      if not hasattr(osu_obj, 'beatmapset_id') or not osu_obj.beatmapset_id:
        osu_obj = await self.api.get_beatmaps(beatmap_id=osu_obj.beatmap_id)
        osu_obj = osu_obj[0]
      return f'https://b.ppy.sh/thumb/{osu_obj.beatmapset_id}l.jpg'
    elif type(osu_obj) == osuapi.model.User:
      return f'https://a.ppy.sh/{osu_obj.user_id}?.png'
    return 'http://w.ppy.sh/c/c9/Logo.png'

  def __unload(self):
    self.api.close()
Beispiel #9
0
class NSFW:
    def __init__(self, bot):
        self.bot = bot
        self.loop = bot.loop
        self.conf = Config('configs/nsfw.json')

        if 'update' not in self.conf:
            self.conf['update'] = {
                'safebooru': {
                    'url': 'https://safebooru.donmai.us'
                },
                'lolibooru': {
                    'url': 'https://lolibooru.moe'
                }
            }
            self.conf.save()
        pybooru.resources.SITE_LIST.update(self.conf['update'])

        if 'yandere-conf' not in self.conf:
            self.conf['yandere-conf'] = {}
        if 'danbooru-conf' not in self.conf:
            self.conf['danbooru-conf'] = {}
        if 'safebooru-conf' not in self.conf:
            self.conf['safebooru-conf'] = {}
        if 'lolibooru-conf' not in self.conf:
            self.conf['lolibooru-conf'] = {}

        self.yandere = pybooru.Moebooru('yandere', **self.conf['yandere-conf'])
        self.danbooru = pybooru.Danbooru('danbooru',
                                         **self.conf['danbooru-conf'])
        self.lolibooru = pybooru.Moebooru('lolibooru',
                                          **self.conf['lolibooru-conf'])
        self.safebooru = pybooru.Danbooru('safebooru',
                                          **self.conf['safebooru-conf'])

    @commands.group(pass_context=True)
    async def nsfw(self, ctx):
        """NSFW stuff"""
        channel = ctx.message.channel
        if not channel.is_private and 'nsfw' not in channel.name.lower():
            await self.bot.say(formatter.error('not in nsfw channel'))
            ctx.invoked_subcommand = None
            return

        if ctx.invoked_subcommand is None:
            await self.bot.say(
                formatter.error("Please specify valid subcommand"))
            return

    @nsfw.command(name='danbooru', aliases=['d'])
    async def _danbooru(self, *, search_tags: str = ''):
        """
      searches danbooru for an image

      usage: .nsfw danbooru [num] tags1 tag2, tag_3, etc...
      (optional) num: number of posts to show [1,5]
      if not tags are given, rating:e is assumed
      will potentially return nsfw images
    """
        tags = re.split(',?\\s+', search_tags)
        for i in range(len(tags)):
            if re.search('^(//|#)', tags[i]):
                tags = tags[:i]
                break

        for i in range(len(tags)):
            if re.search('^(/\\*)', tags[i]):
                for j in range(i, len(tags)):
                    if re.search('^(\\*/)', tags[j]):
                        break
                tags = tags[:i] + tags[j + 1:]
                break

        if len(tags) > 1 and re.match('\\d+$', tags[0]):
            num = min(5, max(1, int(tags[0])))
            tags = tags[1:]
        else:
            num = 1

        if not tags:
            tags = ['rating:e']

        tags = ' '.join(tags)
        get = lambda: self.danbooru.post_list(
            limit=num, tags=tags, random=True)
        posts = await self.loop.run_in_executor(None, get)

        if not posts:
            await self.bot.say('could not find anything')
            return

        for post in posts:
            em = Embed()
            em.title = search_tags or 'rating:e'
            em.url = 'https://danbooru.donmai.us/posts/{}'.format(post['id'])
            u = 'https://danbooru.donmai.us'
            if 'large_file_url' in post:
                u += post['large_file_url']
            elif 'file_url' in post:
                u += post['file_url']
            else:
                await self.bot.say('''
                Sorry, there seems to be a premium tag in the image,
                send me $20 if you you want to search it.
        ''')
            em.set_image(url=u)
            if post['tag_string']:
                em.set_footer(text=post['tag_string'])

            await self.bot.say(embed=em)

    @nsfw.command(name='lolibooru', aliases=['l'])
    async def _lolibooru(self, *, search_tags: str = ''):
        """
      searches lolibooru for an image

      usage: .nsfw lolibooru [num] tags1 tag2, tag_3, etc...
      (optional) num: number of posts to show [1,5]
      if not tags are given, rating:e is assumed
      will potentially return nsfw images
    """
        tags = re.split(',?\\s+', search_tags)
        for i in range(len(tags)):
            if re.search('^(//|#)', tags[i]):
                tags = tags[:i]
                break

        for i in range(len(tags)):
            if re.search('^(/\\*)', tags[i]):
                for j in range(i, len(tags)):
                    if re.search('^(\\*/)', tags[j]):
                        break
                tags = tags[:i] + tags[j + 1:]
                break

        if len(tags) > 1 and re.match('\\d+$', tags[0]):
            num = min(5, max(1, int(tags[0])))
            tags = tags[1:]
        else:
            num = 1

        if not tags:
            tags = ['rating:e']

        tags = ' '.join(tags)
        get = lambda: self.lolibooru.post_list(limit=100, tags=tags)
        posts = await self.loop.run_in_executor(None, get)

        if not posts:
            await self.bot.say('could not find anything')
            return

        for i in range(num):
            if not posts:
                break
            post = random.choice(posts)
            posts.remove(post)
            em = Embed()
            em.title = search_tags or 'rating:e'
            em.url = 'https://lolibooru.moe/post/show/{}'.format(post['id'])
            u = post['file_url'].replace(' ', '%20')
            em.set_image(url=u)
            if post['tags']:
                em.set_footer(text=post['tags'])

            await self.bot.say(embed=em)

    @nsfw.command(name='yandere', aliases=['y'])
    async def _yandre(self, *, search_tags: str = '' or 'rating:e'):
        """
      searches yande.re for an image

      usage: .nsfw yandere [num] tags1 tag2, tag_3, etc...
      (optional) num: number of posts to show [1,5]
      if not tags are given, rating:e is assumed
      will potentially return nsfw images
    """
        tags = re.split(',?\\s+', search_tags)
        for i in range(len(tags)):
            if re.search('^(//|#)', tags[i]):
                tags = tags[:i]
                break

        for i in range(len(tags)):
            if re.search('^(/\\*)', tags[i]):
                for j in range(i, len(tags)):
                    if re.search('^(\\*/)', tags[j]):
                        break
                tags = tags[:i] + tags[j + 1:]
                break

        if len(tags) > 1 and re.match('\\d+$', tags[0]):
            num = min(5, max(1, int(tags[0])))
            tags = tags[1:]
        else:
            num = 1

        if not tags:
            tags = ['rating:e']

        tags = ' '.join(tags)
        get = lambda: self.yandere.post_list(limit=100, tags=tags)
        posts = await self.loop.run_in_executor(None, get)

        if not posts:
            await self.bot.say('could not find anything')
            return

        for i in range(num):
            if not posts:
                break
            post = random.choice(posts)
            posts.remove(post)
            em = Embed()
            em.title = search_tags or 'rating:e'
            em.url = 'https://yande.re/post/show/{}'.format(post['id'])
            u = post['file_url']
            em.set_image(url=u)
            if post['tags']:
                em.set_footer(text=post['tags'])

            await self.bot.say(embed=em)

    @commands.command(name='safebooru', aliases=['s', 'safe'])
    async def _safebooru(self, *, search_tags: str):
        """
      searches safebooru for an image

      usage: .safebooru [num] tags1 tag2, tag_3, etc...
      (optional) num: number of posts to show [1,5]
      at least 1 tag must be specified
      will potentially return nsfw images
    """
        tags = re.split(',?\\s+', search_tags)
        for i in range(len(tags)):
            if re.search('^(//|#)', tags[i]):
                tags = tags[:i]
                break

        for i in range(len(tags)):
            if re.search('^(/\\*)', tags[i]):
                for j in range(i, len(tags)):
                    if re.search('^(\\*/)', tags[j]):
                        break
                tags = tags[:i] + tags[j + 1:]
                break

        if len(tags) > 1 and re.match('\\d+$', tags[0]):
            num = min(5, max(1, int(tags[0])))
            tags = tags[1:]
        else:
            num = 1

        tags = ' '.join(tags)
        get = lambda: self.safebooru.post_list(
            limit=num, tags=tags, random=True)
        posts = await self.loop.run_in_executor(None, get)

        if not posts:
            await self.bot.say('could not find anything')
            return

        for post in posts:
            em = Embed()
            em.title = search_tags
            em.url = 'https://safebooru.donmai.us/posts/{}'.format(post['id'])
            u = 'https://safebooru.donmai.us'
            if 'large_file_url' in post:
                u += post['large_file_url']
            elif 'file_url' in post:
                u += post['file_url']
            else:
                await self.bot.say('''
                Sorry, there seems to be a premium tag in the image,
                send me $20 if you you want to search it.
        ''')
            em.set_image(url=u)
            if post['tag_string']:
                em.set_footer(text=post['tag_string'])

            await self.bot.say(embed=em)
Beispiel #10
0
class Server:
    def __init__(self, bot):
        self.bot = bot
        self.conf = Config('configs/server.json')
        self.heap = Config('configs/heap.json')
        self.cut = {}

        for rem in self.conf.pop('end_role', []):
            self.heap['heap'].push(rem)

    @perms.has_perms(manage_messages=True)
    @commands.command(name='prune', pass_context=True)
    async def _prune(self, ctx, num_to_delete: int, *message):
        """
    deletes specified number of messages from channel
    if message is specified, message will be echoed by bot after prune

    USAGE: .prune <num> [user] [message...]

    NOTE: if first word after number is a user,
          only user's messages will be pruned
    """
        # tmp channel/server pointer
        chan = ctx.message.channel
        serv = ctx.message.server

        #if num_to_delete > 100:                       # api only allows up to 100
        #  await self.bot.say('Sorry, only up to 100') # TODO - copy thing done in
        #  return                                      #        self._paste
        if num_to_delete < 1:  # delete nothing?
            await self.bot.say('umm... no')  #  answer: no
            return

        # if the first word in the message matches a user,
        #   remove that word from the message, store the user
        try:
            user = dh.get_user(serv or self.bot, message[0])
            if user:
                message = message[1:]
        except:
            logger.debug('did not match a user')
            user = None

        check = lambda m: True
        if user:  # if a user was matched, delete messages for that user only
            logger.debug(f'pruning for user {user.name}')
            check = lambda m: m.author.id == user.id

        message = ' '.join(message)  #make the message a string

        logs = []
        async for m in self.bot.logs_from(chan, num_to_delete, reverse=True):
            if check(m):
                logs.append(m)

        deleted = len(logs)
        old = False
        while len(logs) > 0:  # while there are messages to delete
            if len(logs) > 1:  #   if more than one left to delete and not old,
                if not old:  #     attempt batch delete [2-100] messages
                    try:
                        await self.bot.delete_messages(logs[:100])
                    except:  #   if problem when batch deleting
                        old = True  #     then the messages must be old
                if old:  # if old, traverse and delete individually
                    for entry in logs[:100]:
                        try:
                            await self.bot.delete_message(entry)
                        except:
                            logger.exception(
                                '<{0.author.name}> {0.content}'.format(entry))
                logs = logs[100:]
            else:  # if only one message, delete individually
                await self.bot.delete_message(logs[0])
                logs.remove(logs[0])

        #report that prume was complete, how many were prunned, and the message
        await self.bot.say(
            ok('Deleted {} message{} {}'.format(
                deleted, '' if deleted == 1 else 's',
                f'({message})' if message else '')))

    @commands.group(name='role',
                    aliases=['give', 'giveme', 'gimme'],
                    pass_context=True)
    async def _role(self, ctx):
        """
    Manage publicly available roles
    """
        # if no sub commands were called, guess at what the user wanted to do
        if ctx.invoked_subcommand is None:
            msg = ctx.message.content.split()  # attempt to parse args
            if len(msg) < 2:
                await self.bot.say('see help (`.help role`)')
                return
            role = msg[1]
            date = ' '.join(msg[2:])

            # if the user cannot manage roles, then they must be requesting a role
            #   or they are trying to do something that they are not allowed to
            if not perms.check_permissions(ctx.message, manage_roles=True):
                await self._request_wrap(ctx, role,
                                         date)  # attempt to request role
                return

            #if the user does have permission to manage, they must be an admin/mod
            #  ask them what they want to do - since they clearly did not know what
            #  they were trying to do
            await self.bot.say('Are you trying to [a]dd a new role ' + \
                               'or are you [r]equesting this role for yourself?'
            )
            try:  # wait for them to reply
                msg = await self.bot.wait_for_message(
                    30, author=ctx.message.author, channel=ctx.message.channel)
            except:  # if they do not reply, give them a helpful reply
                #   without commenting on their IQ
                await self.bot.say(
                    error('Response timeout, maybe look at the help?'))
                return
            # if a reply was recived, check what they wanted to do and pass along
            msg = msg.content.lower()
            if msg.startswith('a') or 'add' in msg:  # adding new role to list
                await self._add_wrap(ctx, role)
                reply = f"Please run `.role request {role}` to get the \"{role}\" role"
                await self.bot.say(reply)
            elif msg.startswith(
                    'r') or 'request' in msg:  # requesting existing role
                await self._request_wrap(ctx, role, date)
            else:  # they can't read
                await self.bot.say(error('I have no idea what you are attempting' + \
                                         ' to do, maybe look at the help?')
                )

    @_role.command(name='add', aliases=['create', 'a'], pass_context=True)
    @perms.has_perms(manage_roles=True)
    async def _add(self, ctx, role: str):
        """
    adds role to list of public roles
    """
        await self._add_wrap(ctx, role)

    @_role.command(name='list', aliases=['ls', 'l'], pass_context=True)
    async def _list(self, ctx):
        """
    lists public roles avalible in the server
    """

        # pull roles out of the config file
        serv = ctx.message.server
        names = []
        m_len = 0
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])

        # if no roles, say so
        if not available_roles:
            await self.bot.say('no public roles in this server\n' + \
                               ' see `.help role create` and `.help role add`'
            )
            return

        # For each id in list
        #   find matching role in server
        #   if role exists, add it to the role list
        # Note: this block also finds the strlen of the longest role name,
        #       this will be used later for formatting
        for role_id in available_roles:
            role = discord.utils.find(lambda r: r.id == role_id, serv.roles)
            if role:
                names.append(role.name)
                m_len = max(m_len, len(role.name))

        # create a message with each role name and id on a seperate line
        # seperators(role - id) should align due to spacing - this is what the
        #   lenght of the longest role name is used for
        msg = 'Roles:\n```'
        line = '{{:{}}} - {{}}\n'.format(m_len)
        for name, rid in zip(names, available_roles):
            msg += line.format(name, rid)

        # send message with role list
        await self.bot.say(msg + '```')

    @_role.command(name='remove', aliases=['rm'], pass_context=True)
    @perms.has_perms(manage_roles=True)
    async def _delete(self, ctx, role: str):
        """
    removes role from list of public roles
    """

        # attempt to find specified role and get list of roles in server
        serv = ctx.message.server
        role = dh.get_role(serv, role)
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])

        # if user failed to specify role, complain
        if not role:
            await self.bot.say('Please specify a valid role')
            return

        if serv.id not in self.conf:
            self.conf[serv.id] = {'pub_roles': []}
            self.conf.save()
        elif 'pub_roles' not in self.conf[serv.id]:
            self.conf[serv.id]['pub_roles'] = []
            self.conf.save()

        if role.id in available_roles:  # if role is found, remove and report
            self.conf[serv.id]['pub_roles'].remove(role.id)
            self.conf.save()
            await self.bot.say(ok('role removed from public list'))
        else:  # if role is not in server, just report
            await self.bot.say(error('role is not in the list'))

    @_role.command(name='request', aliases=['r'], pass_context=True)
    async def _request(self, ctx, role: str, date: str = ''):
        """
    adds role to requester(if in list)
    """
        await self._request_wrap(ctx, role, date)

    @_role.command(name='unrequest', aliases=['rmr', 'u'], pass_context=True)
    async def _unrequest(self, ctx, role: str):
        """removes role from requester(if in list)"""

        # attempt to find role that user specied for removal
        auth = ctx.message.author
        serv = ctx.message.server
        role = dh.get_role(serv, role)

        # if user failed to find specify role, complain
        if not role:
            await self.bot.say('Please specify a valid role')
            return

        # get a list of roles that are listed as public and the user roles
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])
        user_roles = discord.utils.find(lambda r: r.id == role.id, auth.roles)

        # ONLY remove roles if they are in the public roles list
        # Unless there is no list,
        #   in which case any of the user's roles can be removed
        if role.id in available_roles or user_roles:
            await self.bot.remove_roles(auth, role)
            await self.bot.say(ok('you no longer have that role'))
        else:
            await self.bot.say(
                error('I\'m afraid that I can\'t remove that role'))

    # wrapper function for adding roles to public list
    async def _add_wrap(self, ctx, role):
        serv = ctx.message.server

        # find the role,
        # if it is not found, create a new role
        role_str = role
        if type(role) != discord.Role:
            role = dh.get_role(serv, role_str)
        if not role:
            role = await self.bot.create_role(serv,
                                              name=role_str,
                                              mentionable=True)
            await self.bot.say(ok(f'New role created: {role_str}'))

        # if still no role, report and stop
        if not role:
            await self.bot.say(error("could not find or create role role"))
            return

        # The @everyone role (also @here iiuc) cannot be given/taken
        if role.is_everyone:
            await self.bot.say(error('umm... no'))
            return

        if serv.id not in self.conf:  # if server does not have a list yet create it
            self.conf[serv.id] = {'pub_roles': [role.id]}
        elif 'pub_roles' not in self.conf[serv.id]:  # if list is corruptted
            self.conf[serv.id]['pub_roles'] = [role.id]  # fix it
        elif role.id in self.conf[
                serv.id]['pub_roles']:  # if role is already there
            await self.bot.say('role already in list')  #   report and stop
            return
        else:  # otherwise add it to the list and end
            self.conf[serv.id]['pub_roles'].append(role.id)

        # save any changes to config file, and report success
        self.conf.save()
        await self.bot.say(ok('role added to public role list'))

    # wrapper function for getting roles that are on the list
    async def _request_wrap(self, ctx, role, date=''):
        auth = ctx.message.author
        chan = ctx.message.channel
        serv = ctx.message.server

        # attempt to find the role if a string was given,
        #   if not found, stop
        if type(role) != discord.Role:
            role = dh.get_role(serv, role)
        if not role:
            await self.bot.say(
                error("could not find role, ask a mod to create it"))
            return

        # get list of public roles
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])

        if role.id in available_roles:  # if role is a public role,
            await self.bot.add_roles(auth, role)  #   give it
            await self.bot.say(ok('you now have that role'))
        else:  # otherwise don't
            await self.bot.say(
                error('I\'m afraid that I can\'t give you that role'))
            return

        if date:  # if a timeout was specified
            end_time = dh.get_end_time(date)[0]
            role_end = RoleRemove(end_time, role.id, auth.id, chan.id, serv.id)

            self.heap['heap'].push(role_end)
            self.heap.save()
            await role_end.begin(self.bot)

    @perms.has_perms(manage_messages=True)
    @commands.command(name='cut', pass_context=True)
    async def _cut(self, ctx, num_to_cut: int, num_to_skip: int = 0):
        '''
    cuts num_to_cut messages from the current channel
    skips over num_to_skip messages (skips none if not specified)

    example:
    User1: first message
    User2: other message
    User3: final message
    Using ".cut 1 1" will cut User2's message
    Using ".cut 1" will cut User3's message

    messages will not be deleted until paste
    needs manage_messages perm in the current channel to use
    see .paste
    '''
        #if num_to_cut > 100:
        #  await self.bot.say('Sorry, only up to 100')
        #  return
        if num_to_cut < 1:  # can't cut no messages
            await self.bot.say('umm... no')
            return

        # store info in easier to access variables
        aid = ctx.message.author.id
        chan = ctx.message.channel
        cid = chan.id
        bef = ctx.message.timestamp

        # delete the original `.cut` message(not part of cutting)
        # also sorta serves as confirmation that messages have been cut
        await self.bot.delete_message(ctx.message)

        # if messages should be skipped when cutting
        # save the timestamp of the oldest message
        if num_to_skip > 0:
            async for m in self.bot.logs_from(chan, num_to_skip, reverse=True):
                bef = m.timestamp
                break

        # save the logs to a list
        logs = []
        async for m in self.bot.logs_from(chan,
                                          num_to_cut,
                                          before=bef,
                                          reverse=True):
            logs.append(m)

        #store true in position 0 of list if channel is a nsfw channel
        logs.insert(0, 'nsfw' in chan.name.lower())

        # save logs to dict (associate with user)
        self.cut[aid] = logs

    @perms.has_perms(manage_messages=True)
    @commands.command(name='paste', pass_context=True)
    async def _paste(self, ctx):
        '''
    paste cutted messages to current channel

    needs manage_messages perm in the current channel to use
    deletes original messages
    see .cut
    '''
        # get messages that were cut and other variables
        aid = ctx.message.author.id
        chan = ctx.message.channel
        logs = self.cut.pop(aid, [])

        # if nothing was cut, stop
        if not logs:
            await self.bot.say('You have not cut anything')
            return

        # it messages were cut in a nsfw channel,
        #   do not paste unless this is a nsfw channel
        # NOTE: cutting/pasting to/from PMs is not possible(for now)
        if logs[0] and 'nsfw' not in chan.name.lower():
            await self.bot.say('That which hath been cut in nsfw, ' + \
                               'mustn\'t be pasted in such a place'
            )
            return

        # remove the nsfw indicator(since it's not really part of the logs)
        logs = logs[1:]

        # delete the `.paste` message
        await self.bot.delete_message(ctx.message)

        # compress the messages - many messages can be squished into 1 big message
        # but ensure that output messages do not exceede the discord message limit
        buf = ''  # current out message that is being compressed to
        out = []  # output messages that have been compressed
        for message in logs:
            # save messages as:
            #   <nick> message
            # and attachments as(after the message):
            #   filename: url_to_attachment
            if message.content or message.attachments:
                tmp = '<{0.author.name}> {0.content}\n'.format(message)
                for a in message.attachments:
                    tmp += '{filename}: {url}\n'.format(**a)
            else:
                tmp = ''
            # if this message would make the current output buffer too long,
            #   append it to the output message list and reset the buffer
            # or just append to the buffer if it's not going to be too long
            if len(buf) + len(tmp) > 1900:
                out.append(buf)
                buf = tmp
            else:
                buf += tmp

            # if the message is composed of *only* embeds,
            #   flush buffer,
            #   and append embed to output list
            if message.embeds and not message.content:
                if buf:
                    out.append(buf)
                    buf = ''
                for embed in message.embeds:
                    out.append(embed)

        # if there is still content in the buffer after messages have been traversed
        #   treat buffer as complete message
        if buf:
            out.append(buf)

        # send each message in output list
        for mes in out:
            if type(mes) == str:
                if mes:
                    await self.bot.say(mes)
            else:  # if it's an embed, n
                await self.bot.say(embed=EmWrap(mes)
                                   )  #   it needs to be wrapped

        # once all messages have been pasted, delete(since cut) the old ones

        old = False  # messages older than 2 weeks cannot be batch deleted

        while len(logs) > 0:  # while there are messages to delete
            if len(logs) > 1:  #   if more than one left to delete and not old,
                if not old:  #     attempt batch delete [2-100] messages
                    try:
                        await self.bot.delete_messages(logs[:100])
                    except:  #   if problem when batch deleting
                        old = True  #     then the messages must be old
                if old:  # if old, traverse and delete individually
                    for entry in logs[:100]:
                        await self.bot.delete_message(entry)
                logs = logs[100:]
            else:  # if only one message, delete individually
                await self.bot.delete_message(logs[0])
                logs.remove(logs[0])

        # remove cut entry from dict of cuts
        if aid in self.cut:
            del self.cut[aid]

    @commands.command(name='topic', pass_context=True)
    async def _topic(self, ctx, *, new_topic=''):
        """manage topic

    if a new_topic is specified, changes the topic
    otherwise, displays the current topic
    """
        # store channel in tmp pointer
        c = ctx.message.channel

        if new_topic:
            # if a topic was passed,
            #   change it if user has the permisssions to do so
            #   or tell user that they can't do that
            if perms.check_permissions(ctx.message, manage_channels=True):
                await self.bot.edit_channel(c, topic=new_topic)
                await self.bot.say(
                    ok('Topic for #{} has been changed'.format(c.name)))
            else:
                await self.bot.say(
                    error('You cannot change the topic for #{}'.format(c.name))
                )
        elif c.topic:
            # if no topic has been passed,
            #   say the topic
            await self.bot.say('Topic for #{}: `{}`'.format(c.name, c.topic))
        else:
            # if not topic in channel,
            #   say so
            await self.bot.say('#{} has no topic'.format(c.name))

    @perms.has_perms(manage_roles=True)
    @commands.command(name='timeout_send', aliases=['ts'], pass_context=True)
    async def _timeout_send(self,
                            ctx,
                            member: discord.Member,
                            time: float = 300):
        """puts a member in timeout for a duration(default 5 min)

    usage `.timeout [add] @member [time in seconds]`
    """
        if not perms.is_owner() and \
          ctx.message.author.server_permissions < member.server_permissions:
            await self.bot.say('Can\'t send higher ranking members to timeout')
            return

        server = ctx.message.server
        channel = ctx.message.channel

        if perms.in_group('timeout') and not perms.is_owner():
            await self.bot.say('You\'re in timeout... No.')
            return

        if not ctx.message.server:
            await self.bot.say('not in a server at the moment')
            return

        if time < 10:
            await self.bot.say('And what would the point of that be?')
            return

        if time > 10000:
            await self.bot.say('Too long, at this point consider banning them')
            return

        criteria = lambda m: re.search('(?i)^time?[ _-]?out.*', m.name)

        to_role = discord.utils.find(criteria, server.roles)
        to_chan = discord.utils.find(criteria, server.channels)

        try:
            timeout_obj = Timeout(channel, server, member, time)
            self.heap['heap'].push(timeout_obj)
            self.heap.save()
            await timeout_obj.begin(self.bot, to_role, to_chan)
        except:
            for index, obj in enumerate(self.heap['heap']):
                if obj == timeout_obj:
                    self.heap['heap'].pop(index)
                    break
            await self.bot.say(
                'There was an error sending {}\'s to timeout \n({}{}\n)'.
                format(
                    member.name,
                    '\n  - do I have permission to manage roles(and possibly channels)?',
                    '\n  - is my highest role above {}\'s highest role?'.
                    format(member.name)))
            #raise

    @perms.has_perms(manage_roles=True)
    @commands.command(name='timeout_end', aliases=['te'], pass_context=True)
    async def _timeout_end(self, ctx, member: discord.Member):
        """removes a member from timeout

    usage `.timeout end @member`
    """
        server = ctx.message.server
        channel = ctx.message.channel

        if perms.in_group('timeout') and not perms.is_owner():
            await self.bot.say('You\'re in timeout... No.')
            return

        if not ctx.message.server:
            await self.bot.say('not in a server at the moment')
            return

        # test timeout object for comparison
        test = namedtuple({'server_id': server.id, 'user_id': member.id})
        index = 0  # inext is used to more efficently pop from heap

        # error message in case ending timeout fails
        error_msg = 'There was an error ending {}\'s timeout \n({}{}\n)'.format(
            member.name,
            '\n  - do I have permission to manage roles(and possibly channels)?',
            '\n  - is my highest role above {}\'s highest role?'.format(
                member.name))

        for timeout in Timeout.conf['timeouts']:  # look trhough all timouts
            if timeout == test:  #   if found
                try:
                    await timeout.end(self.bot, index)  #     attempt to end
                except:
                    await self.bot.say(error_msg
                                       )  #     if error when ending, report
                return
            index += 1  #   not found increment index
        else:  # not found at all, report
            await self.bot.say('{} is not in timeout...'.format(member.name))
            return

    # checks timeouts and restores perms when timout expires
    async def check_timeouts(self):
        if 'timeouts' not in Timeout.conf:  #create timeouts list if needed
            Timeout.conf['timeouts'] = []

        while self == self.bot.get_cog('Server'):  # in case of cog reload
            # while timeouts exist, and the next one's time has come,
            #   end it
            while Timeout.conf['timeouts'] and \
                  Timeout.conf['timeouts'][0].time_left < 1:
                await Timeout.conf['timeouts'][0].end(self.bot, 0)

            # wait a bit and check again
            #   if the next one ends in < 15s, wait that much instead of 15s
            if Timeout.conf['timeouts']:
                delay = min(Timeout.conf['timeouts'].time_left, 15)
            else:
                delay = 15
            await asyncio.sleep(delay + 0.5)
Beispiel #11
0
class Music:
  def __init__(self, bot):
    self.bot = bot
    self.voice_states = {}
    self.conn = emby_helper.conn
    self.conf = Config('configs/music.json')
    if 'volume' not in self.conf:
      self.conf['volume'] = {}

    self.bot.loop.create_task(self.update_db())

  async def update_db(self):
    while self == self.bot.get_cog('Music'):
      for item in ('playlists', 'songs', 'albums', 'artists'):
        try:
          await getattr(self.conn, item+'_force')
        except:
          pass
      await asyncio.sleep(120)

  @commands.group(pass_context=True, aliases=['m'])
  async def music(self, ctx):
    """Manage music player stuff"""
    if ctx.invoked_subcommand is None:
      await self.bot.say(error("Please specify valid subcommand"))

  @music.command(pass_context=True, aliases=['u', 'reload'])
  async def update(self, ctx):
    '''reload database from emby'''
    for item in ('playlists', 'songs', 'albums', 'artists'):
      await getattr(self.conn, item+'_force')

    await self.bot.say(ok('database reloaded '))

  def get_voice_state(self, server):
    state = self.voice_states.get(server.id)
    if state is None:
      state = VoiceState(self.bot, self, server.id)
      self.voice_states[server.id] = state

    return state

  async def create_voice_client(self, channel):
    voice = await self.bot.join_voice_channel(channel)
    state = self.get_voice_state(channel.server)
    state.vchan = voice

  def __unload(self):
    for state in self.voice_states.values():
      try:
        state.audio_player.cancel()
        if state.vchan:
          self.bot.loop.create_task(state.vchan.disconnect())
      except:
        pass

  #TODO discord.TextChannel
  @music.command(pass_context=True, aliases=['j'], no_pm=True)
  async def join(self, ctx, *, channel : discord.Channel):
    """Joins a voice channel."""
    try:
      await self.create_voice_client(channel)
    except discord.ClientException:
      await self.bot.say('Already in a voice channel...')
    except discord.InvalidArgument:
      await self.bot.say('This is not a voice channel...')
    else:
      await self.bot.say('Ready to play audio in ' + channel.name)

  @music.command(pass_context=True, aliases=['su'], no_pm=True)
  async def summon(self, ctx):
    """Summons the bot to join your voice channel."""
    summoned_channel = ctx.message.author.voice_channel
    if summoned_channel is None:
      await self.bot.say('You are not in a voice channel.')
      return False

    state = self.get_voice_state(ctx.message.server)
    if state.vchan is None:
      state.vchan = await self.bot.join_voice_channel(summoned_channel)
    else:
      await state.vchan.move_to(summoned_channel)

    return True

  @music.command(pass_context=True, aliases=['s', 'sr', 'find'], no_pm=False)
  async def search(self, ctx, *, search : str):
    """Searchs song on emby

    usage: .search [-a] [<number>] <search terms...>

    flags:
      -a searches albums before songs (playlists still first)

      search terms:
        - search terms are space seperated and case insensitive
        - if a term is an itemid, that item will be included
        - will search playlists, songs, albums, and album artists FOR:
          - name/title
          - filepath
          - description
          - artist/album artist names (for songs)
        NOTE: if none are specified - all songs on emby will be considered
    """
    search = search.split(' ')
    albm   = False

    logger.debug('search - parsing options')
    while search:
      if search[0] == '-':
        search = search[1:]
      elif search[0][0] == '-':
        for flag in search[0][1:]:
          if flag == 'a':
            search = search[1:]
            albm = True
          else:
            break
        else:
          continue
        break
      else:
        break

    items = await self._search(search, albm)

    if not items:
      await self.bot.send_message(ctx.message.channel, error('nothing found'))
      return

    em = await emby_helper.makeEmbed(items[0])
    await self.bot.send_message(ctx.message.channel, embed=em)

  @music.command(pass_context=True, aliases=['p'], no_pm=True)
  async def play(self, ctx, *, search : str):
    """Plays a searched song from emby.

    usage: .play [-rsam] [<number>] <search terms...>

    flags:
      -n will insert the songs next into the queue(not at the end)
      -r and -s will shuffle the songs
      -n will queue next instead of queue last
      -a and -m will enable playing multiple songs
        if no number is specified then all songs will be played
        if  a number is specified then that many song will play
          if at least that many songs were found

      search terms:
        - search terms are space separated and case insensitive
        - if a term is an itemid, that item will be included
        - will search playlists, songs, albums, and album artists FOR:
          - name/title
          - filepath
          - description
          - artist/album artist names (for songs)
        NOTE: if none are specified - all songs on emby will be considered

    If there is a song currently in the queue, then it is
    queued until the next song is done playing.

    This command searches emby for a song
    """
    search = search.split(' ')
    qnext  = False
    mult   = False
    shuf   = False
    albm   = False
    num    = 0

    while search:
      if re.search('^\\d{1,2}$', search[0]):
        mult  = True
        num   = int(search[0])
        search = search[1:]
      elif search[0] == '-':
        search = search[1:]
      elif search[0][0] == '-':
        for flag in search[0][1:]:
          if flag in 'am':
            if flag == 'a':
              albm = True
            search = search[1:]
            mult   = True
          elif flag in 'rs':
            search = search[1:]
            shuf   = True
          elif flag in 'n':
            search = search[1:]
            qnext  = True
          else:
            break
        else:
          continue
        break
      else:
        break
    state = self.get_voice_state(ctx.message.server)

    if state.vchan is None:
      success = await ctx.invoke(self.summon)
      if not success:
        await self.bot.say(error('error joining channel'))
        return

    items = await self._search(search, albm)

    if not items:
      await self.bot.say(error('could not find song'))
      return

    if hasattr(items[0], 'songs') and await items[0].songs:
      display_item = items[0]
      items        = await items[0].songs
    else:
      display_item = self.conn

    items = [s for s in items if s.type == 'Audio']

    if not items:
      await self.bot.say(error('could not find song'))
      return

    if shuf:
      random.shuffle(items)

    if mult:
      if num > 0:
        items = items[:num]
      ignore = 'Songs,Artists' if display_item is self.conn else 'Songs'
      em = await emby_helper.makeEmbed(display_item, 'Queued: ', ignore)

      songs_str = ''
      for i in items:
        if hasattr(i, 'index_number'):
          songs_str += f'{i.index_number:02} - {i.name}\n'
        else:
          songs_str += f'{i.name}\n'
        await self._play_emby(ctx, state, i, display=False, qnext=qnext)
      if qnext:
        songs_str = songs_str.split('\n')
        songs_str = '\n'.join(songs_str[::-1])
      if len(songs_str) >= 1024:
        songs_str = songs_str[:1020]+'\n...'
      em.add_field(name='Items', value=songs_str)
      await self.bot.say(embed=em)
    else:
      await self._play_emby(ctx, state, random.choice(items), qnext=qnext)

  async def _play_emby(self, ctx, state, item, display=True, qnext=False):
    entry = VoiceEntry(ctx.message, item=item)
    if display:
      em = await emby_helper.makeEmbed(item, 'Queued: ', 'Songs')
      await self.bot.say(embed=em)
    if qnext:
      state.songs._queue.appendleft(entry)
      state.songs._unfinished_tasks += 1
      state.songs._finished.clear()
      state.songs._wakeup_next(state.songs._getters)
    else:
      await state.songs.put(entry)

  @music.command(pass_context=True, aliases=['t'], no_pm=True)
  async def tag(self, ctx, *tags):
    '''
    Tag the currently playing song

    currently avalible: instrumental drama comment
    eg. .music tag i
    '''
    state = self.get_voice_state(ctx.message.server)

    if not state.is_playing():
      await self.bot.say(error("Not playing anything, can't tag"))
      return
    if not state.current.item:
      await self.bot.say(error("Not an emby item, can't tag"))
      return

    item   = state.current.item
    path   = item.path
    muten  = mutagen.File(item.path)
    genres = muten.get('genre', [])
    bpost  = False
    bname  = False

    for i,t in enumerate(tags):
      t = t.lower()
      if t in ('i', 'instrumental'):
        if 'instrumental' not in path:
          path  = path.rpartition('.')
          path  = f'{path[0]} -instrumental-.{path[2]}'
          bname = True
      elif t in ('d', 'drama'):
        if 'drama' not in ' '.join(genres).lower():
          genres.append('Drama')
          muten['genre'] = '; '.join(genres)
          item.genres    = genres
          bpost = True
      elif t in ('c', 'comment'):
        comment = ' '.join(tags[(i+1):])
        item.overview = comment
        if type(muten) == ID3:
          comment = COMM(encoding=3, lang=u'eng', desc='desc', text=comment)
        muten['comment'] = comment
        bpost = True
        break

    if bpost:
      await item.post()
      muten.save()
    if bname:
      os.rename(item.path, path)
    await self.bot.say(ok('tags set'))

  @music.command(pass_context=True, aliases=['shuff', 'sh'], no_pm=True)
  async def shuffle(self, ctx):
    """Shuffles the queue (excluding the current song)"""

    state = self.get_voice_state(ctx.message.server)
    if state.is_playing():
      random.shuffle(state.songs._queue)
      await self.bot.say(ok('items shuffled'))
    else:
      await self.bot.say(error('nothing seems to be playing'))

  @music.command(pass_context=True, aliases=['v'], no_pm=True)
  async def volume(self, ctx, value : int):
    """Sets the volume of the currently playing song."""

    state = self.get_voice_state(ctx.message.server)
    if state.is_playing():
      player = state.player
      player.volume = value / 100
      if state.current.item:
        if value == 60 and state.current.item.id in self.conf['volume']:
          del self.conf['volume'][state.current.item.id]
        elif value != 60:
          self.conf['volume'][state.current.item.id] = value
        self.conf.save()
      await self.bot.say(f'Set the volume to {player.volume:.0%}')
    else:
      await self.bot.say(error('Nothing seems to be playing'))

  @music.command(pass_context=True, no_pm=True)
  async def pause(self, ctx):
    """Pauses the currently played song."""
    state = self.get_voice_state(ctx.message.server)
    if state.is_playing():
      player = state.player
      player.pause()

  @music.command(pass_context=True, aliases=['q'], no_pm=True)
  async def queue(self, ctx):
    """Checks the song queue, up to 30."""
    state = self.get_voice_state(ctx.message.server)

    if not state.is_playing():
      await self.bot.say("It seems as though nothing is playing")
      return

    songs = state.songs._queue
    em = await emby_helper.makeEmbed(self.conn, 'Queued: ', 'Songs,Artists')
    index = [i for i,f in enumerate(em.fields) if f.name == 'Songs']
    if index:
      em.remove_field(index[0])
    songs_str = ''
    for index,song in zip(range(1,31),songs):
      item = getattr(song, 'item', None)
      if item:
        songs_str += f'{index:02} - {item.name}\n'
      else:
        songs_str += f'{index:02} - {song}\n'
    em.add_field(name='Items', value=songs_str)
    await self.bot.say(embed=em)

  @music.command(pass_context=True, no_pm=True)
  async def resume(self, ctx):
    """Resumes the currently played song."""
    state = self.get_voice_state(ctx.message.server)
    if state.is_playing():
      player = state.player
      player.resume()

  @music.command(pass_context=True, aliases=['st'], no_pm=True)
  async def stop(self, ctx):
    """Stops playing audio and leaves the voice channel.

    This also clears the queue.
    """
    server = ctx.message.server
    state  = self.get_voice_state(server)

    await state.stop()

  @music.command(pass_context=True, aliases=['sk'], no_pm=True)
  async def skip(self, ctx):
    """Vote to skip a song. The song requester can automatically skip.

    3 skip votes are needed for the song to be skipped.
    """

    state = self.get_voice_state(ctx.message.server)
    if not state.is_playing():
      await self.bot.say('Not playing any music right now...')
      return

    voter = ctx.message.author
    if voter == state.current.requester:
      await self.bot.say('Requester requested skipping song...')
      state.skip()
    elif voter.id not in (m.id for m in state.vchan.channel.voice_members):
      await self.bot.say(error("You're not even in the voice channel. No."))
    elif voter.id not in state.skip_votes:
      state.skip_votes.add(voter.id)
      total_votes = len(state.skip_votes)
      needed      = (len(state.vchan.channel.voice_members)-1)/2
      logger.debug('needed = (len-1)/2 = (%d-1)/2 = %d', len(state.vchan.channel.voice_members), needed)
      if total_votes >= needed:
        await self.bot.say('Skip vote passed, skipping song...')
        state.skip()
      else:
        await self.bot.say(f'Skip vote added, currently at [{total_votes}/{needed}]')
    else:
      await self.bot.say('You have already voted to skip this song.')

  @music.command(pass_context=True, aliases=['np'], no_pm=True)
  async def playing(self, ctx):
    """Shows info about the currently played song."""

    state = self.get_voice_state(ctx.message.server)
    if state.current is None:
      await self.bot.say('Not playing anything.')
    else:
      skip_count = len(state.skip_votes)
      if state.current.item:
        em = await emby_helper.makeEmbed(state.current.item, 'Now playing: ')
        em.add_field(name="**Skip Count**", value=str(skip_count))
        await self.bot.say(embed=em)
      else:
        string = str(state.current)
        await self.bot.say(f'Now playing {string} [skips: {skip_count}/3]')

  @music.group(pass_context=True, name='playlist', aliases=['list'], no_pm=True)
  async def _playlist(self, ctx):
    '''Manage emby playlists'''
    if ctx.invoked_subcommand is None:
      await self.bot.say(error("Please specify valid subcommand"))

  @_playlist.command(pass_context=True, name='add',
                     aliases=['n', 'new', 'add_songs', 'a'], no_pm=True)
  async def _playlist_new(self, ctx, options : str):
    '''
    create a new playlist - or adds songs to existing playlist

    Usage: .music playlist add <name of playlist>
            <song1 search criteria>
            <song2 search criteria>
            ...

    Creates a playlist with the name specifed if it does not exist,
      if more lines are provided, the first song matching the creteria
      provided by that line will be added to the playlist
      (creteria works like `.music play <search>`)
    '''
    options = options.split('\n')
    items   = []

    plsts = await self.conn.playlists
    songs = await self.conn.songs
    albms = await self.conn.albums
    artts = await self.conn.artists

    run      = lambda: search_f(options[0].split(), *plsts)
    found    = await self.bot.loop.run_in_executor(None, run)
    playlist = found[0] if found else None

    for search in options[1:]:
      run   = lambda: search_f(search.split(), *songs, *albms, *artts)
      found = await self.bot.loop.run_in_executor(None, run)
      if found:
        items.append(found[0])

    if playlist:
      await remove_items.add_items(*items)
      await self.bot.say(ok('Songs added'))
    else:
      await self.conn.create_playlist(name, *items)
      await self.bot.say(ok('Playlist created'))

  @_playlist.command(pass_context=True, name='remove_songs',
                     aliases=['rm', 'rs', 'rm_songs', 'r'], no_pm=True)
  async def _playlist_rm_songs(self, ctx, options : str):
    '''
    remove songs from an existing playlist

    Usage: .music playlist remove_songs <name of playlist>
            <song1 search criteria>
            <song2 search criteria>
            ...

    First line is the search criteria for the playlist, following lines are
      per song search criteria. If a song is not in the playlist or the search
      does not match any song - that line will be ignored
    '''
    options = options.split('\n')
    items   = []

    plsts = await self.conn.playlists

    run      = lambda: search_f(options[0].split(), *plsts)
    found    = await self.bot.loop.run_in_executor(None, run)
    playlist = found[0] if found else None

    if not playlist:
      await self.bot.say(error('could not find playlist'))
      return

    songs = await self.conn.songs
    albms = await self.conn.albums
    artts = await self.conn.artists

    for search in options[1:]:
      run   = lambda: search_f(search.split(), *playlist.items)
      found = await self.bot.loop.run_in_executor(None, run)
      if found:
        items.append(found[0])

    await playlist.remove_items(*items)
    await self.bot.say(ok('Songs removed'))

  @_playlist.command(pass_context=True, name='list', aliases=['ls', 'l'])
  async def _playlist_list(self, ctx, name = ''):
    '''
    list songs in specified playlist

    if no playlist is specified, list all playlists
    '''
    run = lambda: self.conn.playlists
    playlists = await self.bot.loop.run_in_executor(None, run)

    if not name:
      names = ''
      for playlist in playlists:
        names += f'{playlist.id} - {playlist.name}\n'
      await self.bot.say(code(names))
      return

    run   = lambda: search_f(name.split(), *playlists)
    found = await self.bot.loop.run_in_executor(None, run)

    if not found:
      await self.bot.say(error("Could not find playlist"))
      return

    playlist = found[0]
    songs = await playlist.songs
    song_info = ''
    for song in songs:
      song_info += f'{song.id} - {song.name} ({song.album_artist_name})\n'
    await self.bot.say(code(song_info))
    return

  async def _search(self, search, albm=False):
    logger.debug('search - getting db')
    plsts = await self.conn.playlists
    songs = await self.conn.songs
    albms = await self.conn.albums
    artts = await self.conn.artists

    if albm:
      logger.debug('search - with album')
      return await self.bot.loop.run_in_executor(None, search_f, search,
                                                 *plsts, *albms, *artts, *songs
      )
    logger.debug('search - no album')
    return await self.bot.loop.run_in_executor(None, search_f, search,
                                               *plsts, *songs, *albms, *artts
    )
Beispiel #12
0
class General:
    def __init__(self, bot):
        self.bot = bot
        self.loop = bot.loop
        self.stopwatches = {}
        self.conf = Config('configs/general.json')
        self.heap = Config('configs/heap.json')

        if 'responses' not in self.conf:
            self.conf['responses'] = {}
        if 'todo' not in self.conf:
            self.conf['todo'] = {}
        if 'situations' not in self.conf:
            self.conf['situations'] = []
        if '8-ball' not in self.conf:
            self.conf['8-ball'] = []
        for rem in self.conf.pop('reminders', []):
            self.heap['heap'].push(rem)
        self.heap.save()
        self.conf.save()

    @commands.command(hidden=True)
    async def ping(self):
        """Pong."""
        await self.bot.say("Pong.")

    @commands.command(pass_context=True)
    async def time(self, ctx):
        '''remind people to hurry up'''
        await self.bot.send_message(ctx.message.channel,
                                    datetime.now().isoformat())

    @commands.command(pass_context=True)
    async def invite(self, ctx):
        '''reply with a link that allows this bot to be invited'''
        await self.bot.send_message(
            ctx.message.channel,
            'https://discordapp.com/oauth2/authorize?client_id={self.bot.id}' +
            '&permissions=305260592&scope=bot')

    async def tally(self, message):
        chan = message.channel
        user = message.author
        mess = message.content
        loop = asyncio.get_event_loop()

        #bots don't get a vote
        if user.bot:
            return

        if len(mess.strip()) < 2 or \
            mess.strip()[0] in self.bot.command_prefix + ['$','?','!']:
            return

        test_poll = Poll('', [], chan, 0, 1)

        for poll in self.heap['heap']:
            if test_poll == poll:
                await loop.run_in_executor(None, poll.vote, user, mess)

    async def respond(self, message):
        if message.author.bot:
            return

        if len(message.content.strip()) < 2 or \
            message.content.strip()[0] in self.bot.command_prefix + ['$','?','!']:
            return

        loop = asyncio.get_event_loop()

        for i in self.conf['responses']:
            if re.search("(?i){}".format(i[0]), message.content):
                rep = i[1]
                subs = {
                    "\\{un\\}": message.author.name,
                    "\\{um\\}": message.author.mention,
                    "\\{ui\\}": message.author.mention,
                    "\\{situations\\}": random.choice(self.conf['situations'])
                }
                for j in re.findall("\\(.*\\|.*\\)", rep):
                    rep = rep.replace(j, random.choice(j[1:-1].split("|")))
                for j in subs:
                    rep = await loop.run_in_executor(None, re.sub, j, subs[j],
                                                     rep)
                msg = re.sub("(?i){}".format(i[0]), rep, message.content)
                if rep:
                    await self.bot.send_message(message.channel, msg)
                return

    @commands.command(name='roll', aliases=['r', 'clench'], pass_context=True)
    async def _roll(self, ctx, *dice):
        """rolls dice given pattern [Nd]S[(+|-)C]

    N: number of dice to roll
    S: side on the dice
    C: constant to add or subtract from each die roll
    """
        loop = asyncio.get_event_loop()

        roll = '\n'.join(await loop.run_in_executor(None, self.rolls, dice))
        message = ctx.message.author.mention + ':\n'
        if '\n' in roll:
            message += code(roll)
        else:
            message += inline(roll)
        await self.bot.say(message)

    @commands.command(name="8ball", aliases=["8"])
    async def _8ball(self, *, question: str):
        """Ask 8 ball a question

    Question must end with a question mark.
    """
        if question.endswith("?") and question != "?":
            await self.bot.say("`" + random.choice(self.conf['8-ball']) + "`")
        else:
            await self.bot.say("That doesn't look like a question.")

    @commands.group(aliases=['t', 'td'], pass_context=True)
    async def todo(self, ctx):
        '''
    manages user TODO list
    Note: if no sub-command is specified, TODOs will be listed
    '''
        if ctx.invoked_subcommand is None:
            await self._td_list(ctx)

    @todo.command(name='list', aliases=['l', 'ls'], pass_context=True)
    async def _td_list_wp(self, ctx):
        '''
    prints your complete todo list
    '''
        await self._td_list(ctx)

    @todo.command(name='add', aliases=['a', 'insert', 'i'], pass_context=True)
    async def _td_add(self, ctx, *, task: str):
        '''
    adds a new task to your todo list
    '''
        todos = self.conf['todo'].get(ctx.message.author.id, [])
        todos.append([False, task])
        self.conf['todo'][ctx.message.author.id] = todos
        self.conf.save()
        await self.bot.say(ok())

    @todo.command(name='done',
                  aliases=['d', 'complete', 'c'],
                  pass_context=True)
    async def _td_done(self, ctx, *, index: int):
        '''
    sets/unsets a task as complete
    Note: indicies start at 1
    '''
        todos = self.conf['todo'].get(ctx.message.author.id, [])
        if len(todos) < index or index <= 0:
            await self.bot.say(error('Invalid index'))
        else:
            index -= 1
            todos[index][0] = not todos[index][0]
            self.conf['todo'][ctx.message.author.id] = todos
            self.conf.save()
            await self.bot.say(ok())

    @todo.command(name='remove', aliases=['rem', 'rm', 'r'], pass_context=True)
    async def _td_remove(self, ctx, *, index: int):
        '''
    remove a task from your todo list
    Note: indicies start at 1
    '''
        todos = self.conf['todo'].get(ctx.message.author.id, [])
        if len(todos) < index or index <= 0:
            await self.bot.say(error('Invalid index'))
        else:
            task = todos.pop(index - 1)
            self.conf['todo'][ctx.message.author.id] = todos
            self.conf.save()
            await self.bot.say(ok('Removed task #{}'.format(index)))

    async def _td_list(self, ctx):
        todos = self.conf['todo'].get(ctx.message.author.id, [])
        if not todos:
            await self.bot.send_message(ctx.message.channel, 'No TODOs found.')
        else:
            #TODO - ensure that the outgoing message is not too long
            msg = 'TODO:\n'
            length = len(str(len(todos)))
            done = '{{:0{}}} - ~~{{}}~~\n'.format(length)
            working = '{{:0{}}} - {{}}\n'.format(length)
            for i, todo in enumerate(todos, 1):
                if todo[0]:
                    msg += done.format(i, todo[1])
                else:
                    msg += working.format(i, todo[1])
            await self.bot.send_message(ctx.message.channel, msg)

    @commands.group(aliases=["sw"], pass_context=True)
    async def stopwatch(self, ctx):
        """
    manages user stopwatch
    starts/stops/unpauses (depending on context)
    """
        if ctx.invoked_subcommand is None:
            aid = ctx.message.author.id
            if aid in self.stopwatches and self.stopwatches[aid][0]:
                await self._sw_stop(ctx)
            else:
                await self._sw_start(ctx)

    @stopwatch.command(name='start',
                       aliases=['unpause', 'u', 'resume', 'r'],
                       pass_context=True)
    async def _sw_start_wrap(self, ctx):
        """
    unpauses or creates new stopwatch
    """
        await self._sw_start(ctx)

    async def _sw_start(self, ctx):
        aid = ctx.message.author.id
        tme = ctx.message.timestamp.timestamp()
        if aid in self.stopwatches and self.stopwatches[aid][0]:
            await self.bot.send_message(
                ctx.message.channel, 'You\'ve already started a stopwatch.')
        elif aid in self.stopwatches:
            self.stopwatches[aid][0] = tme
            await self.bot.send_message(ctx.message.channel,
                                        'Stopwatch resumed.')
        else:
            self.stopwatches[aid] = [tme, 0]
            await self.bot.send_message(ctx.message.channel,
                                        'Stopwatch started.')

    @stopwatch.command(name='stop', aliases=['end', 'e'], pass_context=True)
    async def _sw_stop_wrap(self, ctx):
        """
    prints time and deletes timer

    works even if paused
    """
        await self._sw_stop(ctx)

    async def _sw_stop(self, ctx):
        aid = ctx.message.author.id
        now = ctx.message.timestamp.timestamp()
        old = self.stopwatches.pop(aid, None)
        if old:
            if old[0]:
                tme = now - old[0] + old[1]
            else:
                tme = old[1]
            tme = str(timedelta(seconds=tme))
            msg = '```Stopwatch stopped: {}\n'.format(tme)
            for lap in zip(range(1, len(old)), old[2:]):
                msg += '\nLap {0:03} - {1}'.format(*lap)
            msg += '```'
            await self.bot.send_message(ctx.message.channel, msg)
        else:
            await self.bot.send_message(
                ctx.message.channel, 'No stop watches started, cannot stop.')

    @stopwatch.command(name='status',
                       aliases=['look', 'peak'],
                       pass_context=True)
    async def _sw_status(self, ctx):
        aid = ctx.message.author.id
        now = ctx.message.timestamp.timestamp()
        if aid in self.stopwatches:
            old = self.stopwatches[aid]
            if old[0]:
                tme = now - old[0] + old[1]
            else:
                tme = old[1]
            tme = str(timedelta(seconds=tme))
            msg = '```Stopwatch time: {}'.format(tme)
            if old[0]:
                msg += '\n'
            else:
                msg += ' [paused]\n'
            for lap in zip(range(1, len(old)), old[2:]):
                msg += '\nLap {0:03} - {1}'.format(*lap)
            msg += '```'
            await self.bot.send_message(ctx.message.channel, msg)
        else:
            await self.bot.send_message(
                ctx.message.channel, 'No stop watches started, cannot look.')

    @stopwatch.command(name='lap', aliases=['l'], pass_context=True)
    async def _sw_lap(self, ctx):
        """
    prints time

    does not pause, does not resume, does not delete
    """
        aid = ctx.message.author.id
        now = ctx.message.timestamp.timestamp()
        if aid in self.stopwatches:
            old = self.stopwatches[aid]
            if old[0]:
                tme = now - old[0] + old[1]
            else:
                tme = old[1]
            tme = str(timedelta(seconds=tme))
            await self.bot.say("Lap #{:03} time: **{}**".format(
                len(old) - 1, tme))
            if self.stopwatches[aid][-1] != tme:
                self.stopwatches[aid].append(tme)
        else:
            await self.bot.say('No stop watches started, cannot lap.')

    @stopwatch.command(name='pause',
                       aliases=['p', 'hold', 'h'],
                       pass_context=True)
    async def _sw_pause(self, ctx):
        """
    pauses the stopwatch

    Also prints current time, does not delete
    """
        aid = ctx.message.author.id
        now = ctx.message.timestamp.timestamp()
        if aid in self.stopwatches and self.stopwatches[aid][0]:
            old = now - self.stopwatches[aid][0] + self.stopwatches[aid][1]
            self.stopwatches[aid] = [0, old]
            old = str(timedelta(seconds=old))
            await self.bot.say("Stopwatch paused: **{}**".format(old))
        elif aid in self.stopwatches:
            await self.bot.say('Stop watch already paused.')
        else:
            await self.bot.say('No stop watches started, cannot pause.')

    def rolls(self, dice):
        out = []

        if not dice:
            dice = ['20']

        for roll in dice:
            match = re.search('^((\\d+)?d)?(\\d+)([+-]\\d+)?$', roll, re.I)
            message = ''
            if not match:
                message = 'Invalid roll'
            else:
                times = 1
                sides = int(match.group(3))
                add = 0
                if match.group(2):
                    times = int(match.group(2))
                if match.group(4):
                    add = int(match.group(4))

                if times > 100:
                    message = 'Cannot roll that many dice  '
                elif sides > 120:
                    message = 'Cannot find a dice with that many sides  '
                elif times < 1:
                    message = 'How?  '
                elif sides < 2:
                    message = 'No  '
                else:
                    total = 0
                    for i in range(times):
                        num = random.randint(1, sides) + add
                        total += num
                        message += '{}, '.format(num)
                message = message[:-2]
                if times > 1:
                    message += '(sum = {})'.format(total)
            out.append('{}: {}'.format(roll, message))
        return out

    @commands.command(pass_context=True, aliases=['c', 'choice'])
    async def choose(self, ctx, *, choices):
        """Chooses a value from a comma seperated list"""
        choices = split(choices)
        choice = random.choice(choices)
        choice_reps = {
            r'(?i)^(should)\s+I\s+': r'You \1 ',
            r'(?i)^([wcs]hould|can|are|were|is)\s+(\S+)\s+': r'\2 \1 ',
            r'\?$': '.',
            r'(?i)^am\s+I\s+': 'Thou art ',
            r'(?i)\b(I|me)\b': 'you',
            r'(?i)\bmy\b': 'your'
        }
        for r in choice_reps:
            choice = re.sub(r, choice_reps[r], choice)

        message = ctx.message.author.mention + ':\n'
        message += inline(choice)
        await self.bot.say(message)

    @commands.command(name='remindme', pass_context=True, aliases=['remind'])
    async def _add_reminder(self, ctx, *, message: str):
        '''
    adds a reminder

    'at' can be used when specifing exact time
    'in' is optional for offsets
    'me' can be seperate or part of the command name (also optinal)
    cannot mix offsets and exact times

    Samples:
    .remind me in 5 h message
    .remind me in 5 hours 3 m message
    .remind me 1 week message
    .remind me 7 months message
    .remindme in 7 months message
    .remind me at 2017-10-23 message
    .remind me at 2017-10-23T05:11:56 message
    .remindme at 2017-10-23 05:11:56 message
    .remindme at 10/23/2017 5:11 PM message
    .remind at 7:11 message
    .remind at 7:11:15 message
    .remind [me] remove <id>
    .remind [me] end <id>
    '''
        author = ctx.message.author.id
        channel = ctx.message.channel.id
        match = re.match(r'(?i)^(me\s+)?(remove|end|stop)\s+(\d+)', message)
        if match:
            rid = int(match.group(3))
            for index, item in enumerate(self.heap['heap']):
                if type(item) == Reminder \
                    and item.reminder_id == rid \
                    and item.user_id == author:
                    self.heap['heap'].pop(index)
                    await self.bot.say(
                        ok(f'Message with id {rid} has been removed'))
                    return
            else:
                await self.bot.say(ok(f'Could not find message with id {rid}'))
        else:
            r = Reminder(channel, author, message)
            self.heap['heap'].push(r)
            await r.begin(self.bot)
        self.heap.save()

    @commands.command(pass_context=True, aliases=['a', 'ask'])
    async def question(self, ctx):
        '''Answers a question with yes/no'''
        message = ctx.message.author.mention + ':\n'
        message += inline(random.choice(['yes', 'no']))
        await self.bot.say(message)

    @commands.command(pass_context=True)
    async def poll(self, ctx, *, question):
        '''
    Starts a poll
    format:
    poll question? opt1, opt2, opt3 or opt4...
    poll stop|end
    '''

        if question.lower().strip() in ['end', 'stop']:
            for index, poll in enumerate(self.heap['heap']):
                if isinstance(
                        poll,
                        Poll) and poll.channel_id == ctx.message.channel.id:
                    self.heap['heap'].pop(index)
                    await poll.end(self.bot)
                    self.heap.save()
                    break
            else:
                await self.bot.say('There is no poll active in this channel')
            return

        match = re.search(r'^(.*?\?)\s*(.*?)$', question)
        if not match:
            await self.bot.say('Question could not be found.')
            return

        options = split(match.group(2))
        question = escape_mentions(match.group(1))

        poll = Poll(question, options, ctx.message.channel, 600)

        for item in self.heap['heap']:
            if poll == item:
                await self.bot.say(
                    'There is a poll active in this channel already')
                return
        self.heap['heap'].push(poll)
        self.heap.save()
        await poll.begin(self.bot)
Beispiel #13
0
class AZ:
    def __init__(self, bot):
        self.bot = bot
        self.last = {}
        self.conf = Config('configs/az.json')
        self.prev_img = {}
        if 'lenny' not in self.conf:
            self.conf['lenny'] = {}
        if 'img-reps' not in self.conf:
            self.conf['img-reps'] = {}
        if 'repeat_after' not in self.conf:
            self.conf['repeat_after'] = 3
        self.conf.save()

    @commands.command()
    async def lenny(self, first=''):
        out = None
        try:
            num = int(first)
            if num < 1:
                num = 1
            if num > 10:
                num = 10
        except:
            num = 1
            out = self.conf['lenny'].get(first.lower(), None)
        out = code(out) if out else '\n( ͡° ͜ʖ ͡° )'
        await self.bot.say(out * num)

    @commands.command()
    async def shrug(self):
        await self.bot.say('\n¯\_(ツ)_/¯')

    @commands.command(pass_context=True)
    async def me(self, ctx, *, message: str):
        await self.bot.say('*{} {}*'.format(ctx.message.author.name, message))
        await self.bot.delete_message(ctx.message)

    @commands.command(pass_context=True, name='set_colour', aliases=['sc'])
    @perms.is_in_servers('168702989324779520')
    @perms.has_role_check(lambda r: r.id == '258405421813989387')
    async def _set_colour(self, ctx, colour):
        """
    set role colour

    colour can be a hex value or a name:
    teal         0x1abc9c.
    dark_teal    0x11806a.
    green        0x2ecc71.
    dark_green   0x1f8b4c.
    blue         0x3498db.
    dark_blue    0x206694.
    purple       0x9b59b6.
    dark_purple  0x71368a.
    magenta      0xe91e63.
    dark_magenta 0xad1457.
    gold         0xf1c40f.
    dark_gold    0xc27c0e.
    orange       0xe67e22.
    dark_orange  0xa84300.
    red          0xe74c3c.
    dark_red     0x992d22.
    lighter_grey 0x95a5a6.
    dark_grey    0x607d8b.
    light_grey   0x979c9f.
    darker_grey  0x546e7a.
    """
        cols = {
            'teal': discord.Colour.teal(),
            'dark_teal': discord.Colour.dark_teal(),
            'green': discord.Colour.green(),
            'dark_green': discord.Colour.dark_green(),
            'blue': discord.Colour.blue(),
            'dark_blue': discord.Colour.dark_blue(),
            'purple': discord.Colour.purple(),
            'dark_purple': discord.Colour.dark_purple(),
            'magenta': discord.Colour.magenta(),
            'dark_magenta': discord.Colour.dark_magenta(),
            'gold': discord.Colour.gold(),
            'dark_gold': discord.Colour.dark_gold(),
            'orange': discord.Colour.orange(),
            'dark_orange': discord.Colour.dark_orange(),
            'red': discord.Colour.red(),
            'dark_red': discord.Colour.dark_red(),
            'lighter_grey': discord.Colour.lighter_grey(),
            'dark_grey': discord.Colour.dark_grey(),
            'light_grey': discord.Colour.light_grey(),
            'darker_grey': discord.Colour.darker_grey()
        }
        colour = colour.lower().strip()
        m = re.search('^(0[hx])?([a-f0-9]{6})$', colour)
        if colour in cols:
            c = cols[colour]
        elif m:
            c = discord.Colour(int(m.group(2), 16))
        else:
            await self.bot.say('could not find valid colour, see help')
            return

        server = ctx.message.server
        for role in server.roles:
            if role.id == '258405421813989387':
                await self.bot.edit_role(server, role, colour=c)
                await self.bot.say(ok())
                return
        await self.bot.say('could not find role to change')

    @commands.command(pass_context=True)
    @perms.in_group('img')
    async def img(self, ctx, *search):
        if not os.path.exists(self.conf.get('path', '')):
            logger.debug('could not find images')
            await self.bot.say('{path} does not exist')
            return

        try:
            # load repo
            repo = Repo(self.conf.get('path', ''))
            loop = self.bot.loop
            author = Actor('navi', '*****@*****.**')
            remote = repo.remotes.origin
            users = set()
            logger.debug('loaded git info in image repo')

            # check for changed files
            logger.debug('getting users')
            for fname in repo.untracked_files:
                fname = os.path.join(self.conf.get('path', ''), fname)
                uname = getpwuid(stat(fname).st_uid).pw_name
                users.add(uname)
            logger.debug('found users: %s', ', '.join(users))

            # commit changes
            if users or repo.untracked_files:
                logger.debug('adding files')
                await loop.run_in_executor(None, repo.index.add,
                                           repo.untracked_files)
                msg = f"navi auto add - {', '.join(unames)}: added files"
                logger.debug('commiting')
                run = lambda: repo.index.commit(
                    msg, author=author, committer=author)
                await loop.run_in_executor(None, run)
                users = True  # just in case

            # sync with remote
            logger.debug('pull')
            await loop.run_in_executor(None, remote.pull)
            if users:
                logger.debug('push')
                await loop.run_in_executor(None, remote.push)
        except:
            pass

        search = [re.sub(r'[^\w\./#\*-]+', '', i).lower() for i in search]
        search = dh.remove_comments(search)

        loop = asyncio.get_event_loop()
        try:
            f = loop.run_in_executor(None, azfind.search, self.conf['path'],
                                     search)
            path = await f
        except:
            path = ''

        self.prev_img[ctx.message.channel.id] = path

        if not path or not path.strip():
            await self.bot.send_message(
                ctx.message.channel,
                "couldn't find anything matching: `{}`".format(search))
            return

        try:
            url = path.replace(self.conf['path'], self.conf['path-rep'])
            logger.info(url)
            if url.rpartition('.')[2] in ('gif', 'png', 'jpg', 'jpeg'):
                try:
                    em = discord.Embed()
                    em.set_image(url=url)
                    logger.debug(f'sending {str(em.to_dict())}')
                    await self.bot.say(embed=em)
                except:
                    await self.bot.say(url)
            elif url.rpartition('.')[2] in ('zip', 'cbz'):
                zf = zipfile(path, 'r')
                for fl in zf.filelist:
                    f = zf.open(fl.filename)
                    await self.bot.send_file(ctx.message.channel,
                                             f,
                                             filename=fl.filename)
                    f.close()
                zf.close()
            else:
                await self.bot.say(url)
        except:
            raise
            await self.bot.say('There was an error uploading the image, ' + \
                               'but at least I didn\'t crash :p'
            )

    @commands.command(pass_context=True)
    @perms.in_group('img')
    async def imgt(self, ctx, tag):
        if not os.path.exists(self.conf.get('path', '')):
            await self.bot.say('{path} does not exist')
            return
        path = self.prev_img.get(ctx.message.channel.id, default=None)
        if path == None:
            await self.bot.say('Previous image not detected.')
            return

        #debugging purposes
        logger.debug(path)
        logger.debug(tag)
        #probably want to parse tag for valid format

        updatedPath = '{0}_{3}{1}{2}'.format(*path.rpartition('.'), tag)
        logger.debug(updatedPath)
        os.rename(path, updatedPath)
        try:
            # load repo
            repo = Repo(self.conf.get('path', ''))
            loop = self.bot.loop
            author = Actor('navi', '*****@*****.**')
            remote = repo.remotes.origin
            file_dict = {}

            # check for changed files
            for fname in repo.untracked_files:
                fname = os.path.join(self.conf.get('path', ''), fname)
                uname = getpwuid(stat(fname).st_uid).pw_name
                if uname in file_dict:
                    file_dict[uname].append(fname)
                else:
                    file_dict[uname] = [fname]

            # commit changes
            for uname, files in file_dict.items():
                await loop.run_in_executor(None, repo.index.add, files)
                msg = f"navi auto add - {uname}: added files"
                run = lambda: repo.index.commit(
                    msg, author=author, committer=author)
                await loop.run_in_executor(None, run)

            # sync with remote
            await loop.run_in_executor(None, remote.pull)
            if file_dict:
                await loop.run_in_executor(None, remote.push)
        except:
            pass

    async def repeat(self, message):
        chan = message.channel
        data = self.last.get(chan, ['', 0])

        if not message.content:
            return

        if data[0] == message.content.lower():
            data[1] += 1
        else:
            data = [message.content.lower(), 1]

        if data[1] == self.conf.get('repeat_after', 3):
            await self.bot.send_message(chan, message.content)
            data[1] = 0

        self.last[chan] = data
Beispiel #14
0
from cogs.utils.config import Config

logger = logging.getLogger('navi.perms')

# Check for perms, used for checking if a user can run a command

# load the config with special perms
config = Config('configs/perms.json')

if 'owner' not in config:
    import re
    owner = ''
    while not owner or not re.search('^\\d{15,}$', owner):
        owner = input('please enter YOUR id(use `\\@NAME` to find yours): ')
    config['owner'] = owner
    config.save()


def is_owner():
    return commands.check(lambda ctx: is_owner_check(ctx.message))


def in_group(group):
    return commands.check(lambda ctx: in_group_check(ctx.message, group))


def has_perms(**perms):
    return commands.check(lambda ctx: check_permissions(ctx.message, **perms))


def has_role_check(check, **perms):