Ejemplo n.º 1
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
Ejemplo n.º 2
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))
Ejemplo n.º 3
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()
Ejemplo n.º 4
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)
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
from collections import namedtuple

DiceRoll = namedtuple('DiceRoll', ['count', 'face_count', 'modifier'])

dice_types = {'d4', 'd6', 'd8', 'd10', 'd12', 'd20'}
dice_type_grammar = p.one_of(p.string, *dice_types)


def extract_dice_type(token):
    face_count_string = token.value[1:]  # skipping the 'd' prefix

    return int(face_count_string)


dice_grammar = (p.maybe(p.integer) +
                (dice_type_grammar >> extract_dice_type)).named('Dice')

dice_modifier_grammar = ((p.one_of(p.a, '+', '-') >> p.to_s) +
                         p.integer).named('Modifier')


def to_dice_roll(parsed):
    count, face_count, modifier = parsed

    # Default behavior when no prefix is specified is `1` dice
    if count is None:
        count = 1

    if modifier is None:
        modifier_value = 0
Ejemplo n.º 7
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
                )
Ejemplo n.º 8
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)
Ejemplo n.º 9
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)
Ejemplo n.º 10
0
from globibot.lib.helpers import parsing as p

from collections import namedtuple

DiceRoll = namedtuple('DiceRoll', ['count', 'face_count', 'modifier'])

dice_types = { 'd4', 'd6', 'd8', 'd10', 'd12', 'd20' }
dice_type_grammar = p.one_of(p.string, *dice_types)

def extract_dice_type(token):
    face_count_string = token.value[1:] # skipping the 'd' prefix

    return int(face_count_string)

dice_grammar = (
    p.maybe(p.integer) +
    (dice_type_grammar >> extract_dice_type)
).named('Dice')

dice_modifier_grammar = (
    (p.one_of(p.a, '+', '-') >> p.to_s) +
    p.integer
).named('Modifier')

def to_dice_roll(parsed):
    count, face_count, modifier = parsed

    # Default behavior when no prefix is specified is `1` dice
    if count is None:
        count = 1