async def news_auth_add(self, ctx: Context, user: Member, channel: TextChannel, notification_role: Optional[Role]): """ authorize a new user to send news to a specific channel """ if await db.exists( select(NewsAuthorization).filter_by(user_id=user.id, channel_id=channel.id)): raise CommandError(t.news_already_authorized) if not channel.permissions_for(channel.guild.me).send_messages: raise CommandError(t.news_not_added_no_permissions) role_id = notification_role.id if notification_role is not None else None await NewsAuthorization.create(user.id, channel.id, role_id) embed = Embed(title=t.news, colour=Colors.News, description=t.news_authorized) await reply(ctx, embed=embed) await send_to_changelog( ctx.guild, t.log_news_authorized(user.mention, channel.mention))
async def disallow(self, ctx, role: DiscordRole): session = Session() obj = to_sql(session, role, Role) data = self.get_data(obj, DEFAULT_ROLE_DATA) if data: self.set_data(session, obj, False) session.close() embed = generate_embed_template(ctx, 'Role Disallowed Successfully') embed.description = str(role) await ctx.send(embed=embed) return session.close() raise CommandError('Role `{}` is already disallowed'.format(str(role)))
async def reactionpin_add(self, ctx: Context, channel: TextChannel): """ add channel to whitelist """ if await db_thread(db.get, ReactionPinChannel, channel.id) is not None: raise CommandError(translations.channel_already_whitelisted) await db_thread(ReactionPinChannel.create, channel.id) embed = Embed( title=translations.reactionpin, colour=Colours.ReactionPin, description=translations.channel_whitelisted ) await ctx.send(embed=embed) await send_to_changelog(ctx.guild, translations.f_log_channel_whitelisted_rp(channel.mention))
async def logging_maxage(self, ctx: Context, days: int): if days != -1 and not 0 < days < (1 << 31): raise CommandError(tg.invalid_duration) await LoggingSettings.maxage.set(days) embed = Embed(title=t.logging, color=Colors.Logging) if days == -1: embed.description = t.maxage_set_disabled await send_to_changelog(ctx.guild, t.maxage_set_disabled) else: embed.description = t.maxage_set(cnt=days) await send_to_changelog(ctx.guild, t.maxage_set(cnt=days)) await reply(ctx, embed=embed)
async def add_channel(self, ctx: Context, channel: TextChannel): """ add channel to whitelist """ if await run_in_thread(db.get, ReactionPinChannel, channel.id) is not None: raise CommandError(translations.channel_already_whitelisted) await run_in_thread(ReactionPinChannel.create, channel.id) await ctx.send(translations.channel_whitelisted) await send_to_changelog( ctx.guild, translations.f_log_channel_whitelisted_rp(channel.mention))
async def permissions_set(self, ctx: Context, permission_name: str, level: PermissionLevelConverter): level: BasePermissionLevel for permission in get_permissions(): if permission.fullname.lower() == permission_name.lower(): break else: raise CommandError(t.invalid_permission) max_level: BasePermissionLevel = await Config.PERMISSION_LEVELS.get_permission_level( ctx.author) if max(level.level, (await permission.resolve()).level) > max_level.level: raise CommandError(t.cannot_manage_permission_level) await permission.set(level) description = permission.fullname, level.description embed = Embed(title=t.permissions_title, colour=Colors.Permissions, description=t.permission_set(*description)) await reply(ctx, embed=embed) await send_to_changelog(ctx.guild, t.log_permission_set(*description))
async def aoc_role_rank(self, ctx: Context, rank: int): """ set the minimum rank users need to get the role """ if not 1 <= rank <= 200: raise CommandError(t.invalid_rank) await AdventOfCodeSettings.rank.set(rank) await self.update_roles(await AOCConfig.get_leaderboard(disable_hook=True)) await reply(ctx, t.rank_set) await send_to_changelog(ctx.guild, t.log_rank_set(rank))
async def send_discohook(self, ctx: Context, channel: TextChannel, *, discohook_url: str): try: messages: list[MessageContent] = [ msg for msg in await load_discohook_link(discohook_url) if not msg.is_empty ] except DiscoHookError: raise CommandError(t.discohook_invalid) if not messages: raise CommandError(t.discohook_empty) check_message_send_permissions(channel, check_embed=any(m.embeds for m in messages)) try: for message in messages: content: str | None = message.content for embed in message.embeds or [None]: await channel.send(content=content, embed=embed) content = None except (HTTPException, Forbidden): raise CommandError(t.msg_could_not_be_sent) await add_reactions(ctx.message, "white_check_mark")
async def auth_add(ctx: Context, *, role: Role): """ authorize role to control this bot """ authorization: Optional[AuthorizedRole] = await run_in_thread( db.get, AuthorizedRole, role.id) if authorization is not None: raise CommandError(translations.f_already_authorized(role)) await run_in_thread(AuthorizedRole.create, role.id) await ctx.send(translations.f_role_authorized(role)) await send_to_changelog(ctx.guild, translations.f_log_role_authorized(role))
async def prepare_player(self, ctx: Context) -> Any: """Ensure that the user can run these commands.""" if ctx.voice_client is None: if ctx.author.voice: await ctx.author.voice.channel.connect() else: return await ctx.send("You are not connected to a voice channel!") raise CommandError("User is not connected to a voice channel.") elif ctx.author.voice and ctx.author.voice.channel != ctx.voice_client.channel: await self.save_quit_player(ctx.voice_client, ctx.guild.id) await self.join_continue_player(ctx.author.voice.channel) elif ctx.voice_client.is_playing(): self.players[ctx.guild.id].seconds = time() - ctx.voice_client.started ctx.voice_client.stop()
async def invites_add(self, ctx: Context, invite: Invite, applicant: Member): """ allow a new discord server """ if invite.guild is None: raise CommandError(translations.invalid_invite) guild: Guild = invite.guild if await db_thread(db.get, AllowedInvite, guild.id) is not None: raise CommandError(translations.server_already_whitelisted) await db_thread(AllowedInvite.create, guild.id, invite.code, guild.name, applicant.id, ctx.author.id) await db_thread(InviteLog.create, guild.id, guild.name, applicant.id, ctx.author.id, True) embed = Embed(title=translations.invites, description=translations.server_whitelisted, color=Colours.AllowedInvites) await ctx.send(embed=embed) await send_to_changelog( ctx.guild, translations.f_log_server_whitelisted(guild.name))
async def verification_password(self, ctx: Context, *, password: str): """ configure verification password """ if len(password) > 256: raise CommandError(translations.password_too_long) await run_in_thread(Settings.set, str, "verification_password", password) await ctx.send(translations.verification_password_configured) await send_to_changelog( ctx.guild, translations.f_log_verification_password_configured(password))
async def walk(self, data, curr, path: typing.List[str], ctx: Context): if len(path) == 0: return curr if curr == 'Boolean' or curr == 'String': await self.bot.add_reaction(ctx.message, u'❌') return None if 'Compound' in curr: cpd = data['compound_arena'][curr['Compound']] if path[0] in cpd['fields']: return await self.walk(data, cpd['fields'][path[0]]['nbttype'], path[1:], ctx) else: if 'supers' in cpd: if cpd['supers'] == None: return None if 'Compound' in cpd['supers']: return await self.walk( data, {'Compound': cpd['supers']['Compound']}, path, ctx) elif 'Registry' in cpd['supers']: return await self.walk( data, { 'Compound': data.registries[cpd['supers']['Registry'] ['target']][1] }, path, ctx) else: # This shouldn't happen raise CommandError('Unknown key in {}'.format( cpd.supers)) else: await self.bot.add_reaction(ctx.message, u'🤷') return None elif 'List' in curr: return await self.walk(data, curr['List']['value_type'], path, ctx) elif 'Index' in curr: return await self.walk( data, data['compound_arena'][data['registries'][ cpd['supers']['Registry']['target']][1]], path, ctx) elif 'Or' in curr: for v in curr['Or']: val = await self.walk(data, v, path, ctx) if val: return val await self.bot.add_reaction(ctx.message, u'❌') return None else: await self.bot.add_reaction(ctx.message, u'❌') return None
async def unregister_role(self, ctx: Context, *, topics: str): """ delete one or more topics """ guild: Guild = ctx.guild roles: List[Role] = [] btp_roles: List[BTPRole] = [] names = split_topics(topics) if not names: await ctx.send_help(self.register_role) return for topic in names: for role in guild.roles: if role.name.lower() == topic.lower(): break else: raise CommandError(translations.f_topic_not_registered(topic)) if (btp_role := await run_in_thread(db.get, BTPRole, role.id)) is None: raise CommandError(translations.f_topic_not_registered(topic)) roles.append(role) btp_roles.append(btp_role)
async def autokick_delay(self, ctx: Context, seconds: int): """ configure autokick delay (in seconds) """ if not 0 < seconds < 300: raise CommandError(translations.invalid_duration) await Settings.set(int, "autokick_delay", seconds) embed = Embed(title=translations.autokick, description=translations.autokick_delay_configured, colour=Colours.AutoMod) await ctx.send(embed=embed) await send_to_changelog( ctx.guild, translations.f_log_autokick_delay_configured(seconds))
async def clear(self, ctx: Context, count: int): channel: TextChannel = ctx.channel if count not in range(1, 101): raise CommandError(t.count_between) if not await Confirmation().run(ctx, t.confirm(channel.mention, cnt=count)): return messages = (await channel.history(limit=count + 2).flatten())[2:] try: await channel.delete_messages(messages) except (Forbidden, NotFound, HTTPException): raise CommandError(t.msg_not_deleted) await reply( ctx, embed=Embed( title=t.clear_channel, description=t.deleted_messages(channel.mention, cnt=count), color=Colors.MessageCommands, ), ) await send_alert(ctx.guild, t.log_cleared(ctx.author.mention, channel.mention, cnt=count))
async def parse_topics(guild: Guild, topics: str, author: Member) -> List[Role]: roles: List[Role] = [] all_topics: List[Role] = await list_topics(guild) for topic in split_topics(topics): for role in guild.roles: if role.name.lower() == topic.lower(): if role in all_topics: break if not role.managed and role > guild.me.top_role: raise CommandError( translations.f_youre_not_the_first_one( topic, author.mention)) else: if all_topics: best_match = min((r.name for r in all_topics), key=lambda a: calculate_edit_distance( a.lower(), topic.lower())) raise CommandError( translations.f_topic_not_found_did_you_mean( topic, best_match)) raise CommandError(translations.f_topic_not_found(topic)) roles.append(role) return roles
async def remove(self, ctx: Context, member: Member): """ remove a member from a private voice channel """ _, _, voice_channel, text_channel = await self.get_dynamic_voice_channel( ctx.author, True) if member in (ctx.author, self.bot.user): raise CommandError(translations.cannot_remove_member) await voice_channel.set_permissions(member, overwrite=None) team_role = await run_in_thread(Settings.get, int, "team_role") if member.guild_permissions.administrator or any( role.id == team_role for role in member.roles): raise CommandError(translations.member_could_not_be_kicked) if member.voice is not None and member.voice.channel == voice_channel: await member.move_to(None) if text_channel is not None: await text_channel.send( translations.f_user_removed_from_private_voice(member.mention)) if text_channel != ctx.channel: await ctx.send( translations.user_removed_from_private_voice_response)
async def logging_exclude_add(self, ctx: Context, channel: TextChannel): """ exclude a channel from logging """ if await db_thread(LogExclude.exists, channel.id): raise CommandError(translations.already_excluded) await db_thread(LogExclude.add, channel.id) embed = Embed(title=translations.excluded_channels, description=translations.excluded, colour=Colours.Logging) await ctx.send(embed=embed) await send_to_changelog(ctx.guild, translations.f_log_excluded(channel.mention))
async def logging_edit_mindist(self, ctx: Context, mindist: int): """ change the minimum edit distance between the old and new content of the message to be logged """ if mindist <= 0: raise CommandError(translations.min_diff_gt_zero) await Settings.set(int, "logging_edit_mindiff", mindist) embed = Embed(title=translations.logging, description=translations.f_edit_mindiff_updated(mindist), color=Colours.Logging) await ctx.send(embed=embed) await send_to_changelog(ctx.guild, translations.f_log_mindiff_updated(mindist))
async def user_notes_remove(self, ctx: Context, note_id: int): user_note: Optional[UserNote] = await db.get(UserNote, id=note_id) if not user_note: raise CommandError(t.note_not_found) if not await Confirmation().run( ctx, t.confirm(f"<@{user_note.member_id}>", user_note.content)): return await db.delete(user_note) await send_to_changelog( ctx.guild, t.removed_note(ctx.author.mention, f"<@{user_note.member_id}>", user_note.content))
async def regex(self, ctx: Context, pattern: ContentFilterConverter, *, new_regex: RegexConverter): pattern: BadWord new_regex: str if await db.exists(filter_by(BadWord, regex=new_regex)): raise CommandError(t.already_blacklisted) old = pattern.regex pattern.regex = new_regex await sync_redis() await add_reactions(ctx.message, "white_check_mark") await send_to_changelog(ctx.guild, t.log_regex_updated(old, pattern.regex))
async def convert(self, ctx, argument): parser_settings = { "RETURN_AS_TIMEZONE_AWARE": True, "PREFER_DATES_FROM": "future", "TIMEZONE": "CET" } argument = argument.replace('for', 'in') argument = argument.replace('voor', 'over') argument = argument.replace('tot', 'om') try: date = parse(argument, languages=['en', 'nl'], settings=parser_settings) except Exception as e: raise CommandError(e) tz = timezone('Europe/Amsterdam') if date <= datetime.now(tz=tz): if ':' in argument: date = date + timedelta(days=1) else: date = datetime.now(tz=tz) + (datetime.now(tz=tz) - date) if not date: raise CommandError("Invalid Date") return date
async def verification_password(self, ctx: Context, *, password: str): """ configure verification password """ if len(password) > 256: raise CommandError(t.password_too_long) await VerificationSettings.password.set(password) embed = Embed(title=t.verification, description=t.verification_password_configured, colour=Colors.Verification) await reply(ctx, embed=embed) await send_to_changelog( ctx.guild, t.log_verification_password_configured(password))
async def logging_exclude_remove(self, ctx: Context, channel: TextChannel): """ remove a channel from exclude list """ if not await db_thread(LogExclude.exists, channel.id): raise CommandError(translations.not_excluded) await db_thread(LogExclude.remove, channel.id) embed = Embed(title=translations.excluded_channels, description=translations.unexcluded, colour=Colours.Logging) await ctx.send(embed=embed) await send_to_changelog(ctx.guild, translations.f_log_unexcluded(channel.mention))
async def cleverbot_add(self, ctx: Context, channel: TextChannel): """ add channel to whitelist """ if await db.get(CleverBotChannel, channel=channel.id) is not None: raise CommandError(t.channel_already_whitelisted) await CleverBotChannel.create(channel.id) embed = Embed(title=t.cleverbot, description=t.channel_whitelisted, colour=Colors.CleverBot) await reply(ctx, embed=embed) await send_to_changelog(ctx.guild, t.log_channel_whitelisted(channel.mention))
async def report(self, ctx: Context, member: Member, *, reason: str): """ report a member """ if len(reason) > 900: raise CommandError(translations.reason_too_long) await run_in_thread(Report.create, member.id, str(member), ctx.author.id, reason) await ctx.send(translations.reported_response) await send_to_changelog( ctx.guild, translations.f_log_reported(ctx.author.mention, member.mention, member, reason))
def query_mc_server(*, server_ip: str, port: int = 25565) -> dict: """Gets information from a minecraft server Keyword Arguments ---------- ip = ip: str The IP of the server to query port = 25565 : int, optional The port of the server to query, by default 25565 Returns ------- serverinfo : dict Structured below: { "MaxPlayers": int, "MOTD": str, "Playerlist": list, "Players": int, "Plugins": list, "Software": str, "Version": str, "Status": str } Raises ------ InvalidMcServer This is raised if the server ip or port is invalid TimeoutError This is raised if the query took too long CommandError This is raised as a generic error response """ res = requests.get(f"https://api.minetools.eu/query/{server_ip}/{port}") res = json.loads(res.text) if res["status"] == "ERR": if res["error"] == "[Errno -2] Name or service not known": raise InvalidMcServer elif res["error"] == "timed out": raise TimeoutError else: raise CommandError(res["error"]) return res
async def aoc_link_remove(self, ctx: Context, *, member: Union[Member, str]): """ remove a link """ if isinstance(member, Member): link = await db_thread(db.get, AOCLink, member.id) else: aoc_member = await AOCConfig.get_member(member) link = aoc_member and await db_thread(db.first, AOCLink, aoc_id=aoc_member["id"]) if not link: raise CommandError(translations.aoc_link_not_found) await db_thread(db.delete, link) await ctx.send(translations.aoc_link_removed)
async def reddit_interval(self, ctx: Context, hours: int): """ change lookup interval (in hours) """ if not 0 < hours < (1 << 31): raise CommandError(t.invalid_interval) await RedditSettings.interval.set(hours) await self.start_loop(hours) embed = Embed(title=t.reddit, colour=Colors.Reddit, description=t.reddit_interval_set) await reply(ctx, embed=embed) await send_to_changelog(ctx.guild, t.log_reddit_interval_set(cnt=hours))