async def ping_unverified(self, ctx): guild = self.bot.get_guild(Configuration.get_var("guild_id")) try: nonmember_role = guild.get_role( Configuration.get_var("nonmember_role")) welcome_channel = self.bot.get_config_channel( guild.id, Utils.welcome_channel) rules_channel = self.bot.get_config_channel( guild.id, Utils.rules_channel) if welcome_channel and rules_channel: txt = Lang.get_string( "welcome/welcome_msg", user=nonmember_role.mention, rules_channel=rules_channel.mention, accept_emoji=Emoji.get_chat_emoji('CANDLE')) await nonmember_role.edit(mentionable=True) await welcome_channel.send(txt) await nonmember_role.edit(mentionable=False) return True except Exception as ex: Logging.info(f"failed to welcome unverified role.") Logging.error(ex) raise ex return False
async def send_welcome(self, member): guild = Utils.get_home_guild() if member.guild.id != guild.id or self.is_member_verified(member): return False try: welcome_channel = self.bot.get_config_channel( guild.id, Utils.welcome_channel) rules_channel = self.bot.get_config_channel( guild.id, Utils.rules_channel) # Send welcome message in configured language. default to english if welcome_channel and rules_channel: txt = Lang.get_locale_string( "welcome/welcome_msg", Configuration.get_var('broadcast_locale', 'en_US'), user=member.mention, rules_channel=rules_channel.mention, accept_emoji=Emoji.get_chat_emoji('CANDLE')) if self.mute_new_members[member.guild.id]: # add mute notification if mute for new members is on mute_txt = Lang.get_locale_string( "welcome/welcome_mute_msg", Configuration.get_var('broadcast_locale', 'en_US')) txt = f"{txt}\n{mute_txt}" await welcome_channel.send(txt) return True except Exception as ex: Logging.info(f"failed to welcome {member.id}") Logging.error(ex) raise ex return False
async def bug_maintenance(self, ctx, active: bool): member_role = ctx.guild.get_role(Configuration.get_var("member_role")) if active: if len(self.in_progress) > 0: await ctx.send( f"There are {len(self.in_progress)} report(s) in progress. Not activating maintenance mode." ) return await ctx.send("setting bug maintenance mode **on**") else: await ctx.send("setting bug maintenance mode **off**") pass # show/hide maintenance channel maint_message_channel = self.bot.get_channel( Configuration.get_var("bug_maintenance_channel")) overwrite = maint_message_channel.overwrites[member_role] overwrite.read_messages = active await maint_message_channel.set_permissions(member_role, overwrite=overwrite) for name, cid in Configuration.get_var("channels").items(): # show/hide reporting channels channel = self.bot.get_channel(cid) overwrite = channel.overwrites[member_role] overwrite.read_messages = None if active else True await channel.set_permissions(member_role, overwrite=overwrite)
def is_user_event_ignored(self, event): ignored_channels = Configuration.get_var('channels') is_ignored_channel = event.channel_id in ignored_channels.values() guild = self.bot.get_guild(event.guild_id) if not guild: # Don't listen to DMs return True is_bot = event.user_id == self.bot.user.id member = guild.get_member(event.user_id) if member is None: return True # ignore reaction events from departing members is_mod = member and member.guild_permissions.ban_members is_admin = event.user_id in Configuration.get_var("ADMINS", []) has_admin = False for role in member.roles: if role in Configuration.get_var("admin_roles", []): has_admin = True # ignore bot, ignore mod, ignore admin users and admin roles if is_bot or is_mod or is_admin or has_admin or is_ignored_channel: return True return False
async def handle_reaction_change(self, t, reaction, user_id): roles = Configuration.get_var("roles") if reaction in roles: guild = self.bot.get_guild(Configuration.get_var("guild_id")) role = guild.get_role(roles[reaction]) member_role = guild.get_role(Configuration.get_var("member_role")) nonmember_role = guild.get_role( Configuration.get_var("nonmember_role")) member = guild.get_member(user_id) if member is None: return action = getattr(member, f"{t}_roles") try: await action(role) # if acting on member role, toggle corresponding nonmember role if role is member_role: if t == 'add': await member.remove_roles(nonmember_role) else: await member.add_roles(nonmember_role) except Exception as ex: Logging.info("failed") Logging.error(ex) raise ex
async def on_member_join(self, member): if member.guild.id == Configuration.get_var("guild_id"): txt = Configuration.get_var("welcome_msg") welcome_channel = self.bot.get_channel( Configuration.get_var('welcome_channel')) txt = txt.format(user=member.mention) if welcome_channel is not None: await welcome_channel.send(txt)
async def on_member_join(self, member): guild = self.bot.get_guild(Configuration.get_var("guild_id")) if member.guild.id != guild.id: return nonmember_role = guild.get_role( Configuration.get_var("nonmember_role")) await member.add_roles(nonmember_role) await self.send_welcome(member)
async def set_rules_react_message_id(self, ctx, message_id: int): """ Set the message ID of the rules react-to-join message Setting rules message ID also clears reactions, and may be helpful if discord glitches from too many reacts message_id: Message id of rules react message """ rules_channel = self.bot.get_config_channel(ctx.guild.id, Utils.rules_channel) if message_id and not rules_channel: ctx.send( 'Rules channel must be set in order to set rules message. Try `!channel_config set rules_channel [id]`' ) # clear old reactions rules_message_id = Configuration.get_var('rules_react_message_id') try: # Un-setting rules message. Clear reactions from the old one. old_rules = await rules_channel.fetch_message(rules_message_id) await old_rules.clear_reactions() await ctx.send(f"Cleared reactions from:\n{old_rules.jump_url}") # TODO: make this guild-specific except Exception as e: await ctx.send(f"Failed to clear existing rules reactions") try: Configuration.MASTER_CONFIG['rules_react_message_id'] = message_id Configuration.save() except Exception as e: await ctx.send( f"Failed while saving configuration. Operation will continue, but check the logs..." ) if message_id == 0: if rules_message_id == 0: await ctx.send(f"Rules message is already unset") return try: new_rules = await rules_channel.fetch_message(message_id) roles = Configuration.get_var("roles") await new_rules.clear_reactions() for emoji, role_id in roles.items(): # if not Emoji.is_emoji_defined(emoji): # continue # emoji = Emoji.get_chat_emoji(emoji) await new_rules.add_reaction(emoji) await ctx.send( f"Rules message set to {message_id} in channel {rules_channel.mention}" ) except (discord.NotFound, discord.Forbidden, discord.HTTPException) as e: await ctx.send( f"Could not find message id {message_id} in channel {rules_channel.mention}" )
def is_member_verified(self, member): try: guild = self.bot.get_guild(Configuration.get_var("guild_id")) if member.guild.id != guild.id: return True # non-members are "verified" so we don't try to interact with them member_role = guild.get_role(Configuration.get_var("member_role")) if member_role not in member.roles: return False return True except Exception as ex: return True # exceptions are "verified" so we don't try to interact with them *again*
async def handle_reaction_change(self, t, reaction, user_id): roles = Configuration.get_var("roles") if reaction in roles: guild = self.bot.get_guild(Configuration.get_var("guild_id")) role = guild.get_role(roles[reaction]) member = guild.get_member(user_id) action = getattr(member, f"{t}_roles") try: await action(role) except Exception as ex: Logging.info("failed") Logging.error(ex) raise ex
async def unload(self, ctx, cog: str): if cog in ctx.bot.cogs: self.bot.unload_extension(f"cogs.{cog}") if cog in Configuration.MASTER_CONFIG["cogs"]: Configuration.get_var("cogs").remove(cog) Configuration.save() await ctx.send(f'**{cog}** has been unloaded.') await Logging.bot_log( f'**{cog}** has been unloaded by {ctx.author.name}') Logging.info(f"{cog} has been unloaded") else: await ctx.send( f"{Emoji.get_chat_emoji('NO')} I can't find that cog.")
async def permission_manage_bot(self, ctx): db_admin = BotAdmin.get_or_none(userid=ctx.author.id) is not None # Logging.info(f"db_admin: {'yes' if db_admin else 'no'}") owner = await ctx.bot.is_owner(ctx.author) # Logging.info(f"owner: {'yes' if owner else 'no'}") in_admins = ctx.author.id in Configuration.get_var("ADMINS", []) # Logging.info(f"in_admins: {'yes' if in_admins else 'no'}") has_admin_role = False if ctx.guild: for role in ctx.author.roles: if role in Configuration.get_var("admin_roles", []): has_admin_role = True # Logging.info(f"has_admin_role: {'yes' if has_admin_role else 'no'}") return db_admin or owner or in_admins or has_admin_role
async def count_shadows(self, ctx): """ Count members who have shadow role """ members = self.bot.get_all_members() nonmember_role = ctx.guild.get_role( Configuration.get_var("nonmember_role")) await ctx.send(f"counting members who have the shadow role...") count = 0 multi_role_count = 0 no_role_count = 0 for member in members: if member.bot or member.guild.id != ctx.guild.id: # Don't count bots or members of other guilds continue if len(member.roles) == 0: no_role_count = no_role_count + 1 if nonmember_role in member.roles: count = count + 1 if len(member.roles) > 1: # count members who have shadow role AND other role(s) multi_role_count = multi_role_count + 1 content = f"There are {count} members with \"{nonmember_role.name}\" role.\n" content += f"Among them, {multi_role_count} members have \"{nonmember_role.name}\" role *and* 1 or more other roles.\n" content += f"There are {no_role_count} members with no roles assigned." await ctx.send(content)
async def give_shadow(self, ctx, time_delta: typing.Optional[int] = 1, add_role: bool = False): """ Add non-member role to members with no role time_delta: how far back (in hours) to search for members with no roles add_role: """ recent = self.fetch_non_role(time_delta) string_name = 'welcome/darkness' if (len( recent['unverified']) == 1) else 'welcome/darkness_plural' await ctx.send( Lang.get_string(string_name, unverified=len(recent['unverified']), time_delta=time_delta, too_old=len(recent['too_old']))) if add_role: nonmember_role = ctx.guild.get_role( Configuration.get_var("nonmember_role")) # slowly add roles, since this may be a large number of members count = 0 try: for member in recent['unverified']: await member.add_roles(nonmember_role) count += 1 await asyncio.sleep(0.3) except Exception as ex: await Utils.handle_exception("problem adding nonmember role", self, ex) string_name = 'welcome/darkened' if count == 1 else 'welcome/darkened_plural' await ctx.send(Lang.get_string(string_name, count=count))
async def on_member_update(self, before, after): try: if before.pending and not after.pending: # member just accepted rules # don't add a role or this defeats the cool-down. # wait for member to talk, then add a role. # TODO: metrics logging # member_role = after.guild.get_role(Configuration.get_var("member_role")) # await after.add_roles(member_role) # print(f"{after.display_name} is a member now") pass except Exception as e: pass try: # Enforce member role on any role changes - in case other bot assigns a role. # TODO: should this be configurable on|off? member_role = before.guild.get_role( Configuration.get_var("member_role")) member_before = member_role in before.roles member_after = member_role in after.roles if before.roles != after.roles: if (not member_before and not member_after) or (member_before and not member_after): await after.add_roles(member_role) except Exception as e: pass
async def on_ready(self): if not self.loaded: Logging.BOT_LOG_CHANNEL = self.get_channel( Configuration.get_var("log_channel")) Emoji.initialize(self) for cog in Configuration.get_var("cogs"): try: self.load_extension("cogs." + cog) except Exception as e: await Utils.handle_exception(f"Failed to load cog {cog}", self, e) Logging.info("Cogs loaded") self.loop.create_task(self.keepDBalive()) self.loaded = True await Logging.bot_log("Journey bot surfing through the dunes!")
async def send_report(): # save report in the database br = BugReport.create(reporter=user.id, platform=platform, deviceinfo=deviceinfo, platform_version=platform_version, branch=branch, app_version=app_version, app_build=app_build, title=title, steps=steps, expected=expected, actual=actual, additional=additional_text) for url in attachment_links: Attachments.create(report=br, url=url) # send report channel_name = f"{platform}_{branch}".lower() report_id_saved = False attachment_id_saved = False user_reported_channels = list() all_reported_channels = list() selected_platform = BugReportingPlatform.get(platform=platform, branch=branch) for row in BugReportingChannel.select().where(BugReportingChannel.platform == selected_platform): report_channel = self.bot.get_channel(row.channelid) message = await report_channel.send( content=Lang.get_locale_string("bugs/report_header", ctx, id=br.id, user=user.mention), embed=report) attachment = None if len(attachment_links) != 0: key = "attachment_info" if len(attachment_links) == 1 else "attachment_info_plural" attachment = await report_channel.send( Lang.get_locale_string(f"bugs/{key}", ctx, id=br.id, links="\n".join(attachment_links))) if report_channel.guild.id == Configuration.get_var('guild_id'): # Only save report and attachment IDs for posts in the official server if not report_id_saved and not attachment_id_saved: if attachment is not None: br.attachment_message_id = attachment.id attachment_id_saved = True br.message_id = message.id report_id_saved = True br.save() user_reported_channels.append(report_channel.mention) else: # guild is not the official server. if author is member, include user_reported_channels this_guild = self.bot.get_guild(report_channel.guild.id) if this_guild.get_member(user.id) is not None: user_reported_channels.append(report_channel.mention) all_reported_channels.append(report_channel) channels_mentions = [] channels_ids = set() if not all_reported_channels: await Logging.bot_log(f"no report channels for bug report #{br.id}") for report_channel in all_reported_channels: channels_mentions.append(report_channel.mention) channels_ids.add(report_channel.id) await channel.send( Lang.get_locale_string("bugs/report_confirmation", ctx, channel_info=', '.join(channels_mentions))) await self.send_bug_info(*channels_ids)
async def sweep_trash(self, user): await asyncio.sleep( Configuration.get_var("bug_trash_sweep_minutes") * 60) if user.id in self.in_progress: if not self.in_progress[user.id].done() or not self.in_progress[ user.id].cancelled(): await user.send(Lang.get_string("bugs/sweep_trash")) await self.delete_progress(user.id)
def permission_official(member_id, permission_name): # ban permission on official server - sort of a hack to propagate perms # TODO: better permissions model try: official_guild = BOT.get_guild(Configuration.get_var("guild_id")) official_member = official_guild.get_member(member_id) return getattr(official_member.guild_permissions, permission_name) except Exception: return False
async def on_ready(self): Logging.info(f"Skybot... {'RECONNECT!' if self.loaded else 'STARTUP!'}") if self.loaded: Logging.info("Skybot reconnect") return Logging.BOT_LOG_CHANNEL = self.get_channel(Configuration.get_var("log_channel")) Emoji.initialize(self) for cog in Configuration.get_var("cogs"): try: self.load_extension("cogs." + cog) except Exception as e: await Utils.handle_exception(f"Failed to load cog {cog}", self, e) Logging.info("Cogs loaded") self.db_keepalive = self.loop.create_task(self.keepDBalive()) self.loaded = True await Logging.bot_log("Skybot soaring through the skies!")
async def startup_cleanup(self): for name, cid in Configuration.get_var("channels").items(): channel = self.bot.get_channel(cid) shutdown_id = Configuration.get_persistent_var(f"{name}_shutdown") if shutdown_id is not None: message = await channel.fetch_message(shutdown_id) if message is not None: await message.delete() Configuration.set_persistent_var(f"{name}_shutdown", None) await self.send_bug_info(name)
async def on_member_remove(self, member): # clear rules reactions roles = Configuration.get_var("roles") guild = self.bot.get_guild(member.guild.id) self.remove_member_from_cooldown(guild.id, member.id) rules_channel = self.bot.get_config_channel(guild.id, Utils.rules_channel) rules_message_id = Configuration.get_var('rules_react_message_id') try: rules = await rules_channel.fetch_message(rules_message_id) except Exception as e: return for reaction, role_id in roles.items(): try: await rules.remove_reaction(reaction, member) except Exception as e: pass
async def on_ready(self): if self.loaded: Logging.info(f"{self.my_name} reconnect") return Logging.BOT_LOG_CHANNEL = self.get_channel( Configuration.get_var("log_channel")) Emoji.initialize(self) for cog in Configuration.get_var("cogs"): try: self.load_extension("cogs." + cog) except Exception as e: await Utils.handle_exception(f"Failed to load cog {cog}", self, e) Logging.info("Cogs loaded") self.db_keepalive = self.loop.create_task(self.keepDBalive()) self.loaded = True await Logging.bot_log(f"{self.my_name} has started. Time to bot!")
def is_member_unverified(self, member): try: guild = Utils.get_home_guild() if member.guild.id != guild.id: return True # non-members are "verified" so we don't try to interact with them nonmember_role = guild.get_role( Configuration.get_var("nonmember_role")) if nonmember_role not in member.roles: return False return True except Exception as ex: return True # exceptions are "verified" so we don't try to interact with them *again*
async def on_member_join(self, member): if self.mute_new_members and not self.discord_verification_flow: self.bot.loop.create_task(self.mute_new_member(member)) if self.discord_verification_flow: # do not welcome new members when using discord verification # set entry_channel to use discord verification return # Only send welcomes for configured guild i.e. sky official # TODO: retool to allow any guild to welcome members? guild = self.bot.get_guild(Configuration.get_var("guild_id")) if member.guild.id != guild.id: return # add the nonmember role nonmember_role = guild.get_role( Configuration.get_var("nonmember_role")) await member.add_roles(nonmember_role) # send the welcome message await self.send_welcome(member)
async def create_site(self): port = Configuration.get_var('METRICS_PORT', 8080) await asyncio.sleep(10) print("starting metrics server") metrics_app = web.Application() metrics_app.add_routes([web.get("/metrics", self.serve_metrics)]) runner = web.AppRunner(metrics_app) await self.bot.loop.create_task(runner.setup()) site = web.TCPSite(runner, port=port, host='localhost') await site.start() self.metric_server = site
async def on_message(self, message: discord.Message): if message.author.bot or message.author.guild_permissions.mute_members: return member_role = message.guild.get_role( Configuration.get_var("member_role")) if member_role in message.author.roles: return welcome_channel = self.bot.get_config_channel(message.guild.id, Utils.welcome_channel) rules_channel = self.bot.get_config_channel(message.guild.id, Utils.rules_channel) log_channel = self.bot.get_config_channel(message.guild.id, Utils.log_channel) if not welcome_channel or not rules_channel: # ignore when channels not configured return if message.channel.id != welcome_channel.id: return now = datetime.now().timestamp() then = 0 grace_period = 10 * 60 # 3 minutes try: was_welcomed = self.welcome_talkers[message.guild.id][ message.author.id] then = was_welcomed + grace_period except Exception as ex: pass if then > now: # print("it hasn't been 10 minutes...") return # record the time so member won't be pinged again too soon if they keep talking self.welcome_talkers[message.guild.id][message.author.id] = now await welcome_channel.send( Lang.get_string("welcome/welcome_help", author=message.author.mention, rules_channel=rules_channel.mention)) # ping log channel with detail if log_channel: await log_channel.send( f"{message.author.mention} spoke in {welcome_channel.mention} ```{message.content}```" )
async def on_message(self, message: discord.Message): if message.author.bot: return if not hasattr(message.channel, "guild") or message.channel.guild is None: return prefix = Configuration.get_var("bot_prefix") if message.content.startswith(prefix, 0): for trigger in self.commands[message.guild.id]: if message.content.lower() == prefix + trigger or ( message.content.lower().startswith( trigger, len(prefix)) and message.content.lower()[len(prefix + trigger)] == " "): command_content = self.commands[ message.guild.id][trigger].replace("@", "@\u200b") await message.channel.send(command_content)
async def on_message(self, message: discord.Message): prefix = Configuration.get_var("bot_prefix") is_boss = await self.cog_check(message) command_context = message.content.startswith(prefix, 0) and is_boss not_in_guild = not hasattr(message.channel, "guild") or message.channel.guild is None if message.author.bot or command_context or not_in_guild: return m = self.bot.metrics pattern = re.compile(self.words[message.guild.id], re.IGNORECASE) # find all matches and reduce to unique set words = set(pattern.findall(message.content)) for word in words: # increment counters word = str(word).lower() m.word_counter.labels(word=word).inc()
async def reset_active(self, ctx): """Reset active bug reports. Bot will attempt to DM users whose reports are cancelled.""" to_kill = len(self.in_progress) active_keys = [key for key in self.in_progress.keys()] for uid in active_keys: try: await self.delete_progress(uid) user = self.bot.get_user(uid) await user.send(Lang.get_locale_string('bugs/user_reset', Configuration.get_var('broadcast_locale', 'en_US'))) await ctx.send(Lang.get_locale_string('bugs/reset_success', uid=uid)) except Exception as e: await ctx.send(Lang.get_locale_string('bugs/reset_fail', uid=uid)) self.in_progress = dict() await ctx.send(Lang.get_locale_string('bugs/dead_bugs_cleaned', ctx, active_keys=len(active_keys), in_progress=len(self.in_progress)))