async def get_inactive_members(context, progress_report=True): """Returns a list of inactive members.""" senders = [] inactive_members = [] now = datetime.datetime.now() days_threshold = Settings.inactive_threshold(context.guild.id) time_boundary = now - datetime.timedelta(days=days_threshold) progress_msg = None include_reactions = Settings.include_reactions_inactivity(context.guild.id) included_channels = context.guild.text_channels channel_count = len(included_channels) if progress_report: progress_msg = await utils.say(context.channel, content=f"Scanning {channel_count} channels for inactive members.") for i, channel in enumerate(included_channels): try: async for m in channel.history(limit=None, after=(now - datetime.timedelta(days=days_threshold)), oldest_first=False): if m.author not in senders: senders.append(m.author) if include_reactions: for r in m.reactions: async for u in r.users(): if u not in senders: senders.append(u) except discord.errors.Forbidden: print(f"Can't access {channel.name}") else: if progress_msg: await progress_msg.edit(content=f"Scanned {i}/{channel_count} channels for inactive members.") if progress_msg: await progress_msg.edit(content=f"Scanned {channel_count} channels for inactive members.") results = [ u for u in context.guild.members if u not in senders and u.joined_at < time_boundary and not u.bot ] db_inactive_members = [] # database.get_all_inactive_members(context.guild.id) for member in results: if member.id in db_inactive_members: m = db_inactive_members[member.id] m.user = member inactive_members.append(m) else: inactive_members.append(database.InactiveMember(context.guild.id, member.id, user=member)) # update_inactive_members(db_inactive_members, {m.member_id: m for m in inactive_members}) return inactive_members
def run_game(): ai_settings = Settings() pygame.init() screen = pygame.display.set_mode((ai_settings.screen_width,ai_settings.screen_height)) pygame.display.set_caption("Alien Invasion") ship = Ship(ai_settings,screen) stats = GameStats(ai_settings) bullets = Group() aliens = Group() stars = Group() drops = Group() sb = Scoreboard(ai_settings, screen, stats) play_button = Button(ai_settings, screen,"Play") gf.create_fleet(ai_settings,screen,ship, aliens) gf.create_sky(ai_settings, screen, stars) #gf.create_rain(ai_settings, screen, drops) while True: gf.check_events(ai_settings,screen,stats,sb, play_button, ship,aliens, bullets) if stats.game_active: ship.update() bullets.update() gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) gf.update_aliens(ai_settings, stats, screen, sb,ship, aliens, bullets) #gf.update_drops(ai_settings, drops) gf.update_screen(ai_settings,screen,stats, sb, stars, ship,aliens, bullets, drops, play_button)
async def message(self, context): async def get_destination(context): def check_destination(msg): return msg.author.id == context.message.author.id and msg.channel.id == context.channel.id and msg.channel_mentions await utils.say(context.channel, content="Which channel should the message be sent to?") try: destination = await self.bot.wait_for("message", timeout=60, check=check_destination) return destination.content except asyncio.TimeoutError: return False async def get_message(context): def check_message(msg): return msg.author.id == context.message.author.id and msg.channel.id == context.channel.id await utils.say(context.channel, content="What's your message?") try: message = await self.bot.wait_for("message", timeout=120, check=check_message) return message.content except asyncio.TimeoutError: return False cmd_settings = Settings.command_settings("message", context.guild.id) if not cmd_settings.get("enabled"): return arguments = context.message.content.split(maxsplit=2) destination_id = None msg = "" try: destination_id = arguments[1] except IndexError: destination_id = await get_destination(context) if not destination_id: return CommandStatus.CANCELLED destination_id = destination_id.strip("<># ") destination = discord.utils.find(lambda c: str(c.id) == destination_id, context.guild.text_channels) if not destination: await utils.say(context.channel, content="I couldn't find that channel on this server.") return CommandStatus.INVALID try: msg = arguments[2] except IndexError: msg = await get_message(context) if not msg: return CommandStatus.CANCELLED try: sent = await utils.say(destination, content=msg) except discord.Forbidden: await utils.say(context.channel, content=f"I don't have permission to send messages to {destination.name}") return CommandStatus.FORBIDDEN else: await utils.say(context.channel, content=f"Message sent: {sent.jump_url}")
def regular_lookup(word: str): """Looks up given word in Merriam-Webster Collegiate dictionary Args: word(str): Word to look up in dictionary. Returns: list: Can be a list of DictionaryEntry objects or a list of suggested strings to search up (in case of misspelling) """ entries = [] entries = regular_cache.get_entries(word) if not entries: api_url = Settings.command_settings("define")["base_api_url"] api_key = Settings.config["env"]["dict_regular_api_key"] req_location = f"{api_url}{word}?key={api_key}" response = requests.get(req_location, timeout=30) res_data = response.json() if not res_data: return None # Word not found original_found = False clean_word = re.sub(r"\s+", " ", word.strip().lower()) for i, entry in enumerate(res_data): if isinstance(entry, str): entries.append(entry) elif re.match( r"{word}\W?(?:\:[\d\w]+)?$".format(word=clean_word), entry.get("meta", {"id": ""})["id"], re.I): original_found = True try: # Check for spelling variants if not entry.get("shortdef") and entry.get("cxs"): for variant in regular_lookup(entry["cxs"][0]["cxtis"][0]["cxt"]): entries.append(variant) else: entries.append(DictionaryEntry(entry)) except (IndexError, KeyError) as e: continue elif 0 == i and not entries: try: # Check for spelling variants if not entry.get("shortdef") and entry.get("cxs"): for variant in entry["cxs"][0]["cxtis"]: if variant.get("cxt"): entries.append(variant["cxt"]) else: entries.append(DictionaryEntry(entry)) except (IndexError, KeyError) as e: continue elif not original_found: entries.append(DictionaryEntry(entry)) regular_cache.add_entries(word, entries) return entries
async def inactivelist(self, context): cmd_settings = Settings.command_settings(context.command.name, context.guild.id) if not cmd_settings.get("enabled"): return inactive_members = await get_inactive_members(context) inactive_list = [] for i in inactive_members: last_notified = i.last_notified.strftime(" (%b %d, %Y %Z)") if i.last_notified else "" entry = f"{'**EXEMPT** ' if i.is_exempt else ''}{i.user.mention} [{i.user.display_name}]{last_notified}" inactive_list.append(entry) days_threshold = Settings.inactive_threshold(context.guild.id) embeds = utils.split_embeds( title=f"Inactive Members ({days_threshold}+ days since last message)", description="\n".join(inactive_list) ) for i, embed in enumerate(embeds): if i < len(embeds) - 1: await utils.say(context.channel, embed=embed) else: # Final message inactivity_message = Settings.inactive_message(context.guild.id) if inactivity_message: embed.set_footer(text="React 📧 below to notify them") report = await utils.say(context.channel, content=f"{context.author.mention}", embed=embed) if inactivity_message: await report.add_reaction("📧") def check(reaction, user): return reaction.message.id == report.id and user.id == context.message.author.id \ and str(reaction.emoji) == "📧" try: await self.bot.wait_for("reaction_add", timeout=600, check=check) except asyncio.TimeoutError: embed.set_footer(text=discord.Embed.Empty) await report.edit(embed=embed) await report.clear_reactions() else: await self.notify_inactive_members(context, inactive_members) return CommandStatus.COMPLETED
async def on_command_error(self, context, error): cmd_name = context.command.name cmd_settings = Settings.command_settings(cmd_name, context.guild.id) if isinstance(error, discord.ext.commands.MissingPermissions ) and cmd_settings.get("visible"): notice = "You need the following permissions for this command: {}".format( ", ".join([f"`{p}`" for p in error.missing_perms])) await utils.say(context.channel, content=notice) elif isinstance(error, discord.ext.commands.NotOwner): return else: Logger.error(logger, error)
async def notify_inactive_members(self, context, members=None): message = Settings.inactive_message(context.guild.id) if not message: await utils.say(context.channel, content="There is no inactivity message for this server.") return CommandStatus.FAILED if not members: members = await get_inactive_members(context) members = [i.user for i in members] await self.notify_members(context, members, message, use_case="inactivity") return CommandStatus.COMPLETED
async def notify_members(self, context, members, message, use_case=None): success = [] failed = [] for member in members: notification = message inactivity_settings = Settings.inactivity_features(context.guild.id) if use_case == "inactivity" and inactivity_settings.get("message_invite_enabled"): # Attach invite to message invite_channel = None invite_channel_id = inactivity_settings.get("message_invite_channel") if invite_channel_id: invite_channel = context.guild.get_channel(invite_channel_id) if not invite_channel: invite_channel = context.guild.text_channels[0] # Translate hours to seconds for max_age max_age = 3600 * inactivity_settings.get("message_invite_hours") max_uses = inactivity_settings.get("message_invite_max_uses") reason = inactivity_settings.get("message_invite_reason") try: invite = await invite_channel.create_invite(max_age=max_age, max_uses=max_uses, reason=reason) notification = f"{notification}\n{invite.url}" except discord.HTTPException as e: print(e) failed.append(member) await utils.say(context.channel, content=f"An error happened while creating invite: {e.text} (error code: {e.code})") break try: await utils.say(member, context=context, parse=True, content=notification) except discord.DiscordException as e: failed.append(member) print(e) else: success.append(member) if success: messaged = "\n".join([f"{m.mention} [{m.display_name}]" for m in success]) for embed in utils.split_embeds( title="Notified Members", description=messaged ): await utils.say(context.channel, embed=embed) if failed: not_messaged = "\n".join([f"{m.mention} [{m.display_name}]" for m in failed]) for embed in utils.split_embeds( title="Failed to Notify", description=not_messaged ): await utils.say(context.channel, embed=embed)
async def exempt(self, context): cmd_settings = Settings.command_settings("exempt", context.guild.id) if not cmd_settings.get("enabled"): return args = context.message.split(maxsplit=2) cmd_type = args[1].lower() if cmd_type == "add": pass elif cmd_type == "remove": pass elif cmd_type == "list": pass
async def define(self, context): args = context.message.clean_content.split(maxsplit=1) if 2 > len(args): await utils.say( context.channel, content= f"Type `{context.prefix}{context.command.name} {context.command.usage}` to look up a term in the dictionary." ) return search_term = re.sub(r"\s+", " ", args[1].strip()) search_results = utils.dictionary.regular_lookup(search_term) if search_results: base_url = Settings.command_settings("define")["base_url"] search_url = requote_uri(f"{base_url}{search_term}") reply = discord.Embed(title=f'Define "{search_term}"', url=search_url) reply.set_footer(text=f"Source: {search_url}") try: num_entries = len(search_results) definitions = [] for i, entry in enumerate(search_results): if i > 2: break is_offensive = " *(offensive)*" if entry.is_offensive else "" term_type = entry.term_type definitions.append( f"**{search_term}** {i + 1}/{num_entries} *({term_type})*{is_offensive}" ) definitions.append("".join( ["*", "\n\n".join(entry.short_definitions), "*"])) definitions.append("\n") reply.description = "\n".join(definitions) except AttributeError: # Suggested search terms reply.url = "" suggestions = "\n".join(search_results) reply.description = f"**Did you mean...**\n*{suggestions}*" await utils.say(context.channel, embed=reply) else: await utils.say( context.channel, content=f"Couldn't find a definition for `{search_term}`.")
def __init__(self): """Initialize the game, and create game resources.""" pygame.init() self.settings = Settings() self.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) self.settings.screen_width = self.screen.get_rect().width self.settings.screen_height = self.screen.get_rect().height pygame.display.set_caption("Alien Invasion") # Create an instance to store game statistics. self.stats = GameStats(self) self.ship = Ship(self) self.bullets = pygame.sprite.Group() self.aliens = pygame.sprite.Group() self._create_fleet()
async def on_member_update(self, before, after): milestones = Settings.on_member_update_features( after.guild.id, "role_message") if not milestones or not milestones.get("enabled"): return before_roles = [r.id for r in before.roles] after_roles = [r.id for r in after.roles] for role in milestones["roles"]: if role in after_roles and role not in before_roles: output_channel = discord.utils.get( after.guild.channels, name=milestones["roles"][role]["channel"]) await utils.say(output_channel, context=after, parse=True, content=milestones["roles"][role]["message"]) return
def set_commands(self, *cmds): self.add_cog(commands.Admin(self)) self.add_cog(commands.General(self)) self.add_cog(commands.Statistics(self)) for c in cmds: self.add_cog(c) for command in self.commands: Logger.debug(logger, f"Setting up command '{command.name}'") cmd_settings = Settings.command_settings(command.name) if not cmd_settings: continue command.aliases = cmd_settings.get("aliases", []) command.hidden = not cmd_settings.get("visible", True) command.description = cmd_settings.get("description", "") command.help = cmd_settings.get("help", "") command.usage = cmd_settings.get("usage", "") Logger.debug(logger, f"Command '{command.name}' all set")
def split_embeds(title: str, description: str, delimiter="\n", **kwargs): """Returns a list of embeds split according to Discord character limits. Args: title(str): Title of the embed description(str): Embed description (body text) """ embeds = [] char_count = len(description) max_chars = Settings.app_standards("embed")["description_limit"] current_count = 0 # Minimum length of description as percentage of max_chars (0 to 1) # e.g. 0.5 = description length must be at least 50% of max_chars min_threshold = 0.25 while current_count < char_count: current_description = description[current_count:] if len(current_description) > max_chars: end = current_count + max_chars current_description = description[current_count:end] split_description = current_description.rsplit(delimiter, maxsplit=1) if len(split_description) < 2 or len(split_description[0]) < (max_chars * min_threshold): split_description = current_description.rsplit(maxsplit=1) if len(split_description) > 1: current_count += 1 elif len(split_description) > 1: current_count += len(delimiter) current_description = split_description[0] current_count += len(current_description) embeds.append(discord.Embed( title=title, description=current_description, **kwargs )) return embeds
async def purgeleaderboard(self, context): cmd_settings = Settings.command_settings("purgeleaderboard", context.guild.id) if not cmd_settings.get("enabled"): return mee6 = mee6_py_api.API(context.guild.id) member_ids = [str(m.id) for m in context.guild.members] try: leaderboard_pages = await mee6.levels.get_all_leaderboard_pages() absent_members = [] for page in leaderboard_pages: players = page.get("players") absent_members.extend([f"**{p.get('username')}**#{p.get('discriminator')} — lv{p.get('level')}" for p in players if p.get("id") not in member_ids]) if not absent_members: continue for embed in utils.split_embeds( title=f"MEE6 leaderboard members who left the server", description="\n".join(absent_members) ): await utils.say(context.channel, embed=embed) except mee6_py_api.exceptions.HTTPRequestError: await utils.say(context.channel, content="I couldn't find this server's MEE6 leaderboard.")
def main(): command_prefix = Settings.app_defaults("cmd_prefix") description = Settings.app_defaults("description") ashe = Bot(command_prefix=command_prefix, description=description) ashe.run()
async def on_message(self, message): async def check_content(message, check, custom_message): if check: content = message.clean_content try: await message.delete() except (discord.Forbidden, discord.HTTPException) as e: Logger.warn( logger, f"Unable to delete message at {message.jump_url}. {e}") else: await utils.say( message.author, context=message, parse=True, content= f"{custom_message}\nYour message: ```{content}```") return True return False user = message.author if message.guild: Logger.info( logger, f"({message.guild.name} - {message.channel.name}) {user.name}: {message.content}" ) else: try: Logger.info( logger, f"({message.channel.name}) {user.name}: {message.content}") except AttributeError: Logger.info(logger, f"({user.name}) {message.content}") finally: return mee6_level_up = Settings.on_message_features(message.guild.id, "mee6_level_up") if mee6_level_up and mee6_level_up.get( "enabled") and user.id == mee6_level_up["bot_id"]: result = re.compile(r"{}".format( mee6_level_up["message_pattern"])).search(message.content) if result: mentioned = message.mentions[0] level = int(result.group(1)) Logger.info(logger, f"{mentioned.name} reached level {level}") roles = mee6_level_up["roles"] for r in roles: if level >= r: role = discord.utils.get(message.guild.roles, id=roles[r]["id"]) await mentioned.add_roles( role, reason=f"User reached level {r}") pics_only = Settings.on_message_features(message.guild.id, "pics_only") if pics_only and pics_only.get("enabled"): channel = message.channel if channel.id in pics_only["channels"]: if await check_content( message, not message.attachments, pics_only["channels"][channel.id]["message"]): return await self.process_commands(message)
async def on_ready(self): prefix = Settings.app_defaults("cmd_prefix") Logger.info(logger, f"{self.user.name} (ID: {self.user.id}) is now online.") status = Settings.app_defaults("status").format(prefix=prefix) await self.change_presence(activity=discord.Game(name=status))
async def inactivenotify(self, context): cmd_settings = Settings.command_settings(context.command.name, context.guild.id) if not cmd_settings.get("enabled"): return await self.notify_inactive_members(context)
async def edit(self, context): def check_id(msg): return msg.author.id == context.message.author.id and msg.channel.id == context.channel.id def check_message(msg): return msg.author.id == context.message.author.id and msg.channel.id == context.channel.id cmd_settings = Settings.command_settings("edit", context.guild.id) if not cmd_settings.get("enabled"): return # TODO: Let message ID be passed in first go - skip channel arg and loop through all? # arguments = context.message.content.split() message_id = 0 if not context.message.channel_mentions: await utils.say(context.channel, content="To use, put: ```;edit #[channel name]```, where [channel name] is where the message is.") return CommandStatus.INVALID channel = context.message.channel_mentions[0] await utils.say(context.channel, content="Enter the message ID to be edited:") try: message_id = await self.bot.wait_for("message", timeout=300, check=check_id) message_id = message_id.content except asyncio.TimeoutError: await utils.say(context.channel, content="Time's up.") return CommandStatus.CANCELLED try: message_id = int(message_id) except ValueError: await utils.say(context.channel, content=f"{message_id} is not a valid message ID.") return CommandStatus.INVALID to_edit = await channel.fetch_message(message_id) if not to_edit: await utils.say(context.channel, content=f"Couldn't find message with ID #{message_id}.") return CommandStatus.INVALID elif to_edit.author.id != context.guild.me.id: await utils.say(context.channel, content="I can only edit messages I sent.") return CommandStatus.INVALID else: for preview in utils.split_embeds( title="Message Preview", description=discord.utils.escape_markdown(to_edit.content), url=to_edit.jump_url, timestamp=to_edit.edited_at if to_edit.edited_at else to_edit.created_at ): await utils.say(context.channel, embed=preview) await utils.say(context.channel, content="Enter the newly edited message below.") try: new_edit = await self.bot.wait_for("message", timeout=900, check=check_message) except asyncio.TimeoutError: await utils.say(context.channel, content="Time's up.") return CommandStatus.CANCELLED else: try: await to_edit.edit(content=new_edit.content) except discord.Forbidden: await utils.say(context.channel, content="I'm not allowed to edit this message.") return CommandStatus.FORBIDDEN else: await utils.say(context.channel, content=f"Message edited: {to_edit.jump_url}") return CommandStatus.COMPLETED
Can be a list of DictionaryEntry objects or a list of suggested strings to search. """ if word in self._cache: self._cache[word]["requests"] += 1 return self._cache[word]["entries"] return [] def _check_entries(self): """Remove entry with least amount of requests if cache is at or over limit""" if len(self._cache) >= self.limit: sorted_cache = [k for k, v in sorted(self._cache.items(), key=lambda item: item[1]["requests"])] self._cache.pop(sorted_cache[0], None) regular_cache = DictionaryCache(limit=Settings.command_settings("define")["cache_limit"]) format_tokens = [ { "start_token": "{b}", "end_token": "{\/b}", "pattern": r"", "start_sub": "**", "end_sub": "**" } ] def format_text(text: str): pass def regular_lookup(word: str): """Looks up given word in Merriam-Webster Collegiate dictionary