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)
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']
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
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)
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()
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
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
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()
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}' )
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)
"""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()
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)