def debug(message): guild = '-' if message.guild: guild = message.guild.name log.debug( f'[{guild}][{message.channel}][{message.author.display_name}] {message.content}' )
async def task_check_for_data_updates(discord_client): filenames = LANG_FILES + [ 'World.json', 'User.json', 'Campaign.json', 'Soulforge.json' ] now = datetime.datetime.now() modified_files = [] for filename in filenames: file_path = GameAssets.path(filename) try: modification_time = datetime.datetime.fromtimestamp( os.path.getmtime(file_path)) except FileNotFoundError: continue modified = now - modification_time <= datetime.timedelta( seconds=CONFIG.get('file_update_check_seconds')) if modified: modified_files.append(filename) if modified_files: log.debug( f'Game file modification detected, reloading {", ".join(modified_files)}.' ) await asyncio.sleep(5) lock = asyncio.Lock() async with lock: try: old_expander = discord_client.expander del discord_client.expander discord_client.expander = TeamExpander() update_translations() except Exception as e: log.error('Could not update game file. Stacktrace follows.') log.exception(e) discord_client.expander = old_expander
async def soulforge_preview(self, message, lang, search_term, release_date=None, switch=None, **kwargs): if switch is None: switch = CONFIG.get('default_news_platform') == 'switch' async with message.channel.typing(): if message.interaction_id: await self.send_slash_command_result( message, content=None, embed=None, response_type=InteractionResponseType.PONG.value) start = time.time() weapon_data = self.expander.get_soulforge_weapon_image_data( search_term, release_date, switch, lang) if not weapon_data: e = discord.Embed( title= f'Weapon search for `{search_term}` did not yield any result', description=':(', color=self.BLACK) return await self.answer(message, e) image_data = graphic_soulforge_preview.render_all(weapon_data) result = discord.File(image_data, f'soulforge_{release_date}.png') duration = time.time() - start log.debug(f'Soulforge generation took {duration:0.2f} seconds.') await message.channel.send(file=result)
async def campaign_preview(self, message, lang, switch=None, team_code=None, **kwargs): switch = switch or CONFIG.get('default_news_platform') == 'switch' async with message.channel.typing(): if hasattr(message, 'interaction_id') and message.interaction_id: await self.send_slash_command_result( message, response_type=InteractionResponseType.MESSAGE.value, content='Please stand by ...', embed=None) start = time.time() campaign_data = self.expander.get_campaign_tasks(lang) campaign_data['switch'] = switch campaign_data['team'] = None if team_code: campaign_data['team'] = self.expander.get_team_from_message( team_code, lang) image_data = graphic_campaign_preview.render_all(campaign_data) result = discord.File( image_data, f'campaign_{campaign_data["start_date"]}.png') duration = time.time() - start log.debug(f'Soulforge generation took {duration:0.2f} seconds.') await message.channel.send(file=result)
async def register_slash_commands(self): guild_id = CONFIG.get('slash_command_guild_id') existing_commands = await get_all_commands(self.user.id, TOKEN, guild_id=guild_id) new_command_names = [c['function'] for c in COMMAND_REGISTRY] for command in existing_commands: if command['name'] not in new_command_names or CONFIG.get( 'deregister_slash_commands'): log.debug(f'Deregistering slash command {command["name"]}...') await remove_slash_command(self.user.id, TOKEN, guild_id, command['id']) if not CONFIG.get('register_slash_commands'): return for command in COMMAND_REGISTRY: if 'description' not in command: continue if command['function'] in [c['name'] for c in existing_commands]: continue log.debug(f'Registering slash command {command["function"]}...') await add_slash_command(self.user.id, bot_token=TOKEN, guild_id=guild_id, cmd_name=command['function'], description=command['description'], options=command.get('options', []))
async def on_slash_command(self, function, options, message): try: if 'lang' not in options: options['lang'] = self.language.get(message.guild) debug(message) await function(message=message, **options) except discord.HTTPException as e: log.debug(f'Could not answer to slash command: {e}')
def is_banner(source): request = requests.get(source) image = Image.open(BytesIO(request.content)) size = image.size ratio = size[0] / size[1] arbitrary_ratio_limit_for_banners = 10 log.debug(f'[NEWS] Found a ration of {ratio} in {source}.') return ratio >= arbitrary_ratio_limit_for_banners
async def task_update_dbl_stats(client): if client.topgg_client is None: return try: await client.topgg_client.post_guild_count() log.debug('Posted server count ({})'.format(client.topgg_client.guild_count)) except Exception as e: log.exception('Failed to post server count\n{}: {}'.format(type(e).__name__, e))
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) log.debug( f'--------------------------- Starting {self.BOT_NAME} v{self.VERSION} --------------------------' ) self.expander = TeamExpander() self.tower_data = TowerOfDoomData(self.my_emojis) self.prefix = models.Prefix(CONFIG.get('default_prefix')) self.language = models.Language(CONFIG.get('default_language')) self.subscriptions = models.Subscriptions() self.views = Views(emojis={})
async def load_rescues(cls, client): db = DB() db_result = db.cursor.execute('SELECT * FROM PetRescue;').fetchall() rescues = [] broken_rescues = [] for i, entry in enumerate(db_result, start=1): log.debug(f'Loading pet rescue {i} of {len(db_result)}') pet = client.expander.pets[entry['pet_id']].copy() client.expander.translate_pet(pet, entry['lang']) try: channel = await client.fetch_channel(entry['channel_id']) guild = None if not isinstance(channel, discord.DMChannel): guild = channel.guild message = FakeMessage('author', guild, channel, 'content') if entry['message_id']: message = await channel.fetch_message(entry['message_id']) except discord.errors.DiscordException: broken_rescues.append(entry['id']) continue rescue = PetRescue( pet=pet, time_left=0, message=message, mention=entry['mention'], lang=entry['lang'], answer_method=client.answer, config=client.pet_rescue_config, ) try: if entry['alert_message_id']: rescue.alert_message = await channel.fetch_message( entry['alert_message_id']) rescue.pet_message = await channel.fetch_message( entry['pet_message_id']) except discord.errors.DiscordException: broken_rescues.append(entry['id']) continue rescue.start_time = entry['start_time'] rescues.append(rescue) db.close() if broken_rescues: log.debug( f'Pruning {len(broken_rescues)} broken pet rescues from the database: {broken_rescues}.' ) for rescue_id in broken_rescues: await cls.delete_by_id(rescue_id=rescue_id) return rescues
async def handle_team_code(self, message, lang, team_code, shortened='', **kwargs): team = self.expander.get_team_from_message(team_code, lang) if not team or not team['troops']: log.debug(f'nothing found in message {team_code}.') return author = message.author.display_name author = await pluralize_author(author) e = self.views.render_team(team, author, shortened) await self.answer(message, e)
async def change_prefix(self, message, new_prefix, **kwargs): my_prefix = self.prefix.get(message.guild) if len(new_prefix) != 1: e = self.generate_response( 'Prefix change', self.RED, 'Error', f'Your new prefix has to be 1 characters long,' f' `{new_prefix}` has {len(new_prefix)}.') await self.answer(message, e) return await self.prefix.set(message.guild, new_prefix) e = self.generate_response( 'Administrative action', self.RED, 'Prefix change', f'Prefix was changed from `{my_prefix}` to `{new_prefix}`') await self.answer(message, e) log.debug( f'[{message.guild.name}] Changed prefix from {my_prefix} to {new_prefix}' )
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) log.debug( f'--------------------------- Starting {self.BOT_NAME} v{self.VERSION} --------------------------' ) self.expander = TeamExpander() self.tower_data = TowerOfDoomData(self.my_emojis) self.prefix = models.Prefix(CONFIG.get('default_prefix')) self.language = models.Language(CONFIG.get('default_language')) self.subscriptions = models.Subscriptions() self.views = Views(emojis={}) self.pet_rescues = [] self.pet_rescue_config: PetRescueConfig = None token = CONFIG.get('dbl_token') self.dbl_client = None self.server_status_cache = {'last_updated': datetime.datetime.min} if token: self.dbl_client = dbl.DBLClient(self, token)
async def on_guild_join(self, guild): await super().on_guild_join(guild) first_writable_channel = self.first_writable_channel(guild) ban = Ban.get(guild.id) if ban: log.debug( f'Guild {guild} ({guild.id}) was banned by {ban["author_name"]} because: {ban["reason"]}' ) if first_writable_channel: try: ban_message = self.views.render_ban_message(ban) await first_writable_channel.send(embed=ban_message) finally: return await guild.leave() welcome_message = self.views.render_welcome_message( self.prefix.get(guild)) if first_writable_channel: await first_writable_channel.send(embed=welcome_message)
async def register_slash_commands(self): log.debug('Deregistering all slash commands...') guild_id = CONFIG.get('slash_command_guild_id') for command in await get_all_commands(self.user.id, TOKEN, guild_id=guild_id): await remove_slash_command(self.user.id, TOKEN, guild_id, command['id']) if not CONFIG.get('register_slash_commands'): return log.debug(f'Registering slash commands...') for command in COMMAND_REGISTRY: if 'description' not in command: continue await add_slash_command(self.user.id, bot_token=TOKEN, guild_id=guild_id, cmd_name=command['function'], description=command['description'], options=command.get('options', []))
async def on_ready(self): if not self.bot_connect: self.bot_connect = datetime.datetime.now() log.debug(f'Connected at {self.bot_connect}.') else: await self.on_resumed() self.invite_url = f'https://discordapp.com/api/oauth2/authorize' \ f'?client_id={self.user.id}' \ f'&scope=bot' \ f'&permissions={self.permissions.value}' log.info(f'Logged in as {self.user.name}') subscriptions = sum([s.get('pc', True) for s in self.subscriptions]) log.info(f'{subscriptions} channels subscribed to news.') log.info(f'Active in {len(self.guilds)} guilds.') game = discord.Game("Gems of War") await self.change_presence(status=discord.Status.online, activity=game) await self.update_base_emojis() self.views.my_emojis = self.my_emojis
async def change_language(self, message, new_language, **kwargs): my_language = self.language.get(message.guild) if new_language not in LANGUAGES: e = discord.Embed(title='Default Language', color=self.BLACK) e.add_field( name='Error', value=f'`{new_language}` is not a valid language code.') self.add_available_languages(e) await self.answer(message, e) return await self.language.set(message.guild, new_language) e = self.generate_response( 'Default Language', self.WHITE, f'Default language for {message.guild}', f'Default language was changed from `{my_language}` to `{new_language}`.' ) await self.answer(message, e) log.debug( f'[{message.guild.name}] Changed language from {my_language} to {new_language}.' )
async def handle_team_code(self, message, lang, team_code, shortened='', **kwargs): team = self.expander.get_team_from_message(team_code, lang) if not team or not team['troops']: log.debug(f'nothing found in message {team_code}.') return author = message.author.display_name author = await pluralize_author(author) if kwargs.get('title') is None: team_code = None e = self.views.render_team(team, author, shortened, team_code=team_code, title=kwargs.get('title')) await self.answer(message, e) if team_code: await message.channel.send(content=f'[{team_code}]')
async def show_latest_news(self): if not self.is_ready(): return with open(NewsDownloader.NEWS_FILENAME) as f: articles = json.load(f) articles.reverse() if articles: log.debug( f'Distributing {len(articles)} news articles to {len(self.subscriptions)} channels.' ) for article in articles: e = discord.Embed(title='Gems of War news', color=self.WHITE, url=article['url']) content = self.views.trim_news_to_length(article['content'], article['url']) e.add_field(name=article['title'], value=content) embeds = [e] for image_url in article['images']: e = discord.Embed(type='image', color=self.WHITE) e.set_image(url=image_url) embeds.append(e) for subscription in self.subscriptions: relevant_news = subscription.get(article['platform']) if not relevant_news: continue channel = self.get_channel(subscription['channel_id']) if not channel: log.debug( f'Subscription {subscription} is broken, skipping.') continue log.debug( f'Sending [{article["platform"]}] {article["title"]} to {channel.guild.name}/{channel.name}.' ) if not await self.is_writable(channel): message = 'is not writable' if channel else 'does not exist' log.debug(f'Channel {message}.') continue try: for e in embeds: await channel.send(embed=e) except Exception as e: log.error('Could not send out news, exception follows') log.exception(e) with open(NewsDownloader.NEWS_FILENAME, 'w') as f: f.write('[]')
async def soulforge_preview(self, message, lang, search_term, release_date=None, switch=False, **kwargs): async with message.channel.typing(): start = time.time() weapon_data = self.expander.get_soulforge_weapon_image_data( search_term, release_date, switch, lang) if not weapon_data: e = discord.Embed( title= f'Weapon search for `{search_term}` did not yield any result', description=':(', color=self.BLACK) return await self.answer(message, e) image_data = soulforge_preview.render_all(weapon_data) result = discord.File(image_data, f'soulforge_{release_date}.png') duration = time.time() - start log.debug(f'Soulforge generation took {duration:0.2f} seconds.') await message.channel.send(file=result)
async def on_ready(self): if not self.bot_connect: self.bot_connect = datetime.datetime.now() log.debug(f'Connected at {self.bot_connect}.') else: await self.on_resumed() self.invite_url = discord.utils.oauth_url(client_id=self.user.id, permissions=self.permissions) subscriptions = sum([s.get('pc', True) for s in self.subscriptions]) log.info(f'{subscriptions} channels subscribed to PC news.') game = discord.Game("Gems of War") await self.change_presence(status=discord.Status.online, activity=game) await self.update_base_emojis() self.views.my_emojis = self.my_emojis log.info(f'Logged in as {self.user.name}') log.info(f'Active in {len(self.guilds)} guilds.') self.pet_rescue_config = PetRescueConfig() await self.pet_rescue_config.load() self.pet_rescues = await PetRescue.load_rescues(self) log.debug(f'Loaded {len(self.pet_rescues)} pet rescues after restart.') await self.register_slash_commands()
async def show_latest_news(self): if not self.is_ready(): return with open(NewsDownloader.NEWS_FILENAME) as f: articles = json.load(f) articles.reverse() if articles: log.debug( f'Distributing {len(articles)} news articles to {len(self.subscriptions)} channels.' ) for article in articles: embeds = self.views.render_news(article) for subscription in self.subscriptions: relevant_news = subscription.get(article['platform']) if not relevant_news: continue channel = self.get_channel(subscription['channel_id']) if not channel: log.debug( f'Subscription {subscription} is broken, skipping.') continue log.debug( f'Sending [{article["platform"]}] {article["title"]} to {channel.guild.name}/{channel.name}.' ) if not await self.is_writable(channel): message = 'is not writable' if channel else 'does not exist' log.debug(f'Channel {message}.') continue try: for e in embeds: await channel.send(embed=e) except Exception as ex: log.error('Could not send out news, exception follows') log.error(repr(e.fields)) log.exception(ex) with open(NewsDownloader.NEWS_FILENAME, 'w') as f: f.write('[]')