Пример #1
0
    async def task_confirm_auto(self):
        await self.bot.wait_until_ready()
        sleep_cycle = (60 * 60 * 0.5)  # half hour cycle

        while not self.bot.is_closed():
            await asyncio.sleep(8)
            logger.debug('Task running: task_confirm_auto')

            utilities.connect()
            for guild in self.bot.guilds:
                staff_output_channel = guild.get_channel(
                    settings.guild_setting(guild.id, 'game_request_channel'))
                if not staff_output_channel:
                    logger.debug(
                        f'Could not load game_request_channel for server {guild.id} - skipping'
                    )
                    continue

                prefix = settings.guild_setting(guild.id, 'command_prefix')
                (unconfirmed_count, games_confirmed) = await self.confirm_auto(
                    guild, prefix, staff_output_channel)
                if games_confirmed:
                    await staff_output_channel.send(
                        f'Autoconfirm process complete. {games_confirmed} games auto-confirmed. {unconfirmed_count - games_confirmed} games left unconfirmed.'
                    )

            await asyncio.sleep(sleep_cycle)
Пример #2
0
 async def on_ready(self):
     utilities.connect()
     if self.bot.user.id == 479029527553638401:
         # beta bot, using nelluk server to watch for messages
         self.announcement_message = self.get_draft_config(settings.server_ids['test'])['announcement_message']
     else:
         # assume polychampions
         self.announcement_message = self.get_draft_config(settings.server_ids['polychampions'])['announcement_message']
Пример #3
0
 def async_game_search():
     utilities.connect()
     query = models.Game.search(status_filter=2,
                                guild_id=guild.id)
     query = list(
         query
     )  # reversing 'Incomplete' queries so oldest is at top
     query.reverse()
     return query
Пример #4
0
    async def task_send_polychamps_invite(self):
        await self.bot.wait_until_ready()

        message = (
            'You have met the qualifications to be invited to the **PolyChampions** discord server! '
            'PolyChampions is a competitive Polytopia server organized into a league, with a focus on team (2v2 and 3v3) games.'
            '\n To join use this invite link: https://discord.gg/cX7Ptnv')
        while not self.bot.is_closed():
            sleep_cycle = (60 * 60 * 6)
            await asyncio.sleep(30)
            logger.info('Running task task_send_polychamps_invite')
            guild = discord.utils.get(self.bot.guilds,
                                      id=settings.server_ids['main'])
            if not guild:
                logger.warn('Could not load guild via server_id')
                break
            utilities.connect()
            dms = models.DiscordMember.members_not_on_polychamps()
            logger.info(f'{len(dms)} discordmember results')
            for dm in dms:
                wins_count, losses_count = dm.wins().count(), dm.losses(
                ).count()
                if wins_count < 5:
                    logger.debug(
                        f'Skipping {dm.name} - insufficient winning games')
                    continue
                if dm.games_played(in_days=15).count() < 1:
                    logger.debug(
                        f'Skipping {dm.name} - insufficient recent games')
                    continue
                if dm.elo_max > 1150:
                    logger.debug(
                        f'{dm.name} qualifies due to higher ELO > 1150')
                elif wins_count > losses_count:
                    logger.debug(
                        f'{dm.name} qualifies due to positive win ratio')
                else:
                    logger.debug(
                        f'Skipping {dm.name} - ELO or W/L record insufficient')
                    continue

                logger.debug(f'Sending invite to {dm.name}')
                guild_member = guild.get_member(dm.discord_id)
                if not guild_member:
                    logger.debug(
                        f'Could not load {dm.name} from guild {guild.id}')
                    continue
                try:
                    await guild_member.send(message)
                except discord.DiscordException as e:
                    logger.warn(f'Error DMing member: {e}')
                else:
                    dm.date_polychamps_invite_sent = datetime.datetime.today()
                    dm.save()
            await asyncio.sleep(sleep_cycle)
Пример #5
0
        def process_leaderboard():
            utilities.connect()
            leaderboard_query = Player.leaderboard(date_cutoff=date_cutoff,
                                                   max_flag=max_flag)

            for counter, player in enumerate(leaderboard_query[:2000]):
                wins, losses = player.get_record()
                elo_field = player.elo_max if max_flag else player.elo
                leaderboard.append((
                    f'{(counter + 1):>3}. {player.name}',
                    f'`ELO {elo_field}\u00A0\u00A0\u00A0\u00A0W {wins} / L {losses}`'
                ))
            return leaderboard, leaderboard_query.count()
Пример #6
0
    async def convert(self, ctx, game_id):
        # allows a SpiesGame to be used as a parameter for a discord command, and get converted into a database object on the fly

        utilities.connect()
        try:
            game = Game.get(id=int(game_id))
        except (ValueError, peewee.DataError):
            await ctx.send(f'Invalid game ID "{game_id}".')
            raise commands.UserInputError()
        except peewee.DoesNotExist:
            await ctx.send(f'Game with ID {game_id} cannot be found.')
            raise commands.UserInputError()
        else:
            logger.debug(f'Game with ID {game_id} found.')
            return game
Пример #7
0
        def async_create_player_embed():
            utilities.connect()
            wins, losses = player.get_record()
            rank, lb_length = player.leaderboard_rank(settings.date_cutoff)

            if rank is None:
                rank_str = 'Unranked'
            else:
                rank_str = f'{rank} of {lb_length}'

            max_str = f'(Max: {player.elo_max})\n' if player.elo_max > player.elo else ''
            results_str = f'ELO: {player.elo}\n{max_str}W\u00A0{wins}\u00A0/\u00A0L\u00A0{losses}'

            embed = discord.Embed(
                description=f'__Player card for <@{player.discord_id}>__')
            embed.add_field(name='**Results**', value=results_str)
            embed.add_field(name='**Ranking**', value=rank_str)

            guild_member = ctx.guild.get_member(player.discord_id)
            if guild_member:
                embed.set_thumbnail(url=guild_member.avatar_url_as(size=512))

            return embed
Пример #8
0
    async def on_member_update(self, before, after):
        # Listen for changes to member roles or display names and update database if any relevant changes detected
        player_query = Player.select().where((Player.discord_id == after.id))

        banned_role = discord.utils.get(before.guild.roles, name='ELO Banned')
        if banned_role not in before.roles and banned_role in after.roles:
            utilities.connect()
            try:
                player = player_query.get()
            except peewee.DoesNotExist:
                return
            player.is_banned = True
            player.save()
            logger.info(f'ELO Ban added for player {player.id} {player.name}')

        if banned_role in before.roles and banned_role not in after.roles:
            utilities.connect()
            try:
                player = player_query.get()
            except peewee.DoesNotExist:
                return
            player.is_banned = False
            player.save()
            logger.info(
                f'ELO Ban removed for player {player.id} {player.name}')

        # Updates display name in DB if user changes their display name
        if before.display_name == after.display_name:
            return

        logger.debug(
            f'Attempting to change displayname for {before.display_name} to {after.display_name}'
        )
        # update name in guild's Player record
        utilities.connect()
        try:
            player = player_query.get()
        except peewee.DoesNotExist:
            return
        player.display_name = after.display_name
        player.save()
Пример #9
0
 async def pre_invoke_setup(ctx):
     utilities.connect()
     logger.debug(
         f'Command invoked: {ctx.message.clean_content}. By {ctx.message.author.name} in {ctx.channel.id} {ctx.channel.name} on {ctx.guild.name}'
     )
Пример #10
0
def init_bot(loop: asyncio.AbstractEventLoop = None, args: List[str] = None):
    main(args)
    utilities.connect()
    am = discord.AllowedMentions(everyone=False)
    intents = discord.Intents().all()
    intents.typing = False
    bot = commands.Bot(command_prefix=get_prefix,
                       owner_id=settings.owner_id,
                       allowed_mentions=am,
                       intents=intents,
                       activity=discord.Activity(
                           name='$guide', type=discord.ActivityType.playing),
                       loop=loop)
    settings.bot = bot
    bot.purgable_messages = [
    ]  # auto-deleting messages to get cleaned up by Administraton.quit  (guild, channel, message) tuple list
    bot.locked_game_records = set(
    )  # Games which cannot be written to since another command is working on them right now. Ugly hack to do what should be done at the DB level

    cooldown = commands.CooldownMapping.from_cooldown(6, 30.0,
                                                      commands.BucketType.user)

    @bot.check
    async def globally_block_dms(ctx):
        # Should prevent bot from being able to be controlled via DM
        return ctx.guild is not None

    @bot.check
    async def restrict_banned_users(ctx):
        if ctx.author.id in settings.discord_id_ban_list or discord.utils.get(
                ctx.author.roles, name='ELO Banned'):
            await ctx.send(
                'You are banned from using this bot. :kissing_heart:')
            return False
        return True

    @bot.check
    async def cooldown_check(ctx):
        if ctx.invoked_with == 'help' and ctx.command.name != 'help':
            # otherwise check will run once for every command in the bot when someone invokes $help
            return True
        if ctx.author.id == settings.owner_id:
            return True
        bucket = cooldown.get_bucket(ctx.message)
        retry_after = bucket.update_rate_limit()
        if retry_after:
            await ctx.send('You\'re on cooldown. Slow down those commands!')
            logger.warning(f'Cooldown limit reached for user {ctx.author.id}')
            return False

        # not on cooldown
        return True

    @bot.event
    async def on_command_error(ctx, exc):
        # This prevents any commands with local handlers being handled here in on_command_error.
        if hasattr(ctx.command, 'on_error'):
            return
        print(type(exc))
        error = getattr(exc, "original", exc)
        print(error, type(error))
        ignored = (commands.CommandNotFound, commands.UserInputError,
                   commands.CheckFailure)

        if isinstance(
                exc,
                commands.CommandNotFound) and ctx.invoked_with[:4] == 'join':
            await ctx.send(
                f'Cannot understand command. Make sure to include a space and a numeric game ID.\n*Example:* `{ctx.prefix}join 11234`'
            )

        # Anything in ignored will return and prevent anything happening.
        if isinstance(exc, ignored):
            logger.warning(
                f'Exception on ignored list raised in {ctx.command}. {exc}')
            return
        if isinstance(exc, commands.CommandOnCooldown):
            logger.info(f'Cooldown triggered: {exc}')
            await ctx.send(
                f'This command is on a cooldown period. Try again in {exc.retry_after:.0f} seconds.'
            )
        elif isinstance(exc, exceptions.RecordLocked):
            return await ctx.send(f':warning: {exc}')
        else:
            exception_str = ''.join(
                traceback.format_exception(etype=type(exc),
                                           value=exc,
                                           tb=exc.__traceback__))
            logger.critical(
                f'Ignoring exception in command {ctx.command}: {exc} {exception_str}',
                exc_info=True)
            await ctx.send(
                f'Unhandled error (notifying <@{settings.owner_id}>): {exc}')

    @bot.before_invoke
    async def pre_invoke_setup(ctx):
        utilities.connect()
        logger.debug(
            f'Command invoked: {ctx.message.clean_content}. By {ctx.message.author.name} in {ctx.channel.id} {ctx.channel.name} on {ctx.guild.name}'
        )

    initial_extensions = [
        'modules.games', 'modules.customhelp', 'modules.matchmaking',
        'modules.administration', 'modules.misc', 'modules.league',
        'modules.api_cog'
    ]
    for extension in initial_extensions:
        bot.load_extension(extension)

    @bot.event
    async def on_message(message):
        if settings.maintenance_mode:
            if message.content and message.content.startswith(
                    tuple(get_prefix(bot, message))):
                logger.debug(
                    'Ignoring messages while settings.maintenance_mode is set to True'
                )
        else:
            # it is possible to modify the content of a message here before processing, ie replace curly quotes in message.content with straight quotes
            await bot.process_commands(message)

    @bot.event
    async def on_ready():
        """http://discordpy.readthedocs.io/en/rewrite/api.html#discord.on_ready"""

        print(
            f'\n\nv2 Logged in as: {bot.user.name} - {bot.user.id}\nVersion: {discord.__version__}\n'
        )
        print('Successfully logged in and booted...!')

        for g in bot.guilds:
            if g.id in settings.config:
                logger.debug(f'Loaded in guild {g.id} {g.name}')
            else:
                logger.error(
                    f'Unauthorized guild {g.id} {g.name} not found in settings.py configuration - Leaving...'
                )
                await g.leave()

    if loop:
        loop.create_task(bot.start(settings.discord_key))
    else:
        bot.run(settings.discord_key)
Пример #11
0
"""Entry point to run the API server.

Too run this, use the following command:

$ python3 -m uvicorn server:server

(Replacing 'python3' with your Python installation).
You can specify the port/address to bind to with the `--host` and
`--port` options.
"""
from modules.api import server
from modules.utilities import connect

import logging
from logging.handlers import RotatingFileHandler

api_handler = RotatingFileHandler(filename='logs/api.log',
                                  encoding='utf-8',
                                  maxBytes=1024 * 1024 * 2,
                                  backupCount=5)
api_handler.setFormatter(
    logging.Formatter('%(asctime)s:%(levelname)s:%(name)s: %(message)s'))
api_logger = logging.getLogger('polybot.api')
api_logger.setLevel(logging.DEBUG)
api_logger.addHandler(api_handler)

connect()
Пример #12
0
    async def task_purge_incomplete(self):
        await self.bot.wait_until_ready()
        sleep_cycle = (60 * 60 * 2)  # 2 hour cycle

        while not self.bot.is_closed():
            await asyncio.sleep(20)
            logger.debug('Task running: task_purge_incomplete')

            old_60d = (datetime.date.today() + datetime.timedelta(days=-60))
            old_90d = (datetime.date.today() + datetime.timedelta(days=-90))
            old_120d = (datetime.date.today() + datetime.timedelta(days=-120))
            old_150d = (datetime.date.today() + datetime.timedelta(days=-150))

            for guild in self.bot.guilds:
                staff_output_channel = guild.get_channel(
                    settings.guild_setting(guild.id, 'game_request_channel'))
                if not staff_output_channel:
                    logger.debug(
                        f'Could not load game_request_channel for server {guild.id} {guild.name} - skipping'
                    )
                    continue

                utilities.connect()

                def async_game_search():
                    utilities.connect()
                    query = models.Game.search(status_filter=2,
                                               guild_id=guild.id)
                    query = list(
                        query
                    )  # reversing 'Incomplete' queries so oldest is at top
                    query.reverse()
                    return query

                game_list = await self.bot.loop.run_in_executor(
                    None, async_game_search)

                delete_result = []
                for game in game_list[:500]:
                    game_size = len(game.lineup)
                    rank_str = ' - *Unranked*' if not game.is_ranked else ''
                    if game_size == 2 and game.date < old_60d and not game.is_completed:
                        delete_result.append(
                            f'Deleting incomplete 1v1 game older than 60 days. - {game.get_headline()} - {game.date}{rank_str}'
                        )
                        # await self.bot.loop.run_in_executor(None, game.delete_game)
                        game.delete_game()

                    if game_size == 3 and game.date < old_90d and not game.is_completed:
                        delete_result.append(
                            f'Deleting incomplete 3-player game older than 90 days. - {game.get_headline()} - {game.date}{rank_str}'
                        )
                        await game.delete_game_channels(
                            self.bot.guilds, guild.id)
                        # await self.bot.loop.run_in_executor(None, game.delete_game)
                        game.delete_game()

                    if game_size == 4:
                        if game.date < old_90d and not game.is_completed and not game.is_ranked:
                            delete_result.append(
                                f'Deleting incomplete 4-player game older than 90 days. - {game.get_headline()} - {game.date}{rank_str}'
                            )
                            await game.delete_game_channels(
                                self.bot.guilds, guild.id)
                            await self.bot.loop.run_in_executor(
                                None, game.delete_game)
                            game.delete_game()
                        if game.date < old_120d and not game.is_completed and game.is_ranked:
                            delete_result.append(
                                f'Deleting incomplete ranked 4-player game older than 120 days. - {game.get_headline()} - {game.date}{rank_str}'
                            )
                            await game.delete_game_channels(
                                self.bot.guilds, guild.id)
                            # await self.bot.loop.run_in_executor(None, game.delete_game)
                            game.delete_game()

                    if (
                            game_size == 5 or game_size == 6
                    ) and game.is_ranked and game.date < old_150d and not game.is_completed:
                        # Max out ranked game deletion at game_size==6
                        delete_result.append(
                            f'Deleting incomplete ranked {game_size}-player game older than 150 days. - {game.get_headline()} - {game.date}{rank_str}'
                        )
                        await game.delete_game_channels(
                            self.bot.guilds, guild.id)
                        # await self.bot.loop.run_in_executor(None, game.delete_game)
                        game.delete_game()

                    if game_size >= 5 and not game.is_ranked and game.date < old_120d and not game.is_completed:
                        # no cap on unranked game deletion above 120 days old
                        delete_result.append(
                            f'Deleting incomplete unranked {game_size}-player game older than 120 days. - {game.get_headline()} - {game.date}{rank_str}'
                        )
                        await game.delete_game_channels(
                            self.bot.guilds, guild.id)
                        # await self.bot.loop.run_in_executor(None, game.delete_game)
                        game.delete_game()

                delete_str = '\n'.join(delete_result)
                logger.info(
                    f'Purging incomplete games for guild {guild.name}:\n{delete_str}'
                )
                if len(delete_result):
                    await staff_output_channel.send(
                        f'{delete_str[:1900]}\nFinished - purged {len(delete_result)} games'
                    )

            await asyncio.sleep(sleep_cycle)