async def scrape_website(client): """ :param client: client bot is connected to :return: only if there's an issue Type '!scrape' to restart the scraping process. Note: this function is executed on bot_ready, so I have to work around not having a convenient guild object. r """ debug_channel = utils.get_bot_commands_channel(client) await debug_channel.send(f"Started web scraping.") print(f"Web scraper starting...") # This loop will always run indefinitely. while True: # During this web scraping, first check if there was # any commands issued to force stop this functionality. if scraper.issued_off: game_lab_channel = utils.get_game_lab_channel(client) print(f"Successfully turned off webscraper") await debug_channel.send( f"Successfully turned off scraper.\n\nPlease go to {game_lab_channel.mention} and verify this action by comparing its edited timestamp." ) scraper.issued_off = False scraper.is_scraping = False return # Secondly, check if the embeds exist. # It's possible someone may have deleted them mid-process. if not await validators.validate_pc_availability_embeds(client): print( f"...web scraping ending prematurely- embeds are missing! (This can be restarted with !scrape)" ) await debug_channel.send( f"ERROR: Machine availability panels must first exist in the channel `#{debug_channel}`! You can add these panels by entering `!gamelab` inside the channel, then start auto-updating PC availability with `!scrape`." ) return scraper.is_scraping = True pc_statuses = await _get_scraped_pc_availability() if pc_statuses is None: print( "Game Lab Availability is offline. Unable to get PC statuses. Restart bot with !restart." ) break print( f"Updating PC availability with the following statuses:\n\t{pc_statuses}" ) await update_machine_availability_embed(client, pc_statuses) print(F"Trying again in 5 seconds") await asyncio.sleep(5) return None
async def display_ping_check(ctx_or_client): last_day_of_month = calendar.monthrange(datetime.today().year, datetime.today().month)[1] # TODO: Get announcements and leadership chat into config try: ping_channel = discord.utils.get(ctx_or_client.guild.channels, name="announcements") if datetime.today().day == last_day_of_month: report_channel = discord.utils.get(ctx_or_client.guild.channels, name="leadership-chat") else: report_channel = utils.get_bot_commands_channel(ctx_or_client.guild) # TODO: Fix up guild differentiation except AttributeError: guild = ctx_or_client.get_guild(int(config['id-guild']['id'])) ping_channel = discord.utils.get(guild.channels, name="announcements") if datetime.today().day == last_day_of_month: report_channel = discord.utils.get(guild.channels, name="leadership-chat") else: report_channel = utils.get_bot_commands_channel(guild) first_day_of_month = datetime.today().replace(day=1) msgs = [msg async for msg in ping_channel.history(limit=100, before=None, after=first_day_of_month, around=None, oldest_first=None)] user_pings = {} total_pings = 0 for msg in msgs: if "@everyone" in msg.content: total_pings += 1 try: user_pings[msg.author] += 1 except KeyError: user_pings[msg.author] = 1 embed = discord.Embed(title=" ", description=f"`Description`: The total of users who have pinged @everyone in {ping_channel.mention} since {calendar.month_name[datetime.today().month]} 1, {datetime.today().year}.", color=0xff961f) embed.set_author(name="Ping Check - Report") embed.set_thumbnail(url="https://i.redd.it/4t5g9j86khp21.png") embed.set_footer(text=f"Total pings: {total_pings}") for user, pings in user_pings.items(): embed.add_field(name=f"User total: {pings}", value=f"{user.mention}", inline=True) await report_channel.send(embed=embed)
async def force_off(guild: discord.guild): debug_channel = utils.get_bot_commands_channel(guild) try: scraper.issued_off = True await debug_channel.send(f"Turning off webscraper, please wait...") except Exception as e: print(f"Unable to force off!") await debug_channel.send( f"Exception caught trying to turn off webscraper:\n\n{e}")
async def on_command_error(ctx, error): message = f"Command error or exception found! See below:\n\nCOMMAND - `{ctx.message.content}`\nERROR - `{error}`\nSENT BY - `{ctx.author}`\nFAILED AT - {ctx.channel.mention}\n\nFULL TRACEBACK:```python\n{error}```" print(dir(ctx)) print(dir(error)) owner = utils.get_codebase_owner_member(ctx.guild) commands_channel = utils.get_bot_commands_channel(ctx.guild) if owner: message += f"\n\n{owner.mention}" if owner and commands_channel is None: await owner.send(f"{message}") else: await commands_channel.send(message)
async def restart_app(self, context): print(F"Restarting bot!") bot_channel = utils.get_bot_commands_channel(context.guild) if self.is_authenticated() and [ dyno.state for dyno in self.app.dynos() ]: await bot_channel.send( "Restarting bot from Heroku- please wait approximately 15 seconds..." ) self.app.restart() else: await bot_channel.send( "Will not restart- this bot must be running on Heroku")
async def twitterpoll(ctx): """ Start the Twitter streaming process :param ctx: context :return: None """ try: await asyncio.wait([ twitterfeed.twitter_poller.poll_for_data_from_stream(client), twitterfeed.twitter_poller.poll_for_tweet_updates(), ]) except Exception as last_resort: commands_channel = utils.get_bot_commands_channel(ctx.guild) owner = utils.get_codebase_owner_member(ctx.guild) await commands_channel.send( f"{owner.mention}, all exceptions were tried against the Twitter streamer. The following was captured:\n{last_resort}\n\nIt is highly recommended to execute `!twitterstream` again." )
async def send_faq_embed(context): await context.message.delete() faq = utils.get_faq_and_a() faq_channel = utils.get_faq_channel(context.guild) help_dir_channel = utils.get_help_directory_channel(context.guild) if faq_channel is None: bot_channel = utils.get_bot_commands_channel(context.guild) await bot_channel.send(f"Missing FAQ channel!") try: # Questions are not a part of the config file questions_channel = discord.utils.get(context.guild.channels, name="questions") except Exception: questions_channel = None if questions_channel is None or help_dir_channel is None: embed = discord.Embed( title=" ", description="Please view our available resources for more help.", color=0x52fffc) else: embed = discord.Embed( title=" ", description= f"For any additional questions, please see {questions_channel.mention} or {help_dir_channel.mention}!", color=0x52fffc) embed.set_author(name="❔ FAQ") embed.set_thumbnail( url= "https://lh3.googleusercontent.com/proxy/NuskMuMLeEstVyxBKL5OLLQ4V-rULdK0fygraiaeqFaWVclGTaxCXz7RjVurr2GsZvS2ijr5H9_3wZPuPPRAYd5Vg-Q" ) for q_and_a in faq: embed.add_field(name=q_and_a[0].capitalize(), value=q_and_a[1], inline=False) await context.send(embed=embed)
async def on_ready(): print(f"On ready...") after = time.time() milliseconds = 1000 * (after - before) print(f"...bot ready! (in {milliseconds} milliseconds!)") channel = utils.get_bot_commands_channel(client) if channel: await channel.send( f"Bot successfully started in `{milliseconds}` milliseconds.\n\nTwitter poller needs to be started with `!twitterpoll`. See `!metrics`." ) print(F"Checking everyone pings...") await servermetrics.display_ping_check(client) # TODO: Automatically update GM panels and leadership panels on setup print(f"Starting web scraper...") await asyncio.wait([gamelabscraper.scrape_website(client)])
async def populate_channel_with_tweets(context): debug_channel = utils.get_bot_commands_channel(context.guild) num_tweets = 20 queue_wrapper.is_populating = True while num_tweets != 0: try: static_data = tweet_data_wrapper.get_static_data( client.profile, num_tweets - 1) dynamic_data = tweet_data_wrapper.get_dynamic_data() await embed_and_send(static_data, dynamic_data) await debug_channel.send( f"✅ Tweet {num_tweets-1} retrieved and sent successfully.") except Exception as e: await debug_channel.send( f"❌ Tweet {num_tweets-1} retrieved unsuccessfully:\n<{e}>") num_tweets -= 1 await asyncio.sleep(1) queue_wrapper.is_populating = False
async def debug_reaction(emoji, channel, member, is_add=True): """ :param emoji: The emoji the user reacted with :param channel: The channel the user reacted inside of :param member: The member that did the reactiobn :return: None Ideally, this should only execute if the user is reacting under personally designated environment (channel compared to emoji) matches. """ bot_channel = utils.get_bot_commands_channel(member.guild) owner = utils.get_codebase_owner_member(member.guild) if is_add: await bot_channel.send( f"{owner.mention}, member `{member}` reacted to {emoji} in {channel.mention}" ) else: await bot_channel.send( f"{owner.mention}, member `{member}` unreacted from {emoji} in {channel.mention}" )
async def send_game_manager_panel(context): """ :param context: Command context :return: None Send out the game manager panel. """ await context.message.delete() contact_info = utils.get_game_manager_info() print(f"Have contact info values?:\n{contact_info.values()}") def _a_gm_channel(): nonlocal contact_info if context.channel.name in str(contact_info.values()): # Had to cast to str return True return False for user_id, info in contact_info.items(): """ 0 = role titles 1 = names 2 = emails 3 = colors 4 = userids 5 = descriptions 6 = channel names (used for role) 7 = icon links """ if context.channel.name in contact_info[user_id] or not _a_gm_channel(): """ The latter condition will only execute if the current iteration is the intended channel the user wanted to type in. Granted, I could update all channels in one fell swoop, but for the sake of responsiveness, I'd rather the user be reassured nearly immediately based on their intuition. (It could very well take a long time for all channels to be updated, depends on how Discord wants to behave in general) """ # Determining description: if info[5]: description = info[5] else: role_name = info[6].replace("-", " ").title().replace("Of", "of").replace("Fifa", "FIFA").replace("Csgo", "CS:GO") role = discord.utils.get(context.guild.roles, name=role_name) role_gm = discord.utils.get(context.guild.roles, name=config['role']['gamemanager']) description = f"Looking for the **{role.mention}** **{role_gm.mention}**? Please message me!" # Adjusting embed data as usual embed = discord.Embed(title=info[1], description=description, color=int(info[3], 16)) member = discord.utils.get(context.message.guild.members, id=int(info[4])) embed.set_author(name=info[0], icon_url=info[7]) embed.set_thumbnail(url=member.avatar_url) embed.add_field(name="`Discord Contact:`", value=member.mention, inline=True) embed.add_field(name="`Email Contact:`", value=f"**{info[2]}**", inline=True) if _a_gm_channel(): gm_channel = context.channel else: gm_channel = discord.utils.get(context.guild.channels, name=info[6]) bot_channel = utils.get_bot_commands_channel(context.guild) last_message = await utils.get_last_message_from_channel(gm_channel) if last_message: await last_message.edit(embed=embed) await bot_channel.send(f"✅ Finished editing contact details in {gm_channel.mention} for Game Manager {member.mention}") else: await context.send(embed=embed) await bot_channel.send(f"✅ Finished creating new contact details in {gm_channel.mention} for Game Manager {member.mention}")
async def poll_for_data_from_stream(self, client): """ A workaround for asynchronous function unable to be threaded. This is function is called *last* at on_ready(). Bot remains operable. Reasoning: embeds need to be awaited. Listener pipeline is NOT asynchronous, which means embed sending can't be awaited there. This (asynchronous) function polls any data that was stored from the pipeline in 2 second intervals. """ listener.social_media_channel = utils.get_social_media_feed_channel( client.guild) listener.commands_channel = utils.get_bot_commands_channel( client.guild) listener.error = None # Assuming a user does this manually, this needs to clear print(f"Polling for stream...") await listener.commands_channel.send(f"Started Twitter feed.") self.is_polling = True while True: try: await asyncio.sleep(self._poll_rate) if listener.static_data and listener.dynamic_data: await embed_and_send(listener.static_data, listener.dynamic_data) listener.static_data = None listener.dynamic_data = None elif listener.error: await listener.commands_channel.send( f"Twitter poll error: `{listener.error}`\n*Unable to update Twitter feed*. Please retry in __15__ minutes, or refer to the Twitter API response codes for more info." ) self.is_polling = False break else: print( f"No messages in stream listener. Retrying in 5 seconds. Error: {listener.error}" ) await asyncio.sleep(5) except Exception as e: if self._num_retries == 0: await listener.commands_channel.send( f"Some unknown exception was caught trying to poll stream. {self._num_retries} retries remaining!\nError: `{e}`" ) else: await listener.commands_channel.send( f"`{self._num_retries}` remaining...") print( f"Some unknown exception caught trying to poll stream, retrying!:\n\n{e}" ) if self._num_retries > 0: self._num_retries -= 1 continue else: self.is_polling = False listener.error = e owner = discord.utils.get(client.guild.members, id=int(config['id']['owner'])) await listener.commands_channel.send( f"{owner.mention}, unable to start poller after 5 retries. See `!metrics` for more information" ) break