Exemplo n.º 1
0
class Foo(Plugin):
    def load(self):
        '''
        Called on plugin load/reload
        '''

        # Loads the value of the `bar` configuration key with a default value
        # if the key were to be absent
        self.bar = self.config.get('bar', 42)

    @command(p.string('!foo') + p.bind(p.integer, 'number'))
    async def foo_command(self, message, number):
        '''
        Called on inputs starting with the string '!foo' (case insensitive)
        followed by an integer whose value will be bound to the `number`
        variable
        '''

        await self.send_message(
            message.
            channel,  # Sends a message in the same channel as the input
            '{} * {} = {}'.format(number, self.bar, number * self.bar))

    @command(p.string('!id') + p.bind(p.mention, 'user_id'), master_only)
    async def id_command(self, message, user_id):
        '''
        Called on inputs starting with the string '!id' (case insensitive)
        followed by a discord user mention (usually displayed '@SomeUser' on
        discord's client)

        We have access to SomeUser's discord's ID in the `user_id` variable

        The command is marked as `master_only` and will execute only if the
        input was sent by someone who was registered as 'master' in the
        configuration file
        '''

        await self.send_message(
            message.channel,
            '{}: the id is {}'.format(message.author.mention, user_id),
            delete_after=30  # Message deletes itself after 30 seconds
        )
Exemplo n.º 2
0
class Dnd(Plugin):
    @command(
        p.string('!roll') + p.bind(p.oneplus(dice_roll_parser), 'dice') +
        p.eof,
        # restrictions
        user_cooldown(30),
        global_cooldown(15))
    async def roll_dice_command(self, message, dice):
        grouped_dice = group_dice(dice)
        enforce_dnd_invariants(grouped_dice)

        results = map(roll_die, grouped_dice)

        response = ('{} 🎲 rolling your dice\n'
                    '{}'.format(message.author.mention,
                                f.code_block('\n'.join(results))))

        await self.send_message(message.channel, response, delete_after=30)
Exemplo n.º 3
0
class Instagram(Plugin):
    def load(self):
        access_token = self.config.get(c.ACCESS_TOKEN_KEY)
        client_secret = self.config.get(c.CLIENT_SECRET_KEY)
        self.api = InstagramAPI(access_token=access_token,
                                client_secret=client_secret)

    @command(p.string('!instagram') + p.bind(p.word, 'channel'), master_only)
    async def last_pic(self, message, channel):
        users = self.api.user_search(channel)
        if users:
            user = users[0]
            media, _ = self.api.user_recent_media(user_id=user.id, count=10)
            medium = random.choice(media)
            await self.send_message(
                message.channel,
                'A media from `{}`\'s instagram\n{}'.format(
                    user.full_name, medium.images['standard_resolution'].url),
                delete_after=30)
Exemplo n.º 4
0
class Stats(Plugin):

    GAME_DUMP_INTERVAL = 60 * 10

    def load(self):
        self.add_web_handlers(
            (r'/stats/game', StatsGameHandler),
            (r'/stats/user', StatsUserHandler),
            (r'/stats/api/games/top/(?P<server_id>\w+)', StatsGamesTopHandler,
             dict(plugin=self)),
            (r'/stats/api/games/user/(?P<user_id>\w+)', StatsGamesUserHandler,
             dict(plugin=self)),
            (r'/stats/(.*)', StatsStaticHandler),
        )

        self.game_times_by_id = dict()

        now = time()

        for server in self.bot.servers:
            for member in server.members:
                if member.game and member.game.name:
                    game_timer = (member.game.name.lower(), now)
                    self.game_times_by_id[member.id] = game_timer

        self.alive = True
        self.run_async(self.dump_periodically())

    def unload(self):
        self.alive = False
        self.dump_all()

    async def on_member_update(self, before, after):
        if before.game != after.game:
            self.update_game(after.id, after.game)

    '''
    Commands
    '''

    stats_prefix = p.string('!stats')

    stats_games_prefix = stats_prefix + p.string('games')

    @command(stats_games_prefix + p.bind(p.mention, 'user_id'), master_only)
    async def stats_games(self, message, user_id):
        member = message.server.get_member(str(user_id))
        if member is None:
            return

        # Flush current tracking
        self.update_game(member.id, member.game)

        games = self.top_user_games(user_id)
        top = [(game.name, game.duration) for game in games[:10]]

        if games:
            since = min(map(lambda g: g.created_at, games))
            since_days = int((time() - since.timestamp()) / (3600 * 24))

            response = (
                '{} has played **{}** different games in the last **{}** days '
                'for a total of **{}** seconds\ntop 10:\n{}').format(
                    f.mention(user_id), len(games), since_days,
                    sum(map(lambda g: g.duration, games)),
                    f.code_block(f.format_sql_rows(top)))
        else:
            response = '{} has not played any games yet'.format(
                f.mention(user_id))

        await self.send_message(message.channel, response, delete_after=25)

    @command(stats_games_prefix + p.string('top') +
             p.bind(p.maybe(p.integer), 'count'), master_only)
    async def stats_games_top(self, message, count=10):
        data = self.top_games(message.server, count)

        if data:
            await self.send_message(
                message.channel,
                'Most **{}** games played on this server\n{}'.format(
                    len(data), f.code_block(f.format_sql_rows(data))),
                delete_after=25)

    @command(stats_games_prefix + p.string('server'), master_only)
    async def top_games_link(self, message):
        await self.send_message(
            message.channel,
            'top games played on this server: https://globibot.com/stats/game?id={}'
            .format(message.server.id),
            delete_after=45)

    '''
    Details
    '''

    def top_games(self, server, count):
        user_ids = tuple(member.id for member in server.members)

        with self.transaction() as trans:
            trans.execute(q.top_games, dict(limit=count, authors_id=user_ids))

            return trans.fetchall()

    def top_user_games(self, user_id):
        with self.transaction() as trans:
            trans.execute(q.author_games, dict(author_id=user_id))

            return [GamePlayed(*row) for row in trans.fetchall()]

    def update_game(self, user_id, new_game):
        try:
            game_name, start = self.game_times_by_id[user_id]
            self.add_game_time(user_id, game_name, start)
        except KeyError:
            pass

        if new_game and new_game.name:
            self.game_times_by_id[user_id] = (new_game.name.lower(), time())
        else:
            self.game_times_by_id.pop(user_id, None)

    def add_game_time(self, user_id, game_name, start):
        duration = int(time() - start)

        with self.transaction() as trans:
            trans.execute(q.add_game_times(1),
                          [(user_id, game_name, duration)])

    def dump_all(self):
        now = time()

        data = [(user_id, game_name, now - start)
                for user_id, (game_name,
                              start) in self.game_times_by_id.items()]

        with self.transaction() as trans:
            trans.execute(q.add_game_times(len(data)), data)

        for user_id, (game_name, start) in self.game_times_by_id.items():
            self.game_times_by_id[user_id] = (game_name, now)

    async def dump_periodically(self):
        while True:
            await asyncio.sleep(Stats.GAME_DUMP_INTERVAL)

            if not self.alive:
                break

            self.dump_all()
Exemplo n.º 5
0
class Twitch(Plugin):
    def load(self):
        self.client_id = self.config.get(c.CLIENT_ID_KEY)

        if not self.client_id:
            self.warning('Missing client id: API calls might not work')

        self.api = TwitchAPI(self.client_id, self.debug)
        self.pubsub = PubSub(self.debug, self.run_async)

        self.restore_monitored()

    def unload(self):
        asyncio.ensure_future(self.pubsub.shutdown())

    '''
    Commands
    '''

    twitch_prefix = p.string('!twitch')

    @command(twitch_prefix + p.string('status') + p.bind(p.word, 'name'))
    async def twitch_channel_status(self, message, name):
        stream, channel = await asyncio.gather(self.api.stream(name),
                                               self.api.channel(name))

        response = '`{name}` is __{status}__'
        info = dict(name=channel.display_name,
                    status='online' if stream else 'offline')

        if stream:
            response += ': `{title}`\n\n🕹 {game}\n👀 {viewers:,}\n'
            online_info = dict(title=stream.channel.status,
                               game=stream.game,
                               viewers=stream.viewers)

            info = {**info, **online_info}

        await self.send_message(message.channel,
                                response.format(**info),
                                delete_after=15)

    twitch_top_prefix = twitch_prefix + p.string('top')

    @command(twitch_top_prefix + p.string('games') +
             p.bind(p.maybe(p.integer), 'count'))
    async def twitch_top_games(self, message, count=10):
        top_games = await self.api.top_games(count)

        info = [(top_game.game.name, '📺 {:,}'.format(top_game.channels),
                 '👀 {:,}'.format(top_game.viewers))
                for top_game in top_games]

        await self.send_message(message.channel,
                                'Top {} games on Twitch right now\n{}'.format(
                                    len(top_games),
                                    f.code_block(f.pad_rows(info, ' | '))),
                                delete_after=15)

    @command(twitch_top_prefix + p.string('channels') +
             p.bind(p.maybe(p.integer), 'count'))
    async def twitch_top_channels(self, message, count=10):
        streams = await self.api.top_channels(count)

        info = [(stream.channel.display_name, '🕹 {}'.format(stream.game),
                 '👀 {:,}'.format(stream.viewers), stream.channel.status)
                for stream in streams]

        await self.send_message(message.channel,
                                'Top {} games on Twitch right now\n{}'.format(
                                    len(streams),
                                    f.code_block(f.pad_rows(info, ' | '))),
                                delete_after=15)

    @command(twitch_prefix + p.string('monitor') + p.bind(p.word, 'name'),
             master_only)
    async def twitch_monitor(self, message, name):
        channel = await self.api.channel(name)

        with self.transaction() as trans:
            trans.execute(q.add_monitored,
                          dict(name=channel.name, server_id=message.server.id))

            self.run_async(self.monitor_forever(channel.name, message.server))

            await self.send_message(message.channel,
                                    'Now monitoring `{}`'.format(
                                        channel.display_name),
                                    delete_after=15)

    @command(twitch_prefix + p.string('unmonitor') + p.bind(p.word, 'name'),
             master_only)
    async def twitch_unmonitor(self, message, name):
        channel = await self.api.channel(name)

        await self.pubsub.unsubscribe(
            PubSub.Topics.VIDEO_PLAYBACK(channel.name), message.server.id)

        with self.transaction() as trans:
            trans.execute(q.remove_monitored,
                          dict(name=channel.name, server_id=message.server.id))

            await self.send_message(message.channel,
                                    'Stopped monitoring `{}`'.format(
                                        channel.display_name),
                                    delete_after=15)

    @command(twitch_prefix + p.string('monitored'), master_only)
    async def monitored(self, message):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            channels = [
                channel.name for channel in monitored
                if str(channel.server_id) == message.server.id
            ]

            await self.send_message(
                message.channel,
                'I\'m currently monitoring the following channels:\n{}'.format(
                    f.code_block(channels)),
                delete_after=15)

    '''
    Details
    '''

    async def monitor_forever(self, name, server):
        self.info('Monitoring: {}'.format(name))

        channel = await self.api.channel(name)
        events = await self.pubsub.subscribe(
            PubSub.Topics.VIDEO_PLAYBACK(channel.name), server.id)

        async for event in events:
            self.debug(event)
            if event['type'] == 'stream-up':
                await self.send_message(
                    server.default_channel,
                    '`{}` just went live!\n{}'.format(channel.display_name,
                                                      channel.url))
            if event['type'] == 'stream-down':
                await self.send_message(
                    server.default_channel,
                    '`{}` just went offline 😢'.format(channel.display_name))

        self.info('Stopped monitoring: {}'.format(name))

    def restore_monitored(self):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            for channel in monitored:
                server = next(serv for serv in self.bot.servers
                              if serv.id == str(channel.server_id))
                self.run_async(self.monitor_forever(channel.name, server))
Exemplo n.º 6
0
class Dj(Plugin):
    def load(self):
        context = dict(plugin=self, bot=self.bot)
        self.add_web_handlers(
            (r'/voice', VoiceHandler, context),
            (r'/ws/voice', VoiceWebSocketHandler, context),
        )

        self.players = dict()
        self.volumes = defaultdict(lambda: 1.0)
        self.tts = TTSManager()

        self.ws_consumers = defaultdict(set)
        self.ongoing_skips = dict()

    async def on_reaction_add(self, reaction, user):
        if user == self.bot.user:
            return

        if reaction.emoji == '🗳':
            await self.check_skips(reaction.message, reaction.count - 1)

    '''
    Commands
    '''

    @command(p.string('!tts'))
    async def say_command(self, message):
        content = message.clean_content[5:].strip()
        await self.queue_tts(message.server, message.author, content)

        await self.delete_message_after(message, 5)

    @command(p.string('!voteskip'))
    async def voteskip(self, message):
        if message.server.id in self.ongoing_skips:
            return

        player = self.get_player(message.server)

        embed = Embed(title='Voteskip',
                      description='Skip __{}__ ?'.format(
                          player.items[0].resource))
        embed.set_footer(text='Click the reaction below to cast your vote')

        m = await self.send_message(message.channel, '', embed=embed)
        await self.bot.add_reaction(m, '🗳')

        self.ongoing_skips[message.server.id] = m
        self.run_async(self.skip_timeout(message.server.id, m))

    @command(p.string('!tts-lang'))
    async def tts_lang_command(self, message):
        lang = message.clean_content[10:].strip()
        self.tts.set_server_language(message.server, lang)

        await self.delete_message_after(message, 5)

    @command(p.string('!play'))
    async def play(self, message):
        await self.queue_item(message.server, message.author,
                              ItemType.YTDLLink,
                              message.clean_content[6:].strip())

        await self.delete_message_after(message, 5)

    @command(p.string('!skip'), master_only)
    async def skip(self, message):
        player = self.get_player(message.server)
        player.skip()

        await self.delete_message_after(message, 5)

    @command(p.string('!volume') + p.bind(p.integer, 'vol'), master_only)
    async def volume(self, message, vol):
        if vol < 0 or vol > 100:
            return

        vol = float(vol) / 100
        self.volumes[message.server.id] = vol
        player = self.get_player(message.server)
        player.player.volume = vol

    '''
    Helpers
    '''

    async def check_skips(self, message, count):
        try:
            skip_m = self.ongoing_skips[message.server.id]
        except KeyError:
            pass
        else:
            if skip_m.id == message.id:
                user_count = len(
                    self.bot.voice_client_in(
                        skip_m.server).channel.voice_members) - 1
                if count >= required_skips_for(user_count):
                    player = self.get_player(skip_m.server)
                    player.skip()

                    del self.ongoing_skips[message.server.id]
                    await self.bot.delete_message(message)

    async def skip_timeout(self, server_id, m):
        await asyncio.sleep(30)
        try:
            skip_m = self.ongoing_skips[server_id]
        except:
            pass
        else:
            if skip_m.id == m.id:
                await self.bot.delete_message(m)
                del self.ongoing_skips[server_id]

    async def queue_tts(self, server, user, content, lang=None):
        sound_file = self.tts.talk_in(server, content, lang)

        await self.queue_item(server, user, ItemType.LocalFile, sound_file)

    async def join_voice(self, channel):
        voice = self.bot.voice_client_in(channel.server)
        if voice is None:
            await self.bot.join_voice_channel(channel)
        else:
            await voice.move_to(channel)

    async def leave_voice(self, server):
        voice = self.bot.voice_client_in(server)
        if voice is not None:
            await voice.disconnect()

    async def queue_item(self, server, user, type, resource):
        try:
            item = PlayerItem(type, resource, user)
        except Exception as e:
            self.notify_ws_error(server, str(e), resource)
        else:
            player = self.get_player(server)
            await player.enqueue(item)
            self.notify_ws_queue(server, item)

    def get_player(self, server):
        try:
            player = self.players[server.id]
        except KeyError:
            get_volume = lambda: self.volumes[server.id]
            player = ServerPlayer(server, self.bot.voice_client_in, get_volume)
            self.players[server.id] = player
            on_next = lambda item: self.notify_ws_next(server, item)
            self.run_async(player.start(self.error, on_next))

        return player

    def notify_ws_next(self, server, item):
        data = dict(type='next', playing=item_data(item))

        self.notify_ws(server, data)

    def server_queue_data(self, server):
        player = self.get_player(server)

        return dict(type='queue_data',
                    items=[item_data(item) for item in player.items],
                    server_id=server.id)

    def notify_ws_error(self, server, error, resource):
        data = dict(type='error', error=error, resource=resource)
        self.notify_ws(server, data)

    def notify_ws_queue(self, server, item):
        data = dict(type='queue', item=item_data(item))

        self.notify_ws(server, data)

    def notify_ws(self, server, data):
        data['server_id'] = server.id

        for consumer in self.ws_consumers[server.id]:
            consumer.write_message(data)
Exemplo n.º 7
0
class Logger(Plugin):
    def load(self):
        self.ws_consumers = defaultdict(set)

        self.add_web_handlers(
            (r'/ws/logs', LoggerWebSocketHandler, dict(plugin=self)),
            (r'/logs/top', LogsApiTopHandler, dict(plugin=self, bot=self.bot)),
            (r'/logs/user/(?P<user_id>\d+)', LogsApiUserHandler,
             dict(plugin=self, bot=self.bot)),
        )

    '''
    Raw events
    '''

    async def on_new(self, message):
        if message.server:
            self.save_log(message, message.timestamp)
            self.notify_ws(message, 'original')

    async def on_delete(self, message):
        with self.transaction() as trans:
            trans.execute(q.mark_deleted, dict(id=message.id))

        self.notify_ws(message, 'deleted')

    async def on_edit(self, before, after):
        self.save_log(after, after.edited_timestamp)
        self.notify_ws(after, 'edit')

    '''
    Commands
    '''

    @command(
        p.string('!deleted') + p.bind(p.mention, 'user_id') +
        p.bind(p.maybe(p.integer), 'count'), master_only)
    async def deleted_messages(self, message, user_id, count=5):
        with self.transaction() as trans:
            trans.execute(q.last_deleted_logs,
                          dict(author_id=user_id, limit=count))
            results = trans.fetchall()
            messages = [
                '{}{}'.format(row[0], ' '.join(row[1])) for row in results
            ]

            await self.send_message(
                message.channel,
                'last **{}** deleted messages from <@{}>:\n{}'.format(
                    len(results), user_id, f.code_block(messages)),
                delete_after=30)

    @command(
        p.string('!edited') + p.bind(p.mention, 'user_id') +
        p.bind(p.maybe(p.integer), 'count'), master_only)
    async def edited_messages(self, message, user_id, count=10):
        with self.transaction() as trans:
            trans.execute(q.last_edited_logs,
                          dict(author_id=user_id, limit=count))
            results = trans.fetchall()
            grouped = groupby(results, key=lambda row: row[0])

            messages = [
                ' ➡ '.join([
                    '{}{}'.format(c[1], ' '.join(c[2]))
                    for c in reversed(list(contents))
                ]) for _, contents in grouped
            ]

            await self.send_message(
                message.channel,
                'last **{}** edited messages from <@{}>:\n{}'.format(
                    len(messages), user_id, '\n'.join(messages)),
                delete_after=30)

    @command(
        p.string('!logs') + p.string('find') + p.bind(p.mention, 'user_id') +
        p.bind(p.word, 'what') + p.bind(p.maybe(p.integer), 'count'),
        master_only)
    async def logs_find(self, message, user_id, what, count=10):
        with self.transaction() as trans:
            trans.execute(
                q.find_logs,
                dict(author_id=user_id, str='%%{}%%'.format(what),
                     limit=count))
            results = trans.fetchall()
            messages = [r[0] for r in results]

            await self.send_message(message.channel,
                                    'Found **{}** messages\n{}'.format(
                                        len(results), f.code_block(messages)),
                                    delete_after=30)

    @command(
        p.string('!logs') + p.string('top') +
        p.bind(p.maybe(p.integer), 'count'), master_only)
    async def most_active(self, message, count=10):
        with self.transaction() as trans:
            trans.execute(q.most_logs,
                          dict(server_id=message.server.id, limit=count))
            results = [
                '{}: {:,} messages'.format(f.mention(r[0]), r[1])
                for r in trans.fetchall()
            ]

            await self.send_message(
                message.channel,
                'most **{}** active users on this server\n{}'.format(
                    len(results), '\n'.join(results)),
                delete_after=30)

    '''
    Details
    '''

    def save_log(self, message, stamp):
        attachments = [
            attachment['proxy_url'] for attachment in message.attachments
        ]

        with self.transaction() as trans:
            trans.execute(
                q.create_log,
                dict(id=message.id,
                     server_id=message.server.id,
                     channel_id=message.channel.id,
                     author_id=message.author.id,
                     content=message.content,
                     stamp=stamp,
                     attachments=attachments))

    def notify_ws(self, message, t):
        message_ = dict(id=message.id,
                        tts=message.tts,
                        type=t,
                        pinned=message.pinned,
                        content=message.content,
                        clean_content=message.clean_content)
        author = dict(id=message.author.id,
                      name=message.author.name,
                      nick=message.author.nick,
                      joindate=message.author.joined_at.timestamp(),
                      color=message.author.color.to_tuple())
        channel = dict(id=message.channel.id, name=message.channel.name)
        server = dict(id=message.server.id, name=message.server.name)
        # TODO: Serialize mentions
        # mentions = dict(
        #     users    = message.mentions,
        #     channels = message.channel_mentions,
        #     roles    = message.role_mentions
        # )

        data = dict(
            server=server,
            channel=channel,
            author=author,
            # mentions = mentions,
            message=message_)

        for consumer in self.ws_consumers[message.server.id]:
            consumer.write_message(data)
Exemplo n.º 8
0
class Eval(Plugin):
    def load(self):
        self.docker = AsyncDockerClient()

        with self.transaction() as trans:
            trans.execute(q.fetch_behaviors)
            self.behaviors = [row[0] for row in trans.fetchall()]
            self.default_behavior = self.behaviors[0]

        self.last_snippets = dict()

    '''
    Commands
    '''

    eval_prefix = p.string('!eval')

    @command(p.bind(p.snippet, 'snippet'))
    async def eval_code(self, message, snippet):
        behavior = self.get_behavior(message.author.id)
        if behavior is None:
            await self.send_message(
                message.author,
                'Psst\nYou seem to have posted a `code snippet`\n'
                'I can evaluate it if you want\n'
                'Since your behavior was not defined, I set it to `manual`\n'
                'type `!eval help` for more information')

        self.last_snippets[message.author.id] = snippet

        if behavior != 'auto':
            return

        await self.run_snippet(message, snippet, "")

    @command(eval_prefix + p.string('run'))
    async def eval_run(self, message):
        behavior = self.get_behavior(message.author.id)
        if behavior == 'off':
            return

        try:
            snippet = self.last_snippets[message.author.id]
            run_idx = message.content.index('run')
            await self.run_snippet(message, snippet,
                                   message.content[run_idx + len('run'):])
        except KeyError:
            pass

    @command(eval_prefix + p.string('save') + p.bind(p.word, 'name'))
    async def eval_save(self, message, name):
        try:
            snippet = self.last_snippets[message.author.id]
            snippets = self.get_snippets(message.author.id)
            try:
                next(s for s in snippets if s.name == name)
                await self.send_message(
                    message.channel,
                    '{} You already have a snippet named `{}`'.format(
                        message.author.mention, name),
                    delete_after=30)
            except StopIteration:
                if self.save_snippet(message.author.id, name, snippet):
                    await self.send_message(
                        message.channel,
                        '{} your last snippet was saved as `{}`\n use `/{}` to run it'
                        .format(message.author.mention, name, name),
                        delete_after=30)
        except KeyError:
            pass

    @command(p.bind(p.word, 'name'))
    async def command_run(self, message, name):
        if not name.startswith('/'):
            return

        name = name[1:]
        snippet = self.get_snippet(message.author.id, name)
        if snippet:
            name_idx = message.content.index(name)
            args = message.content[name_idx + len(name):]
            await self.run_snippet(message, snippet, args)

    eval_env_prefix = eval_prefix + p.string('env')

    @command(eval_env_prefix + p.string('inspect') +
             p.bind(p.word, 'env_name'))
    async def eval_env_inspect(self, message, env_name):
        environments = self.get_environments(message.author.id)

        try:
            env = next(e for e in environments if e.name == env_name)
        except StopIteration:
            await self.send_message(
                message.channel,
                '{} You don\'t have any environment saved under the name `{}`'.
                format(message.author.mention, env_name),
                delete_after=10)

        await self.send_message(
            message.channel, '{} `{}` environment was built from:\n{}'.format(
                message.author.mention, env_name,
                f.code_block(env.dockerfile, language='dockerfile')))

    @command(eval_env_prefix + p.string('list'))
    async def eval_env_list(self, message):
        environments = [
            env.name for env in self.get_environments(message.author.id)
            if env.author_id
        ]

        await self.send_message(
            message.channel, '{} You have `{}` saved environments\n{}'.format(
                message.author.mention, len(environments),
                f.code_block(environments)))

    @command(eval_env_prefix + p.string('map'))
    async def eval_env_map(self, message):
        environments = self.get_environments(message.author.id)

        padding = max(map(lambda env: len(env.language), environments),
                      default=0)
        mapping = [
            '{:{}} ➡ {}'.format(env.language, padding, env.name)
            for env in environments
        ]

        await self.send_message(
            message.channel,
            '{} Here is your eval mapping\n{}'.format(message.author.mention,
                                                      f.code_block(mapping)))

    @command(eval_env_prefix + p.string('set') + p.bind(p.word, 'language') +
             p.bind(p.word, 'env_name'))
    async def eval_set(self, message, language, env_name):
        environments = self.get_environments(message.author.id)

        try:
            env = next(e for e in environments
                       if e.name == env_name and e.author_id)
        except StopIteration:
            await self.send_message(
                message.channel,
                '{} You don\'t have any environment saved under the name `{}`'.
                format(message.author.mention, env_name),
                delete_after=10)
            return

        # Removing old mappings
        for env in environments:
            if env.language == language:
                self.set_environment_language(env.id, 'none')
        # Flagging the env with the language
        self.set_environment_language(env.id, language)
        await self.send_message(
            message.channel,
            '{} Your `{}` snippets will now be evaluated with your `{}` environment'
            .format(message.author.mention, language, env_name))

    @command(eval_env_prefix + p.string('library') + p.string('build') +
             p.bind(p.word, 'env_name') + p.bind(p.word, 'language') +
             p.bind(p.snippet, 'snippet'), master_only)
    async def build_env_library(self, message, env_name, language, snippet):
        if snippet.language != 'dockerfile':
            return

        response_stream = await self.send_message(message.channel,
                                                  'Building...')
        dockerfile = BytesIO(snippet.code.encode('utf8'))
        flags = dict(errored=False)

        image = self.tag_name(env_name)
        build_stream = self.docker.async_build(fileobj=dockerfile,
                                               tag=image,
                                               decode=True,
                                               rm=True)

        def format_data(data):
            flags['errored'] = ('error' in data)
            return '\n'.join([str(v) for v in data.values()])

        errored = flags['errored']
        if not errored:
            self.save_environment(None, 'library/{}'.format(env_name), image,
                                  snippet.code, language)

        await self.stream_data(response_stream, build_stream, format_data)
        notice = 'Build errored' if errored else 'Build succeeded'
        await self.send_message(message.channel, notice)
        await self.delete_message_after(response_stream, 10)

    @command(eval_env_prefix + p.string('build') + p.bind(p.word, 'env_name') +
             p.bind(p.snippet, 'snippet'), master_only)
    async def build_env(self, message, env_name, snippet):
        if snippet.language != 'dockerfile':
            return

        response_stream = await self.send_message(message.channel,
                                                  'Building...')
        dockerfile = BytesIO(snippet.code.encode('utf8'))
        flags = dict(errored=False)

        image = self.tag_name(env_name, message.author.id)
        build_stream = self.docker.async_build(fileobj=dockerfile,
                                               tag=image,
                                               decode=True,
                                               rm=True)

        def format_data(data):
            flags['errored'] = ('error' in data)
            return '\n'.join([str(v) for v in data.values()])

        errored = flags['errored']
        if not errored:
            self.save_environment(message.author.id, env_name, image,
                                  snippet.code)

        await self.stream_data(response_stream, build_stream, format_data)
        notice = 'Build errored' if errored else 'Build succeeded'
        await self.send_message(message.channel, notice)
        await self.delete_message_after(response_stream, 10)

    @command(p.string('!eval') + p.string('behavior') + p.eof)
    async def user_behavior(self, message):
        behavior = self.get_behavior(message.author.id)
        if behavior is None:
            behavior = self.default_behavior

        await self.send_message(message.channel,
                                '{} your eval behavior is `{}`'.format(
                                    message.author.mention, behavior),
                                delete_after=5)

    @command(
        p.string('!eval') + p.string('behavior') + p.string('set') +
        p.bind(p.word, 'behavior'))
    async def set_behavior(self, message, behavior):
        if behavior not in self.behaviors:
            return

        self.set_user_behavior(message.author.id, behavior)

        await self.send_message(
            message.channel,
            '{} your eval behavior has been set to `{}`'.format(
                message.author.mention, behavior),
            delete_after=5)

    '''
    details
    '''

    async def run_snippet(self, message, snippet, args):
        environment = self.get_environment(snippet.language, message.author.id)
        if environment is None:
            await self.send_message(
                message.channel,
                '{} You have no environment associated with the language `{}`'.
                format(message.author.mention, snippet.language),
                delete_after=10)
        else:
            directory = '/tmp/globibot/{}'.format(message.author.id)
            os.makedirs(directory, exist_ok=True)
            with open('{}/{}'.format(directory, 'code.snippet'), 'w') as f:
                f.write(snippet.code)

            await self.send_message(
                message.channel,
                '{} Running your `{}` snippet in `{}`'.format(
                    message.author.mention, snippet.language,
                    environment.name),
                delete_after=5)
            response_stream = await self.send_message(message.channel,
                                                      '`Waiting for output`')

            run_stream = self.docker.run_async(args, directory,
                                               environment.image,
                                               message.author.id)
            format_data = lambda line: line.decode('utf8')

            try:
                await self.stream_data(response_stream, run_stream,
                                       format_data)
                await self.send_message(message.channel,
                                        '`Exited`',
                                        delete_after=10)
            except AsyncDockerClient.Timeout:
                await self.send_message(message.channel,
                                        '`Evaluation timed out`')

            rmtree(directory)

    TAG_PREFIX = 'globibot_build'

    def tag_name(self, name, user_id=None):
        return '{}_{}:{}'.format(Eval.TAG_PREFIX, name,
                                 user_id if user_id else 'library'),

    def get_behavior(self, user_id):
        with self.transaction() as trans:
            trans.execute(q.get_behavior, dict(author_id=user_id))

            row = trans.fetchone()
            if row:
                return row[0]
            else:
                trans.execute(q.set_default_behavior,
                              dict(author_id=user_id, ))

    def set_user_behavior(self, user_id, behavior):
        with self.transaction() as trans:
            trans.execute(q.set_behavior,
                          dict(author_id=user_id, behavior=behavior))

    def get_environment(self, language, user_id):
        with self.transaction() as trans:
            trans.execute(q.get_environment,
                          dict(author_id=user_id, language=language))

            row = trans.fetchone()
            if row:
                return Environment(*row)

    def get_environments(self, user_id):
        with self.transaction() as trans:
            trans.execute(q.get_environments, dict(author_id=user_id, ))

            return [Environment(*row) for row in trans.fetchall()]

    def save_environment(self,
                         user_id,
                         env_name,
                         image,
                         dockerfile,
                         language=None):
        with self.transaction() as trans:
            trans.execute(
                q.save_environment,
                dict(author_id=user_id,
                     name=env_name,
                     image=image,
                     dockerfile=dockerfile,
                     language=language))

    def set_environment_language(self, env_id, language):
        with self.transaction() as trans:
            trans.execute(q.set_language, dict(id=env_id, language=language))

    def get_snippet(self, user_id, name):
        with self.transaction() as trans:
            trans.execute(q.get_snippet, dict(author_id=user_id, name=name))

            data = trans.fetchone()
            if data:
                return Snippet(*data)

    def get_snippets(self, user_id):
        with self.transaction() as trans:
            trans.execute(q.get_snippets, dict(author_id=user_id))

            return [Snippet(*row) for row in trans.fetchall()]

    def save_snippet(self, user_id, name, snippet):
        with self.transaction() as trans:
            trans.execute(
                q.save_snippet,
                dict(author_id=user_id,
                     name=name,
                     language=snippet.language,
                     code=snippet.code))

            return True
Exemplo n.º 9
0
class Giveaways(Plugin):
    def load(self):
        self.giveaways_left = defaultdict(lambda: 3)
        self.in_progress = dict()

        context = dict(plugin=self, bot=self.bot)
        self.add_web_handlers((r'/giveaways/user', GiveawayInfo, context),
                              (r'/giveaways/start', GiveawayStart, context))

    def start_giveaway(self, giveaway):
        # Only one giveaway at a time
        if giveaway.server.id in self.in_progress:
            return False
        # Check user limit
        if self.giveaways_left_for(giveaway.user) <= 0:
            return False
        # Consume one token
        self.giveaways_left[giveaway.user.id] -= 1
        # Lock server
        self.in_progress[giveaway.server.id] = set()
        # Runs the giveaway
        self.run_async(self.manage_giveaway(giveaway))
        return True

    @command(p.string('!enter'))
    async def enter_giveaway(self, message):
        try:
            giveaway = self.in_progress[message.server.id]
        except KeyError:
            return
        else:
            giveaway.add(message.author)

    def giveaways_left_for(self, user):
        return self.giveaways_left[user.id]

    def giveaway_message_content(self, giveaway, timeout):
        registered = self.in_progress[giveaway.server.id]

        data = dict(user=giveaway.user.mention,
                    title=giveaway.title,
                    count=len(registered),
                    registered=' '.join([user.mention for user in registered]),
                    time_left=timeout)
        return ('{user} started a giveaway: `{title}`\n'
                'Type `!enter` to participate\n'
                '{count} users are currently registered: {registered}\n'
                'The giveaway will end in approximately {time_left} seconds'.
                format(**data))

    async def manage_giveaway(self, giveaway):
        timeout = min(giveaway.timeout, c.MAX_GIVEAWAY_TIME)
        sleep_duration = 5
        last_registered_count = 0

        send_notification = lambda: self.send_message(
            giveaway.server.default_channel,
            self.giveaway_message_content(giveaway, timeout))

        message = await send_notification()

        while timeout > 0:

            await asyncio.sleep(sleep_duration)
            timeout -= sleep_duration

            registered_count = len(self.in_progress[giveaway.server.id])
            if registered_count > last_registered_count:
                last_registered_count = registered_count
                await self.bot.delete_message(message)
                message = await send_notification()
            else:
                await self.edit_message(
                    message, self.giveaway_message_content(giveaway, timeout))

        await self.bot.delete_message(message)
        await self.end_giveaway(giveaway)

    async def end_giveaway(self, giveaway):
        registered = self.in_progress[giveaway.server.id]
        del self.in_progress[giveaway.server.id]

        if len(registered) == 0:
            await self.send_message(
                giveaway.server.default_channel,
                'No one registered for the giveaway 😢\nNo one wins')
        else:
            winner = choice(list(registered))
            await self.send_message(
                giveaway.server.default_channel,
                '{} You are the giveaway winner!\n'
                'I will whisper you your prize shortly'.format(winner.mention))
            await self.send_message(
                winner, 'You won the giveaway of {} on `{}`\n'
                'Here is your prize:\n'
                '{}'.format(giveaway.user.mention, giveaway.server.name,
                            f.code_block(giveaway.content)))
Exemplo n.º 10
0
class Twitch(Plugin):

    def load(self):
        self.client_id = self.config.get(c.CLIENT_ID_KEY)
        self.client_secret = self.config.get(c.CLIENT_SECRET_KEY)

        if not self.client_id:
            self.warning('Missing client id: API calls might not work')

        self.api = TwitchAPI(self.client_id, self.debug)
        self.pubsub = PubSub(self.debug, self.run_async)

        self.channels_info = dict()
        self.restore_monitored()

        context = dict(plugin=self, bot=self.bot)
        self.add_web_handlers(
            (r'/twitch/oauth', OAuthRequestTokenHandler, context),
            (r'/twitch/authorize', OAuthAuthorizeHandler, context),
            (r'/twitch/status', TwitchStatusHandler, context),
            (r'/twitch/followed', TwitchFollowedHandler, context),
            (r'/twitch/disconnect', TwitchDisconnectHandler, context),
            (r'/twitch/mention', TwitchMentionHandler, context),
            (r'/twitch/whisper', TwitchWhisperHandler, context),
        )

        self.token_states = dict()

        self.run_async(self.refresh_channels_info_forever())

    def unload(self):
        asyncio.ensure_future(self.pubsub.shutdown())

    '''
    Commands
    '''

    twitch_prefix = p.string('!twitch')

    @command(twitch_prefix + p.string('status') + p.bind(p.word, 'name'))
    async def twitch_channel_status(self, message, name):
        stream, channel = await asyncio.gather(
            self.api.stream(name),
            self.api.channel(name)
        )

        response = '`{name}` is __{status}__'
        info = dict(
            name   = channel.display_name,
            status = 'online' if stream else 'offline'
        )

        if stream:
            response += ': `{title}`\n\n🕹 {game}\n👀 {viewers:,}\n'
            online_info = dict(
                title   = stream.channel.status,
                game    = stream.game,
                viewers = stream.viewers
            )

            info = {**info, **online_info}

        await self.send_message(
            message.channel,
            response.format(**info),
            delete_after=15
        )

    twitch_top_prefix = twitch_prefix + p.string('top')

    @command(twitch_top_prefix + p.string('games')
                               + p.bind(p.maybe(p.integer), 'count'))
    async def twitch_top_games(self, message, count=10):
        top_games = await self.api.top_games(count)

        info = [
            (top_game.game.name,
            '📺 {:,}'.format(top_game.channels),
            '👀 {:,}'.format(top_game.viewers))
            for top_game in top_games
        ]

        await self.send_message(
            message.channel,
            'Top {} games on Twitch right now\n{}'
                .format(len(top_games), f.code_block(f.pad_rows(info, ' | '))),
            delete_after=15
        )

    @command(twitch_top_prefix + p.string('channels')
                               + p.bind(p.maybe(p.integer), 'count'))
    async def twitch_top_channels(self, message, count=10):
        streams = await self.api.top_channels(count)

        info = [
            (stream.channel.display_name,
            '🕹 {}'.format(stream.game),
            '👀 {:,}'.format(stream.viewers),
            stream.channel.status)
            for stream in streams
        ]

        await self.send_message(
            message.channel,
            'Top {} games on Twitch right now\n{}'
                .format(len(streams), f.code_block(f.pad_rows(info, ' | '))),
            delete_after=15
        )

    @command(
        twitch_prefix + p.string('monitor') + p.bind(p.word, 'name'),
        master_only
    )
    async def twitch_monitor(self, message, name):
        channel = await self.api.channel(name)

        with self.transaction() as trans:
            trans.execute(q.add_monitored, dict(
                name      = channel.name,
                server_id = message.server.id
            ))

            self.run_async(self.monitor_forever(channel.name, message.server))

            await self.send_message(
                message.channel,
                'Now monitoring `{}`'.format(channel.display_name),
                delete_after=15
            )

    @command(
        twitch_prefix + p.string('unmonitor') + p.bind(p.word, 'name'),
        master_only
    )
    async def twitch_unmonitor(self, message, name):
        channel = await self.api.channel(name)

        await self.pubsub.unsubscribe(
            PubSub.Topics.VIDEO_PLAYBACK(channel.name),
            message.server.id
        )

        with self.transaction() as trans:
            trans.execute(q.remove_monitored, dict(
                name      = channel.name,
                server_id = message.server.id
            ))

            await self.send_message(
                message.channel,
                'Stopped monitoring `{}`'.format(channel.display_name),
                delete_after=15
            )

    @command(twitch_prefix + p.string('monitored'), master_only)
    async def monitored(self, message):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            channels = [
                channel.name for channel in monitored
                if str(channel.server_id) == message.server.id
            ]

            await self.send_message(
                message.channel,
                'I\'m currently monitoring the following channels:\n{}'
                    .format(f.code_block(channels)),
                delete_after=15
            )

    '''
    Details
    '''

    CHANNELS_INFO_REFRESH_INTERVAL = 60 * 10
    async def refresh_channels_info_forever(self):
        while True:
            await asyncio.sleep(Twitch.CHANNELS_INFO_REFRESH_INTERVAL)
            for channel_name in self.channels_info:
                await self.refresh_channel_info(channel_name)

    async def refresh_channel_info(self, channel_name):
        channel = await self.api.channel(channel_name)
        self.channels_info[channel_name] = channel

    async def monitor_forever(self, channel_name, server):
        self.info('Monitoring: {}'.format(channel_name))

        await self.refresh_channel_info(channel_name)
        events = await self.pubsub.subscribe(
            PubSub.Topics.VIDEO_PLAYBACK(channel_name),
            server.id
        )

        async for event in events:
            channel = self.channels_info[channel_name]
            if event['type'] == 'stream-up':
                users = self.users_to_mention(channel.name, server)
                mentions = ' '.join(f.mention(user_id) for user_id in users)
                await self.send_message(
                    server.default_channel, '{}\nWake up!'.format(mentions),
                    embed = twitch_alert_embed(channel, True)
                )

            if event['type'] == 'stream-down':
                await self.send_message(
                    server.default_channel,
                    '`{}` just went offline 😢'.format(channel.display_name)
                )

        self.info('Stopped monitoring: {}'.format(channel_name))

    async def whisper_monitor_forever(self, channel_name, user):
        await self.refresh_channel_info(channel_name)

        events = await self.pubsub.subscribe(
            PubSub.Topics.VIDEO_PLAYBACK(channel_name),
            user.id
        )

        async for event in events:
            if event['type'] == 'stream-up':
                await self.send_message(
                    user, 'Just a heads up',
                    embed = twitch_alert_embed(self.channels_info[channel_name])
                )

    def users_to_mention(self, channel_name, server):
        with self.transaction() as trans:
            trans.execute(q.get_subscribed_users, dict(
                channel = channel_name,
                method = 'mention'
            ))

            return [
                user_id for user_id, in trans.fetchall()
                if server in self.bot.servers_of(
                    self.bot.find_user(str(user_id))
                )
            ]

        return []

    def users_to_whisper(self, channel_name):
        with self.transaction() as trans:
            trans.execute(q.get_subscribed_users, dict(
                channel = channel_name,
                method = 'whisper'
            ))

            return [user_id for user_id, in trans.fetchall()]

        return []

    def restore_monitored(self):
        with self.transaction() as trans:
            # Server monitors
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            for channel in monitored:
                server = next(
                    serv for serv in self.bot.servers
                    if serv.id == str(channel.server_id)
                )
                self.run_async(self.monitor_forever(channel.name, server))

            # Whispers
            trans.execute(q.get_all_notify_whispers)

            for user_id, channel_name in trans.fetchall():
                user = self.bot.find_user(str(user_id))
                if user:
                    self.run_async(self.whisper_monitor_forever(channel_name, user))

    def request_token_state(self, user):
        state = ''.join(
            random.choice(string.ascii_uppercase + string.digits)
            for _ in range(32)
        )

        self.token_states[user.id] = state

        return state

    async def save_user(self, user, code, state):
        try:
            reference_state = self.token_states[user.id]
        except KeyError:
            return
        else:
            if state != reference_state:
                return

            token = await self.access_token(code, state)

            with self.transaction() as trans:
                trans.execute(q.add_user, dict(
                    id = user.id,
                    token = token
                ))

    TOKEN_URL = 'https://api.twitch.tv/kraken/oauth2/token'
    REDIRECT_URI = 'https://globibot.com/bot/twitch/authorize'
    async def access_token(self, code, state):
        client = AsyncHTTPClient()

        payload = (
            ('client_id', self.client_id),
            ('client_secret', self.client_secret),
            ('grant_type', 'authorization_code'),
            ('redirect_uri', Twitch.REDIRECT_URI),
            ('code', code),
            ('state', state),
        )

        url = Twitch.TOKEN_URL
        request = HTTPRequest(
            url = url,
            method = 'POST',
            body = urlencode(payload)
        )
        tornado_future = client.fetch(request)
        future = to_asyncio_future(tornado_future)
        response = await future

        data = json_decode(response.body)
        return data['access_token']

    def disconnect_user(self, user):
        with self.transaction() as trans:
            trans.execute(q.delete_user, dict(
                id = user.id
            ))

    def user_connected(self, user):
        with self.transaction() as trans:
            trans.execute(q.get_user, dict(id=user.id))

            if trans.fetchone():
                return True

        return False

    async def user_followed(self, user):
        token = self.get_user_token(user)

        followed = await self.api.user_followed(token)

        user_servers = self.bot.servers_of(user)
        with self.transaction() as trans:
            trans.execute(q.get_monitored_names, dict(
                server_ids = tuple(server.id for server in user_servers)
            ))

            monitored_names = set(name for name, in trans.fetchall())

            trans.execute(q.get_notified, dict(
                id = user.id,
            ))

            notifieds = [NotifiedChannel(*row) for row in trans.fetchall()]
            whipered = [
                notified.name for notified in notifieds
                if notified.method == 'whisper'
            ]
            mentionned = [
                notified.name for notified in notifieds
                if notified.method == 'mention'
            ]

            followed_channels = [ChannelState(f.channel.name, f.channel.name in whipered) for f in followed]
            monitored_channels = [ChannelState(name, name in mentionned) for name in monitored_names]

            return (
                followed_channels,
                monitored_channels
            )

        return ([], [])

    def get_user_token(self, user):
        with self.transaction() as trans:
            trans.execute(q.get_user, dict(id=user.id))

            return trans.fetchone()[0]

    def user_notify_mention(self, user, channel, state):
        payload = dict(
            id = user.id,
            channel = channel,
            method = 'mention'
        )

        query = q.user_notify_add if state else q.user_notify_remove

        with self.transaction() as trans:
            trans.execute(query, payload)

    async def user_notify_whisper(self, user, channel, state):
        payload = dict(
            id = user.id,
            channel = channel,
            method = 'whisper'
        )
        query = q.user_notify_add if state else q.user_notify_remove

        with self.transaction() as trans:
            trans.execute(query, payload)

            if state:
                self.run_async(self.whisper_monitor_forever(channel, user))
            else:
                await self.pubsub.unsubscribe(
                    PubSub.Topics.VIDEO_PLAYBACK(channel),
                    user.id
                )
Exemplo n.º 11
0
class CryptoCurrency(Plugin):
    def load(self):
        self.api = CryptoApi()
        self.currencies = dict()
        self.markets_per_coin_pairs = dict()

        self.run_async(self.fetch_all_currencies())
        self.run_async(self.fetch_market_coin_pairs())

    async def fetch_all_currencies(self):
        all_tickers = await self.api.all_tickers()

        for ticker in all_tickers:
            id_, name, symbol = ticker['id'], ticker['name'], ticker['symbol']
            self.currencies[name.lower()] = id_
            self.currencies[symbol.lower()] = id_
            self.currencies[id_.lower()] = id_

    async def fetch_market_coin_pairs(self):
        while True:
            self.markets_per_coin_pairs = await self.api.markets_per_coin_pairs(
            )
            await asyncio.sleep(12 * 3600)

    @command(
        p.string('!price-old') + p.bind(p.word, 'currency') +
        p.bind(p.maybe(p.word), 'exchange'))
    async def crypto(self, message, currency, exchange=None):
        if currency.lower() not in self.currencies:
            await self.send_message(
                message.channel,
                '{} I did not find any cryptocurrency named **{}**'.format(
                    message.author.mention, currency))
        elif exchange is not None and exchange.lower() not in EXCHANGES:
            await self.send_message(
                message.channel,
                '{} I cannot convert to **{}**'.format(message.author.mention,
                                                       exchange))
        else:
            currency_id = self.currencies[currency.lower()]
            exchange = exchange.lower() if exchange is not None else None

            try:
                tickers = await self.api.ticker(currency_id, exchange)
                ticker = tickers[0]
            except:
                await self.send_message(
                    message.channel,
                    '{} there was an error contacting __coinmarketcap__'.
                    format(message.author.mention))
            else:
                await self.send_message(message.channel,
                                        format_price(ticker, exchange))

    @command(
        p.string('!price') + p.bind(p.word, 'currency') +
        p.bind(p.maybe(p.word), 'exchange'))
    async def crypto(self, message, currency, exchange='EUR'):
        if currency.lower() not in self.currencies:
            await self.send_message(
                message.channel,
                '{} I did not find any cryptocurrency named **{}**'.format(
                    message.author.mention, currency))
        elif exchange is not None and exchange.lower() not in EXCHANGES:
            await self.send_message(
                message.channel,
                '{} I cannot convert to **{}**'.format(message.author.mention,
                                                       exchange))
        else:
            currency_id = self.currencies[currency.lower()]
            exchange = exchange.lower() if exchange is not None else None

            try:
                tickers = await self.api.ticker(currency_id, exchange)
                ticker = tickers[0]
            except:
                await self.send_message(
                    message.channel,
                    '{} there was an error contacting __coinmarketcap__'.
                    format(message.author.mention))
            else:
                await self.send_message(message.channel,
                                        '',
                                        embed=price_embed(ticker, exchange))

    @command(
        p.string('!chart') + p.bind(p.word, 'quote') + p.bind(p.word, 'base') +
        p.bind(p.integer, 'span_factor') + p.bind(SPAN_UNIT, 'span_unit'))
    async def chart(self, message, quote, base, span_factor, span_unit):
        quote = quote.upper()
        base = base.upper()
        span = span_factor * UNIT_TIMES[span_unit]

        m = await self.send_message(message.channel, 'Generating the chart...')

        try:
            true_base = 'BTC' if base == 'USD' else base
            market = self.markets_per_coin_pairs[(quote, true_base)]
        except KeyError:
            await self.send_message(
                message.channel,
                'I don\'t aggregate any market that has that data')
        else:
            try:
                data = await self.api.candle_data(market, quote, true_base,
                                                  span)
            except Exception as e:
                self.error(format_exc(e))
                await self.send_message(
                    message.channel,
                    'Error fetching data from {}'.format(market.name))
            else:
                try:
                    if base == 'USD':
                        tickers = await self.api.ticker(
                            self.currencies['btc'], None)
                        ticker = tickers[0]
                        price = float(ticker['price_usd'])
                        data = [[i[0], *(x * price for x in i[1:])]
                                for i in data]
                except Exception as e:
                    self.error(format_exc(e))
                    await self.send_message(message.channel,
                                            'Error converting BTC to USD')
                chart = make_chart(quote, base, market, span_factor, span_unit,
                                   data)
                await self.send_file(message.channel, chart)
        finally:
            await self.bot.delete_message(m)
Exemplo n.º 12
0
class Images(Plugin):

    # def load(self):


    GUN_URL = 'https://dl.dropboxusercontent.com/u/3427186/3071dbc60204c84ca0cf423b8b08a204.png'
    @command(p.bind(p.mention, 'user_id') + p.string('🔫'), master_only)
    async def kill_user(self, message, user_id):
        member = message.server.get_member(str(user_id))
        if member is None:
            return

        client = AsyncHTTPClient()

        tornado_future = client.fetch(member.avatar_url)
        future = to_asyncio_future(tornado_future)
        response = await future

        with open('/tmp/plop.png', 'wb') as f:
            f.write(response.body)

        tornado_future = client.fetch(Images.GUN_URL)
        future = to_asyncio_future(tornado_future)
        response = await future

        with open('/tmp/gun.svg', 'wb') as f:
            f.write(response.body)

        avatar = Image.open('/tmp/plop.png')
        gun = Image.open('/tmp/gun.svg')
        gun.resize(avatar.size)

        total = Image.new('RGB', (avatar.width * 2, avatar.height))

        total.paste(avatar, (0, 0))
        total.paste(gun, (avatar.width, 0))

        total.save('/tmp/lol.png')

        await self.send_file(
            message.channel,
            '/tmp/lol.png',
            delete_after = 30,
            content = '{} is now dead 👍'.format(member.mention)
        )

    MISTAKE_URL = 'http://i.imgur.com/3nR5fDf.png'
    @command(p.string('!mistake') + p.bind(p.mention, 'user_id'), master_only)
    async def mistake_user(self, message, user_id):
        member = message.server.get_member(str(user_id))
        if member is None:
            return

        client = AsyncHTTPClient()

        tornado_future = client.fetch(member.avatar_url)
        future = to_asyncio_future(tornado_future)
        response = await future

        with open('/tmp/plop.png', 'wb') as f:
            f.write(response.body)

        tornado_future = client.fetch(Images.MISTAKE_URL)
        future = to_asyncio_future(tornado_future)
        response = await future

        with open('/tmp/mistake.png', 'wb') as f:
            f.write(response.body)

        avatar = Image.open('/tmp/plop.png')
        mistake = Image.open('/tmp/mistake.png')
        avatar = avatar.resize((505, 557))

        mistake.paste(avatar, (mistake.width - 505, mistake.height - 557))
        mistake.save('/tmp/lol.png')

        await self.send_file(
            message.channel,
            '/tmp/lol.png',
            delete_after = 30,
        )

    @command(p.string('!mistake') + p.bind(p.word, 'url'))
    async def mistake_url(self, message, url):
        client = AsyncHTTPClient()

        try:
            tornado_future = client.fetch(url)
            future = to_asyncio_future(tornado_future)
            response = await future

            with open('/tmp/plop.png', 'wb') as f:
                f.write(response.body)
        except:
            return

        tornado_future = client.fetch(Images.MISTAKE_URL)
        future = to_asyncio_future(tornado_future)
        response = await future

        with open('/tmp/mistake.png', 'wb') as f:
            f.write(response.body)

        img = Image.open('/tmp/plop.png')
        mistake = Image.open('/tmp/mistake.png')
        img = img.resize((505, 557))

        mistake.paste(img, (mistake.width - 505, mistake.height - 557))
        mistake.save('/tmp/lol.png')

        await self.send_file(
            message.channel,
            '/tmp/lol.png',
            delete_after = 30,
        )
Exemplo n.º 13
0
class Utils(Plugin):

    PREFIXES = ['!', '-', '/']

    prefixed = lambda s: p.one_of(p.string, *[
        '{}{}'.format(prefix, s)
        for prefix in Utils.PREFIXES
    ]).named('[{}]{}'.format(''.join(Utils.PREFIXES), s)) >> p.to_s

    @command(p.bind(p.oneplus(p.sparsed(unit_value_parser)), 'unit_values'), master_only)
    async def convert(self, message, unit_values):
        converted = [(uv, system_convert(uv)) for uv in unit_values]
        output = ['{} = {}'.format(uv, conv) for uv, conv in converted]

        for t, uvs in groupby(converted, key=lambda uvs: type(uvs[0].unit)):
            values = list(map(lambda x: x[0], uvs))

            if len(values) >= 2:
                summed = sum_units(*values)
                converted_summed = system_convert(summed)
                output.append(
                    '{} total: {} = {}'
                        .format(t.__name__.lower(), summed, converted_summed)
                )

        await self.send_message(
            message.channel,
            'Converted units\n{}'
                .format(f.code_block(output)),
            delete_after = 60
        )

    @command(p.string('!user') + p.bind(p.mention, 'user'))
    async def user_id(self, message, user):
        await self.send_message(
            message.channel,
            '{}'.format(user),
            delete_after=15
        )

    @command(
        p.string('!master') + p.string('add') + p.bind(p.mention, 'user'),
        master_only
    )
    async def add_master(self, message, user):
        self.bot.masters.append(str(user))

        await self.send_message(
            message.channel,
            '{} can now use master commands'.format(f.mention(user)),
            delete_after=20
        )

    @command(p.string('!master') + p.string('remove') + p.bind(p.mention, 'user'))
    async def remove_master(self, message, user):
        if message.author.id != '89108411861467136':
            return

        self.bot.masters.remove(str(user))

        await self.send_message(
            message.channel,
            '{} can no longer use master commands'.format(f.mention(user)),
            delete_after=20
        )

    @command(p.string('!user') + p.bind(p.integer, 'user_id'))
    async def user(self, message, user_id):
        await self.send_message(
            message.channel,
            f.mention(user_id),
            delete_after=15
        )

    @command(p.string('!channel') + p.bind(p.channel, 'channel'))
    async def channel_id(self, message, channel):
        await self.send_message(
            message.channel,
            '{}'.format(channel),
            delete_after=15
        )

    @command(p.string('!channel') + p.bind(p.integer, 'channel_id'))
    async def channel(self, message, channel_id):
        await self.send_message(
            message.channel,
            f.channel(channel_id),
            delete_after=15
        )

    @command(p.string('!server') + p.eof)
    async def server_id(self, message):
        await self.send_message(
            message.channel,
            '{}'.format(message.server.id),
            delete_after=15
        )

    @command(
        p.string('!block') + p.bind(p.mention, 'who'),
        master_only
    )
    async def block(self, message, who):
        # TODO: Actually block that bitch 😂
        await self.send_message(
            message.channel,
            '{} is now blocked 👍'.format(f.mention(who))
        )

    @command(
        p.string('!unsafe') + p.bind(p.snippet, 'snippet'),
        master_only
    )
    async def sql_exec(self, message, snippet):
        if snippet.language.lower() != 'sql':
            return

        try:
            with self.transaction() as trans:
                trans.execute(snippet.code)
                rows = trans.fetchall()
                self.debug(rows)
                if rows:
                    text = f.code_block(f.format_sql_rows(rows))
                else:
                    text = 'Request executed successfully\n`no output`'
                await self.send_message(message.channel, text)
        except Exception as e:
            await self.send_message(message.channel, '```\n{}\n```'.format(e))
Exemplo n.º 14
0
class Meta(Plugin):

    @command(
        p.string('!permissions') + p.bind(p.maybe(p.channel), 'channel_id'),
        master_only
    )
    async def permissions(self, message, channel_id=None):
        if channel_id is None:
            channel_id = message.channel.id

        channel = next(
            channel for channel in message.server.channels
            if channel.id == str(channel_id)
        )

        perms = [' - {}'.format(perm) for perm in self.permissions_in(channel)]

        await self.send_message(
            message.channel,
            'In {}, I can:\n{}'
                .format(f.channel(channel_id), f.code_block(perms)),
            delete_after = 30
        )

    @command(p.string('!channels'))
    async def channels(self, message):
        channels = [
            (channel.name, channel.id)
            for channel in message.server.channels
        ]

        await self.send_message(
            message.channel,
            f.code_block(f.format_sql_rows(channels)),
            delete_after = 30
        )

    @command(
        p.string('!where-can-you') + p.bind(p.word, 'what'), master_only
    )
    async def where_can_you(self, message, what):
        perm_name = PERMISSION_NAMES[what]
        permissions = [
            (channel, self.permissions_in(channel))
            for channel in message.server.channels
        ]

        where = [
            channel.id for channel, perms in permissions
            if perm_name in perms and channel.type == ChannelType.text
        ]

        await self.send_message(
            message.channel,
            'On this server, I can `{}` {}'
                .format(
                    PERMISSION_NAMES[what],
                    ('in ' + ' '.join(map(f.channel, where))) if where else '`nowhere`'
                ),
            delete_after = 30
        )

    @command(
        p.string('!test'), master_only
    )
    async def test(self, message):
        await self.bot.delete_message(message)

    def permissions_in(self, channel):
        member = channel.server.get_member(self.bot.user.id)

        standard_perms = channel.permissions_for(member)
        overwrites = [channel.overwrites_for(role) for role in member.roles]

        sets = [permission_names(perms) for perms in [standard_perms] + overwrites]

        return reduce(set.union, sets)
Exemplo n.º 15
0
class Eval(Plugin):
    def load(self):
        self.docker = docker.from_env()

        self.container_logs = defaultdict(list)

        context = dict(plugin=self)
        self.add_web_handlers((r'/eval/logs/(?P<container_id>\S+)',
                               ContainerLogHandler, context), )

    '''
    Commands
    '''

    @command(p.string('!eval') + p.string('langs'))
    async def eval_langs(self, message):
        await self.send_message(
            message.channel,
            f.code_block('\n'.join(
                f'{lang: <10} -> {spec.image: <15} {spec.command("file.ext")}'
                for lang, spec in EVALSPEC_PER_LANGUAGES.items())))

    @command(p.bind(p.sparsed(p.snippet), 'snippet'))
    async def on_snippet(self, message, snippet):
        try:
            spec = EVALSPEC_PER_ALIASES[snippet.language]
        except KeyError:
            pass
        else:
            query = self.query_yes_no(
                f'{message.author.mention} Run this code with `{spec.image}` ?',
                channel=message.channel,
                author=message.author,
                timeout=20)
            if await query:
                await self.eval(send_message=partial(self.send_message,
                                                     message.channel),
                                user_key=message.author.id,
                                code=snippet.code,
                                spec=spec)

    '''
    Detail
    '''

    async def eval(self, send_message, user_key, code, spec):
        host_dir = os.path.join(C.HOST_MOUNT_DIR, user_key)
        mount_dir = os.path.join(C.DIND_MOUNT_DIR, user_key)
        user_volume_name = C.USER_STORAGE_VOLUME_NAME(user_key)

        file_name = await spec.prepare(host_dir, code)
        file_path = os.path.join(mount_dir, file_name)
        await self.create_volume(user_volume_name)

        try:
            container = self.docker.containers.run(
                image=spec.image,
                command=spec.command(file_path),
                **C.CONTAINER_OPTS(mount_dir, user_volume_name))
        except docker.errors.APIError as e:
            await send_message(
                f'Error while creating container: {f.code_block(e)}\n',
                delete_after=20)
        else:
            logs = self.container_logs[container.id]
            stream = container.logs(**C.CONTAINER_LOG_OPTS)
            logs_url = f'https://globibot.com/bot/eval/logs/{container.id}'
            await send_message(f'Running... Full logs available at {logs_url}')

            streaming_message = await send_message('Waiting for output...')
            self.run_async(self.stream_logs(streaming_message, logs, stream))

            status = await self.wait_for_container(container)
            container.remove(force=True)

            final_notice = (f'Container exited with status {status}'
                            if status is not None else
                            f'Container killed after {C.MAX_RUN_TIME} seconds')
            await send_message(final_notice, delete_after=20)

    async def create_volume(self, volume_name):
        try:
            self.docker.volumes.get(volume_name)
        except docker.errors.NotFound:
            self.info(f'Creating volume: {volume_name}')
            self.docker.volumes.create(volume_name,
                                       driver_opts=C.VOLUME_DRIVER_OPTS)

    async def stream_logs(self, message, logs, stream):
        async def send_update():
            if logs:
                tail = ''.join(logs[-C.MAX_STREAM_LINES:])
                await self.bot.edit_message(message, f.code_block(tail))

        def accumulate_logs():
            try:
                for line in stream:
                    logs.append(line.decode())
            except docker.errors.APIError as e:
                pass

        async def update_logs():
            try:
                while True:
                    await send_update()
                    await asyncio.sleep(C.STREAM_REFRESH_IMTERVAL)
            except asyncio.CancelledError:
                await send_update()
                if not logs:
                    await self.bot.edit_message(message, 'No output')

        loop = asyncio.get_event_loop()
        update_task = asyncio.ensure_future(update_logs(), loop=loop)
        await loop.run_in_executor(None, accumulate_logs)
        update_task.cancel()

    async def wait_for_container(self, container):
        loop = asyncio.get_event_loop()
        call = partial(container.wait, timeout=C.MAX_RUN_TIME)
        try:
            return await loop.run_in_executor(None, call)
        except:
            return None

    async def query_yes_no(self, question, channel, **kwargs):
        question_message = await self.send_message(channel,
                                                   f'{question}\n[y]es/[n]o')
        answer_message = await self.bot.wait_for_message(channel=channel,
                                                         **kwargs)
        await self.bot.delete_message(question_message)

        if answer_message is not None:
            self.run_async(self.delete_message_after(answer_message, 1))
            return answer_message.content.lower().startswith('y')
        else:
            return False
Exemplo n.º 16
0
class Twitter(Plugin):
    def load(self):
        self.oauth = OAuth(
            self.config.get(c.ACCESS_TOKEN_KEY),
            self.config.get(c.ACCESS_TOKEN_SECRET_KEY),
            self.config.get(c.CONSUMER_KEY_KEY),
            self.config.get(c.CONSUMER_SECRET_KEY),
        )

        self.client = TwitterAPI(auth=self.oauth)

        context = dict(plugin=self, bot=self.bot)
        self.add_web_handlers(
            (r'/twitter/oauth_token', OAuthTokenHandler, context),
            (r'/twitter/authorize', OAuthAuthorizeHandler, context),
            (r'/twitter/status', TwitterStatusHandler, context),
            (r'/twitter/disconnect', TwitterDisconnectHandler, context),
        )

        self.monitored = set()
        self.restore_monitored()

        self.request_tokens = dict()
        self.last_tweets = dict()

        self.interactive_tweets = dict()

    def unload(self):
        self.monitored.clear()

    async def on_reaction_add(self, reaction, user):
        try:
            tweet = self.interactive_tweets[reaction.message.id]
        except KeyError:
            pass
        else:
            if reaction.emoji == '❤':
                await self.like_tweet(tweet, reaction.message.channel, user)
            elif reaction.emoji == '🔄':
                await self.rt_tweet(tweet, reaction.message.channel, user)

    async def on_reaction_remove(self, reaction, user):
        try:
            tweet = self.interactive_tweets[reaction.message.id]
        except KeyError:
            pass
        else:
            if reaction.emoji == '❤':
                await self.unlike_tweet(tweet, reaction.message.channel, user)
            elif reaction.emoji == '🔄':
                await self.unrt_tweet(tweet, reaction.message.channel, user)

    async def on_new(self, message):
        for match in TWITTER_STATUS_PATTERN.finditer(message.clean_content):
            status_id = match.group('status_id')
            tweet = self.client.statuses.show(id=status_id)
            if tweet:
                await asyncio.sleep(2)
                await self.set_interactive_tweet(tweet, message)

    '''
    Commands
    '''

    twitter_prefix = p.string('!twitter')

    @command(twitter_prefix + p.string('last') + p.bind(p.word, 'screen_name'),
             master_only)
    async def last_tweet(self, message, screen_name):
        user = self.get_user(screen_name=screen_name)
        tweets = self.get_tweets(user['id'], count=1)

        if tweets:
            tweet = tweets[0]
            m = await self.send_interactive_tweet(message.channel,
                                                  tweet,
                                                  delete_after=150)
            self.last_tweets[message.channel.id] = tweet
            self.run_async(self.update_tweet(tweet, m))

    @command(twitter_prefix + p.string('monitor') +
             p.bind(p.word, 'screen_name'), master_only)
    async def monitor(self, message, screen_name):
        user = self.get_user(screen_name=screen_name)
        user_id = user['id']

        with self.transaction() as trans:
            trans.execute(
                q.add_monitored,
                dict(user_id=user_id,
                     server_id=message.server.id,
                     channel_id=message.channel.id))

            self.run_async(self.monitor_forever(user_id, message.channel))

            await self.send_message(
                message.channel,
                'Now monitoring `{}` tweets in this channel'.format(
                    screen_name),
                delete_after=15)

    @command(twitter_prefix + p.string('unmonitor') +
             p.bind(p.word, 'screen_name'), master_only)
    async def unmonitor(self, message, screen_name):
        user = self.get_user(screen_name=screen_name)
        user_id = user['id']

        if user_id not in self.monitored:
            return

        self.monitored.discard(user_id)

        with self.transaction() as trans:
            trans.execute(q.remove_monitored,
                          dict(user_id=user_id, server_id=message.server.id))

        await self.send_message(message.channel,
                                'Stopped monitoring `{}`'.format(screen_name),
                                delete_after=15)

    @command(twitter_prefix + p.string('monitored'), master_only)
    async def monitored(self, message):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            users = [
                self.get_user_by_id(channel.user_id)['screen_name']
                for channel in monitored
                if str(channel.server_id) == message.server.id
            ]

            await self.send_message(
                message.channel,
                'I\'m currently monitoring the following channels:\n{}'.format(
                    f.code_block(users)))

    @command(p.string('!like'))
    async def like_tweet_command(self, message):
        try:
            tweet = self.last_tweets[message.channel.id]
        except KeyError:
            pass
        else:
            await self.like_tweet(tweet, message.channel, message.author)

    @command(p.string('!unlike'))
    async def unlike_tweet_command(self, message):
        try:
            tweet = self.last_tweets[message.channel.id]
        except KeyError:
            pass
        else:
            await self.unlike_tweet(tweet, message.channel, message.author)

    @command(p.string('!rt'))
    async def rt_tweet_command(self, message):
        try:
            tweet = self.last_tweets[message.channel.id]
        except KeyError:
            pass
        else:
            await self.rt_tweet(tweet, message.channel, message.author)

    @command(p.string('!unrt'))
    async def unrt_tweet_command(self, message):
        try:
            tweet = self.last_tweets[message.channel.id]
        except KeyError:
            pass
        else:
            await self.unrt_tweet(tweet, message.channel, message.author)

    @command(p.string('!reply'))
    async def reply_tweet(self, message):
        try:
            tweet = self.last_tweets[message.channel.id]
        except KeyError:
            pass
        else:
            # Replace emojis to avoid weird moon runes
            reply = re.sub(r'<:(.*):[0-9]+>', r'\1',
                           message.clean_content[len('!reply'):].strip())

            await self.twitter_three_legged_action(
                tweet,
                message.channel,
                message.author,
                lambda api, tweet: api.statuses.update(status='@{} {}'.format(
                    tweet['user']['screen_name'], reply),
                                                       in_reply_to_status_id=
                                                       tweet['id'],
                                                       _method='POST'),
                'reply to',
            )

    '''
    Details
    '''

    def get_user(self, screen_name):
        try:
            return self.client.users.lookup(screen_name=screen_name)[0]
        except Exception:
            raise e.UserNotFound(screen_name)

    def get_user_by_id(self, user_id):
        return self.client.users.lookup(user_id=user_id)[0]

    def get_tweets(self, user_id, count):
        try:
            return self.client.statuses.user_timeline(user_id=user_id,
                                                      count=count,
                                                      exclude_replies=True)
        except:
            return None

    def restore_monitored(self):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            for channel in monitored:
                server = next(serv for serv in self.bot.servers
                              if serv.id == str(channel.server_id))
                self.run_async(
                    self.monitor_forever(
                        channel.user_id,
                        server.get_channel(str(channel.channel_id))))

    async def monitor_forever(self, user_id, channel):
        self.monitored.add(user_id)

        tweets = self.get_tweets(user_id, count=1)
        if not tweets:
            return
        last_tweet = tweets[0]

        self.debug('Monitoring new tweets from {}'.format(user_id))

        while user_id in self.monitored:
            await asyncio.sleep(45)

            tweets = self.get_tweets(user_id, count=1)
            if tweets:
                latest = tweets[0]
                if latest['id'] != last_tweet['id']:
                    if tweet_time(latest) > tweet_time(last_tweet):
                        last_tweet = latest
                        m = await self.send_interactive_tweet(channel, latest)
                        self.last_tweets[channel.id] = latest
                        self.run_async(self.update_tweet(latest, m))

        self.debug('No longer monitoring {}'.format(user_id))

    async def send_interactive_tweet(self, channel, tweet, **kwargs):
        message = await self.send_message(channel,
                                          '',
                                          embed=self.tweet_embed(tweet),
                                          **kwargs)

        await self.set_interactive_tweet(tweet, message)

        return message

    async def set_interactive_tweet(self, tweet, message):
        await self.bot.add_reaction(message, '🔄')
        await self.bot.add_reaction(message, '❤')

        self.interactive_tweets[message.id] = tweet

    async def update_tweet(self, tweet, message):
        for _ in range(10):
            await asyncio.sleep(20)
            try:
                tweet = self.client.statuses.show(id=tweet['id'])
                await self.bot.edit_message(message,
                                            '',
                                            embed=self.tweet_embed(tweet))
            except Exception as e:
                self.error(e)
                pass

    AUTHORIZE_CALLBACK = 'https://globibot.com/bot/twitter/authorize'

    def request_token(self, user):
        tweaked_twitter_client = TwitterAPI(auth=self.oauth,
                                            format='',
                                            api_version=None)

        try:
            response = tweaked_twitter_client.oauth.request_token(
                oauth_callback=Twitter.AUTHORIZE_CALLBACK)
        except Exception as e:
            self.error('Error while generating oauth token: {}'.format(e))
        else:
            params = dict(
                (key, value)
                for key, value in (component.split('=')
                                   for component in response.split('&')))

            try:
                token = params['oauth_token']
                secret = params['oauth_token_secret']
            except:
                return
            else:
                self.info('Generated request token for {}'.format(user.name))
                self.request_tokens[token] = secret
                return token

    def save_user(self, user, oauth_token, oauth_verifier):
        oauth = OAuth(
            oauth_token,
            self.request_tokens[oauth_token],
            self.config.get(c.CONSUMER_KEY_KEY),
            self.config.get(c.CONSUMER_SECRET_KEY),
        )
        tweaked_twitter_client = TwitterAPI(auth=oauth,
                                            format='',
                                            api_version=None)

        try:
            response = tweaked_twitter_client.oauth.access_token(
                oauth_verifier=oauth_verifier)
        except Exception as e:
            self.error(e)
            pass
        else:
            params = dict(
                (key, value)
                for key, value in (component.split('=')
                                   for component in response.split('&')))

            with self.transaction() as trans:
                trans.execute(
                    q.add_user,
                    dict(id=user.id,
                         token=params['oauth_token'],
                         secret=params['oauth_token_secret']))

    def disconnect_user(self, user):
        with self.transaction() as trans:
            trans.execute(q.delete_user, dict(id=user.id))

    def user_connected(self, user):
        with self.transaction() as trans:
            trans.execute(q.get_user, dict(id=user.id))

            if trans.fetchone():
                return True

        return False

    def get_user_oauth(self, user):
        with self.transaction() as trans:
            trans.execute(q.get_user, dict(id=user.id))

            data = trans.fetchone()
            if data:
                return OAuthUser(*data)

    def get_user_api(self, oauth_user):
        oauth = OAuth(
            oauth_user.token,
            oauth_user.secret,
            self.config.get(c.CONSUMER_KEY_KEY),
            self.config.get(c.CONSUMER_SECRET_KEY),
        )

        return TwitterAPI(auth=oauth)

    async def like_tweet(self, tweet, channel, user):
        await self.twitter_three_legged_action(
            tweet,
            channel,
            user,
            lambda api, tweet: api.favorites.create(_id=tweet['id']),
            'like',
        )

    async def unlike_tweet(self, tweet, channel, user):
        await self.twitter_three_legged_action(
            tweet,
            channel,
            user,
            lambda api, tweet: api.favorites.destroy(_id=tweet['id']),
            'unlike',
        )

    async def rt_tweet(self, tweet, channel, user):
        await self.twitter_three_legged_action(
            tweet,
            channel,
            user,
            lambda api, tweet: api.statuses.retweet(id=tweet['id'],
                                                    _method='POST'),
            'retweet',
        )

    async def unrt_tweet(self, tweet, channel, user):
        await self.twitter_three_legged_action(
            tweet,
            channel,
            user,
            lambda api, tweet: api.statuses.unretweet(id=tweet['id'],
                                                      _method='POST'),
            'unretweet',
        )

    async def inform_user_about_connections(self, user):
        await self.send_message(
            user,
            'Psst\nI noticed that you tried using a special Twitter command\n'
            'This command requires me to manipulate a limited scope of '
            'your Twitter account\n'
            'If you trust me enough to do so, please go to '
            'https://globibot.com/#connections to connect your Twitter acount')

    async def twitter_three_legged_action(self, tweet, channel, user, action,
                                          description):
        oauth_user = self.get_user_oauth(user)

        if oauth_user is None:
            await self.inform_user_about_connections(user)
            return

        user_api = self.get_user_api(oauth_user)

        try:
            action(user_api, tweet)
        except Exception as e:
            await self.send_message(
                user, 'I couldn\'t {} `{}`\'s tweet for you\n'
                'Twitter said:\n{}'.format(description,
                                           tweet['user']['screen_name'],
                                           f.code_block(str(e.response_data))))
        else:
            await self.send_message(channel,
                                    '{} I {} `{}`\'s tweet for you 👍'.format(
                                        user.mention, PAST_FORMS[description],
                                        tweet['user']['screen_name']),
                                    delete_after=5)

    def replies_to_tweet(self, tweet):
        screen_name = tweet['user']['screen_name']
        query = '@{}'.format(screen_name)

        try:
            result = self.client.search.tweets(q=query, since_id=tweet['id'])
            statuses = result['statuses']
        except Exception as e:
            self.debug('Error fetching replies: {}'.format(e))
            return []
        else:
            return [
                status for status in statuses
                if status['in_reply_to_status_id'] == tweet['id']
            ]

    def tweet_embed(self, tweet):
        name = tweet['user']['name']
        screen_name = tweet['user']['screen_name']

        body = '{text}\n\n🔄 **{rts}** ❤ **{likes}**'.format(
            text=tweet['text'],
            rts=tweet['retweet_count'],
            likes=tweet_favorite_count(tweet))
        embed = Embed(
            title='Latest tweet',
            description=body,
            url='https://twitter.com/{}/status/{}'.format(
                screen_name, tweet['id']),
        )
        embed.set_author(name=name,
                         icon_url='https://twitter.com/favicon.ico',
                         url='https://twitter.com/{}'.format(screen_name))
        embed.set_thumbnail(url=tweet['user']['profile_image_url_https'])
        embed.colour = 0x1da1f2
        embed.set_footer(text='Click the reactions below to like or retweet')
        embed.timestamp = tweet_time(tweet)

        try:
            entities = tweet['extended_entities']['media']
            media_urls = [entity['media_url_https'] for entity in entities]
        except:
            pass
        else:
            embed.set_image(url=media_urls[0])

        replies = self.replies_to_tweet(tweet)
        if replies:
            embed.add_field(name='Latest replies',
                            value='\n'.join(
                                format_reply(reply, tweet)
                                for reply in replies[:3]))

        return embed
Exemplo n.º 17
0
class Help(Plugin):
    def load(self):
        self.enabled_plugins_names = self.config.get(c.ENABLED_PLUGINS_KEY, [])

    '''
    Commands
    '''

    help_prefix = p.string('!help')

    @command(help_prefix + p.eof)
    async def general_help(self, message):
        help_message = f.code_block(
            ['!help {}'.format(name) for name in self.enabled_plugins_names],
            language='css'  # Kinda look ok
        )

        await self.send_message(message.channel,
                                '{}\n{}'.format(message.author.mention,
                                                help_message),
                                delete_after=30)

    @command(help_prefix + p.bind(p.word, 'plugin_name'))
    async def plugin_help(self, message, plugin_name):
        plugin_name = plugin_name.lower()

        if plugin_name not in self.enabled_plugins_names:
            return

        plugin = self.get_plugin(plugin_name)

        raw_components = [
            '{}{}'.format(
                validator.parser.name, ' /* master only */'
                if validator.pre_hook == master_only else '')
            for validator, _ in plugin.actions
        ]
        components = sorted(
            re.sub(' +', ' ', re.sub(r'[(,)]', '', name)).split(' ')
            for name in raw_components)

        lines = [' '.join(comp) for comp in components]

        help_message = f.code_block(lines, language='css')

        await self.send_message(message.channel,
                                '{} `{}` plugin commands:\n\n{}'.format(
                                    message.author.mention, plugin_name,
                                    help_message),
                                delete_after=30)

    '''
    Internals
    '''

    def get_plugin(self, name):
        return next(plugin for plugin in [
            reloader.plugin
            for reloader in self.bot.plugin_collection.plugin_reloaders
            if reloader.name == name
        ])
Exemplo n.º 18
0
class Twitter(Plugin):
    def load(self):
        self.oauth = OAuth(
            self.config.get(c.ACCESS_TOKEN_KEY),
            self.config.get(c.ACCESS_TOKEN_SECRET_KEY),
            self.config.get(c.CONSUMER_KEY_KEY),
            self.config.get(c.CONSUMER_SECRET_KEY),
        )

        self.client = TwitterAPI(auth=self.oauth)

        self.monitored = set()
        self.restore_monitored()

    def unload(self):
        self.monitored.clear()

    '''
    Commands
    '''

    twitter_prefix = p.string('!twitter')

    @command(twitter_prefix + p.string('last') + p.bind(p.word, 'screen_name'),
             master_only)
    async def last_tweet(self, message, screen_name):
        user = self.get_user(screen_name=screen_name)
        tweets = self.get_tweets(user['id'], count=1)

        if tweets:
            tweet = tweets[0]
            m = await self.send_message(message.channel,
                                        format_tweet(tweet),
                                        delete_after=60)
            self.run_async(self.update_tweet(tweet, m))

    @command(twitter_prefix + p.string('monitor') +
             p.bind(p.word, 'screen_name'), master_only)
    async def monitor(self, message, screen_name):
        user = self.get_user(screen_name=screen_name)
        user_id = user['id']

        with self.transaction() as trans:
            trans.execute(q.add_monitored,
                          dict(user_id=user_id, server_id=message.server.id))

            self.run_async(self.monitor_forever(user_id, message.server))

            await self.send_message(
                message.channel,
                'Now monitoring `{}` tweets'.format(screen_name),
                delete_after=15)

    @command(twitter_prefix + p.string('unmonitor') +
             p.bind(p.word, 'screen_name'), master_only)
    async def unmonitor(self, message, screen_name):
        user = self.get_user(screen_name=screen_name)
        user_id = user['id']

        if user_id not in self.monitored:
            return

        del self.monitored[user_id]

        with self.transaction() as trans:
            trans.execute(q.remove_monitored,
                          dict(user_id=user_id, server_id=message.server.id))

        await self.send_message(message.channel,
                                'Stopped monitoring `{}`'.format(screen_name),
                                delete_after=15)

    @command(twitter_prefix + p.string('monitored'), master_only)
    async def monitored(self, message):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            users = [
                self.get_user_by_id(channel.user_id)['screen_name']
                for channel in monitored
                if str(channel.server_id) == message.server.id
            ]

            await self.send_message(
                message.channel,
                'I\'m currently monitoring the following channels:\n{}'.format(
                    f.code_block(users)))

    '''
    Details
    '''

    def get_user(self, screen_name):
        try:
            return self.client.users.lookup(screen_name=screen_name)[0]
        except Exception:
            raise e.UserNotFound(screen_name)

    def get_user_by_id(self, user_id):
        return self.client.users.lookup(user_id=user_id)[0]

    def get_tweets(self, user_id, count):
        try:
            return self.client.statuses.user_timeline(user_id=user_id,
                                                      count=count,
                                                      exclude_replies=True)
        except:
            return None

    def restore_monitored(self):
        with self.transaction() as trans:
            trans.execute(q.get_monitored)
            monitored = [MonitoredChannel(*row) for row in trans.fetchall()]

            for channel in monitored:
                server = next(serv for serv in self.bot.servers
                              if serv.id == str(channel.server_id))
                self.run_async(self.monitor_forever(channel.user_id, server))

    async def monitor_forever(self, user_id, server):
        self.monitored.add(user_id)

        tweets = self.get_tweets(user_id, count=1)
        if not tweets:
            return
        last_tweet = tweets[0]

        self.debug('Monitoring new tweets from {}'.format(user_id))

        while user_id in self.monitored:
            await asyncio.sleep(45)

            tweets = self.get_tweets(user_id, count=1)
            if tweets:
                latest = tweets[0]
                if latest['id'] != last_tweet['id']:
                    if tweet_time(latest) > tweet_time(last_tweet):
                        last_tweet = latest
                        m = await self.send_message(server.default_channel,
                                                    format_tweet(latest))
                        self.run_async(self.update_tweet(latest, m))

        self.debug('No longer monitoring {}'.format(user_id))

    async def update_tweet(self, tweet, message):
        for i in range(24):
            await asyncio.sleep(5)
            try:
                tweet = self.client.statuses.show(id=tweet['id'])
                await self.edit_message(message, format_tweet(tweet))
            except Exception as e:
                self.error(e)
                pass
Exemplo n.º 19
0
class Reactions(Plugin):
    def load(self):
        self.votes = dict()

    async def on_new(self, message):
        if URL_PATTERN.findall(message.content) or message.attachments:
            await self.bot.add_reaction(message, '👍')
            await self.bot.add_reaction(message, '👎')

    async def on_reaction_add(self, reaction, user):
        if reaction.emoji == '❌':
            await self.check_vote_delete(reaction)
        elif reaction.emoji == '👎':
            await self.check_downvote(reaction)

    @command(
        p.string('!react') + p.bind(p.mention, 'who') +
        p.bind(p.word, 'emoji'), master_only)
    async def react_last_user(self, message, who, emoji):
        try:
            last_message = next(msg for msg in list(self.bot.messages)[-2::-1]
                                if msg.channel.id == message.channel.id
                                and msg.author.id == str(who))
        except StopIteration:
            self.debug('No Message')
        else:
            await self.bot.add_reaction(last_message, emoji)
            await self.bot.delete_message(message)

    @command(
        p.string('!react') + p.bind(p.mention, 'who') +
        p.bind(p.emoji, 'emoji_id'), master_only)
    async def react_last_user_emoji(self, message, who, emoji_id):
        try:
            last_message = next(msg for msg in list(self.bot.messages)[-2::-1]
                                if msg.channel.id == message.channel.id
                                and msg.author.id == str(who))
        except StopIteration:
            self.debug('No Message')
        else:
            for server in self.bot.servers:
                for emoji in server.emojis:
                    if emoji.id == str(emoji_id):
                        try:
                            await self.bot.add_reaction(last_message, emoji)
                            await self.bot.delete_message(message)
                            return
                        except:
                            break

            await self.send_message(message.channel,
                                    "I can't use that emoji 😢",
                                    delete_after=5)
            await self.bot.add_reaction(last_message, '❓')

    @command(
        p.string('!votedel') + p.bind(p.mention, 'who') +
        p.bind(p.maybe(p.integer), 'count'), master_only)
    async def votedel(self, message, who, count=10):
        try:
            last_message = next(msg for msg in list(self.bot.messages)[-2::-1]
                                if msg.channel.id == message.channel.id
                                and msg.author.id == str(who))
        except StopIteration:
            self.debug('No Message')
        else:
            self.votes[last_message.id] = (last_message, count)
            await self.bot.add_reaction(last_message, '❌')
            await self.send_message(
                message.channel,
                'Deletion vote started: Cast your vote by clicking the ❌ reaction in order to delete {}\'s message ({} votes needed)'
                .format(last_message.author.mention, count),
                delete_after=10)

    async def check_vote_delete(self, reaction):
        try:
            message, count = self.votes[reaction.message.id]
        except KeyError:
            pass
        else:
            if reaction.count >= count:
                del self.votes[reaction.message.id]

                await self.bot.delete_message(message)
                await self.send_message(reaction.message.channel,
                                        'Deletion vote passed',
                                        delete_after=5)

    async def check_downvote(self, reaction):
        if reaction.count >= 10:
            await self.bot.delete_message(reaction.message)
            await self.send_message(
                reaction.message.channel,
                '{}, I deleted your post since it was disliked too many times'.
                format(reaction.message.author.mention),
                delete_after=10)
Exemplo n.º 20
0
class Trivia(Plugin):
    def load(self):
        self.trivias = dict()

        for name, Behavior in ALL_TRIVAS:
            try:
                self.trivias[name] = Behavior()
            except Exception as e:
                self.warning('Failed to initialize trivia {}: {}'.format(
                    name, e))

    @command(
        p.string('!trivia') + p.bind(p.word, 'topic') + p.eof,
        global_cooldown(20, True))
    async def trivia(self, message, topic):
        try:
            trivia_gen = self.trivias[topic.lower()]
        except KeyError:
            topics_string = ' '.join('__{}__'.format(name)
                                     for name in self.trivias.keys())
            await self.send_message(
                message.channel,
                'There is no trivia about __{}__, available topics are: {}\n'.
                format(topic, topics_string))
        else:
            await self.run_trivia(message.channel, trivia_gen(), topic)

    async def send(self, channel, message):
        sender = self.send_file if 'file_path' in message else self.send_message
        return await sender(channel=channel, **message)

    async def get_answers(self, channel, trivia_message):
        answers = self.bot.logs_from(channel,
                                     after=trivia_message,
                                     reverse=True)

        users = set()
        unique_answers = list()
        async for answer in answers:
            if answer.author.id in users:
                continue
            if answer.edited_timestamp is not None:
                continue
            unique_answers.append(answer)
            users.add(answer.author.id)

        return sorted(unique_answers, key=lambda m: m.timestamp)

    async def run_trivia(self, channel, trivia, topic):
        premise = next(trivia)
        message = await premise
        trivia_message = await self.send(channel, message)

        query = next(trivia)
        await query()
        answers = await self.get_answers(channel, trivia_message)
        self.debug(
            ['{}: {}'.format(a.author.name, a.clean_content) for a in answers])
        winner, conclusion = trivia.send(answers)

        await self.send(channel, conclusion)

        if winner:
            if len(answers) >= 3:
                self.register_winner(winner, channel.server, topic)
                points = self.get_points(winner.id, channel.server.id)
                await self.send_message(
                    channel,
                    '{} now has {} points!'.format(winner.mention, points))
            else:
                await self.send_message(
                    channel, 'Not enough participants to make this one count')

    @command(
        p.string('!trivia') + p.string('stats') +
        p.bind(p.mention, 'user_id') + p.bind(p.maybe(p.word), 'topic'))
    async def trivia_stats(self, message, user_id, topic=None):
        ranks = self.get_ranks(message.server.id, topic)
        for_string = '' if topic is None else ' for `{}`'.format(topic)
        try:
            rank, points = next((i + 1, points)
                                for i, (points, id) in enumerate(ranks)
                                if id == user_id)
        except StopIteration:
            await self.send_message(
                message.channel, '{} has no points on this server{}'.format(
                    f.mention(user_id), for_string))
        else:
            await self.send_message(
                message.channel,
                '{} has {} points and is ranked #{} on this server{}'.format(
                    f.mention(user_id), points, rank, for_string))

    @command(
        p.string('!trivia') + p.string('stats') + p.string('all') +
        p.bind(p.maybe(p.word), 'topic'))
    async def trivia_stats_all(self, message, topic=None):
        ranks = self.get_ranks(message.server.id, topic)
        ranks = ranks[:10]

        def user_name(u_id):
            user = self.bot.find_user(str(u_id))
            return user.name if user else '???'

        leaderboard = '\n'.join(
            '#{:>2} {:<30} ({:>3} pts)'.format(i +
                                               1, user_name(user_id), points)
            for i, (points, user_id) in enumerate(ranks))

        for_string = '' if topic is None else ' for `{}`'.format(topic)
        await self.send_message(
            message.channel,
            'Top {} on this server{}:\n{}'.format(len(ranks), for_string,
                                                  f.code_block(leaderboard)))

    def register_winner(self, winner, server, topic):
        with self.transaction() as trans:
            trans.execute(
                q.create_trivia_result,
                dict(user_id=winner.id,
                     topic=topic,
                     answer='',
                     stamp=datetime.now(),
                     server_id=server.id))

    def get_points(self, user_id, server_id):
        with self.transaction() as trans:
            trans.execute(q.get_count,
                          dict(
                              user_id=user_id,
                              server_id=server_id,
                          ))

            return trans.fetchone()[0]

    def get_ranks(self, server_id, topic):
        query = q.get_ranks if topic is None else q.get_ranks_topic
        params = dict(server_id=server_id)
        if topic is not None:
            params['topic'] = topic

        with self.transaction() as trans:
            trans.execute(query, params)
            data = trans.fetchall()
            return data
Exemplo n.º 21
0
class UrbanDictionary(Plugin):

    def load(self):
        self.api_key = self.config.get('mashape_key')

        self.client = AsyncHTTPClient()

        self.definitions_by_message = dict()

    async def on_reaction_add(self, reaction, user):
        if user == self.bot.user:
            return

        show_prev = (reaction.emoji == '⬅')
        show_next = (reaction.emoji == '➡')

        if not show_prev and not show_next:
            return

        message = reaction.message

        try:
            (definitions, index) = self.definitions_by_message[message.id]
        except KeyError:
            pass
        else:
            new_index = index + 1 if show_next else index - 1

            if new_index < 0 or new_index >= len(definitions):
                return

            new_embed = definition_embed(definitions, new_index)

            await self.bot.clear_reactions(message)
            await self.bot.edit_message(message, '', embed=new_embed)

            self.definitions_by_message[message.id] = (definitions, new_index)

            await asyncio.sleep(5)
            if new_index > 0:
                await self.bot.add_reaction(message, '⬅')
            if new_index < (len(definitions) - 1):
                await self.bot.add_reaction(message, '➡')

    @command(p.string('!define'), global_cooldown(60, True))
    async def define_word_command(self, message):
        term = message.clean_content[len('!define'):].strip()
        definitions = await self.fetch_definitions(term)

        if definitions:
            by_thumbs_up = lambda d: d['thumbs_up']
            sorted_definitions = sorted(definitions, key=by_thumbs_up, reverse=True)

            message = await self.send_message(
                message.channel, '',
                embed        = definition_embed(sorted_definitions, 0),
                delete_after = 120
            )

            await self.register_definition_message(message, sorted_definitions)
        else:
            await self.send_message(
                message.channel,
                'There is no definition of `{}` on urban dictionary yet'
                    .format(term),
                delete_after = 5
            )

    async def register_definition_message(self, message, definitions):
        if len(definitions) >= 2:
            await self.bot.add_reaction(message, '➡')

            self.definitions_by_message[message.id] = (definitions, 0)

    DEFINE_URL = 'https://mashape-community-urban-dictionary.p.mashape.com/define'
    async def fetch_definitions(self, term):
        url = url_concat(UrbanDictionary.DEFINE_URL, dict(term=term))
        request = HTTPRequest(
            url     = url,
            headers = {
                'X-Mashape-Key': self.api_key,
                'Accept'       : 'text/plain'
            }
        )
        tornado_future = self.client.fetch(request)
        future = to_asyncio_future(tornado_future)
        response = await future

        data = json_decode(response.body)

        return data['list']