From 807434ab45a8f647346458473ba00f9f39b9b916 Mon Sep 17 00:00:00 2001 From: Dhruva Shaw Date: Mon, 17 Jan 2022 18:31:33 +0530 Subject: [PATCH] idk some update --- .../bot_files/cogs/moderation/moderation.py | 252 +++++++++++++++++- .../bot_files/cogs/setup_server.py | 173 ------------ .../bot_files/lib/classes/setup/__init__.py | 1 - .../{setup/ban.py => setup_classes.py} | 47 ++-- minato_namikaze/bot_files/lib/util/context.py | 6 +- 5 files changed, 266 insertions(+), 213 deletions(-) delete mode 100644 minato_namikaze/bot_files/cogs/setup_server.py rename minato_namikaze/bot_files/lib/classes/{setup/ban.py => setup_classes.py} (56%) diff --git a/minato_namikaze/bot_files/cogs/moderation/moderation.py b/minato_namikaze/bot_files/cogs/moderation/moderation.py index 44ace70b..413f6da7 100644 --- a/minato_namikaze/bot_files/cogs/moderation/moderation.py +++ b/minato_namikaze/bot_files/cogs/moderation/moderation.py @@ -16,6 +16,8 @@ MemberID, check_if_warning_system_setup, has_permissions, + FutureTime, + format_relative ) @@ -66,7 +68,7 @@ async def kick(self, *, reason=None): """A command which kicks a given user""" - member = ctx.get_user(member) + if not await ctx.prompt( f"Are you sure that you want to **kick** {member} from the guild?", author_id=ctx.author.id, @@ -95,7 +97,7 @@ async def ban( reason: ActionReason = None, ): """A command which bans a given user""" - member = ctx.get_user(member) + if not await ctx.prompt( f"Are you sure that you want to **ban** {member} from the guild?", author_id=ctx.author.id, @@ -247,7 +249,7 @@ async def banlist(self, ctx, *, member: Optional[Union[str, MemberID, @has_permissions(kick_members=True) async def softban(self, ctx, - member: MemberID, + member: Union[MemberID, discord.Member], *, reason: ActionReason = None): """Soft bans a member from the server. @@ -261,7 +263,7 @@ async def softban(self, To use this command you must have Kick Members permissions. """ - member = ctx.get_user(member) + if not await ctx.prompt( f"Are you sure that you want to **softban** {member} from the guild?", author_id=ctx.author.id, @@ -273,7 +275,7 @@ async def softban(self, await ctx.guild.ban(member, reason=reason) await ctx.guild.unban(member, reason=reason) - await ctx.send("\N{OK HAND SIGN}") + await ctx.send("\N{OK HAND SIGN}", delete_after=4) # Unban @commands.command() @@ -293,8 +295,6 @@ async def unban(self, To use this command you must have Ban Members permissions. """ - - member = ctx.get_user(member) if not await ctx.prompt( f"Are you sure that you want to **unban** {member} from the guild?", author_id=ctx.author.id, @@ -321,7 +321,7 @@ async def unban(self, async def ar( self, ctx, - member: Optional[Union[MemberID, discord.Member]], + member: Union[MemberID, discord.Member], role: Union[int, discord.Role], *, reason: ActionReason = None, @@ -329,7 +329,7 @@ async def ar( """Adds a role to a given user""" if member is None: member = ctx.message.author - member = ctx.get_user(member) + role = ctx.get_roles(role) if not await ctx.prompt( @@ -346,6 +346,226 @@ async def ar( description=f"I have added the role '{role.mention}' to {member.mention}!", ) await ctx.send(embed=embed) + + @commands.command() + @commands.guild_only() + @has_permissions(ban_members=True) + async def multiban(self, ctx, members: commands.Greedy[MemberID], *, reason: ActionReason = None): + """Bans multiple members from the server. + This only works through banning via ID. + In order for this to work, the bot must have Ban Member permissions. + To use this command you must have Ban Members permission. + """ + if not await ctx.prompt( + "Are you sure that you want to **ban multiple** members?", + author_id=ctx.author.id): + return + + if reason is None: + reason = f'Action done by {ctx.author} (ID: {ctx.author.id})' + + total_members = len(members) + if total_members == 0: + return await ctx.send('Missing members to ban.') + + confirm = await ctx.prompt(f'This will ban **{plural(total_members):member}**. Are you sure?', reacquire=False) + if not confirm: + return await ctx.send('Aborting.') + + failed = 0 + for member in members: + try: + await ctx.guild.ban(member, reason=reason) + except discord.HTTPException: + failed += 1 + + await ctx.send(f'Banned {total_members - failed}/{total_members} members.') + + @commands.command() + @commands.guild_only() + @has_permissions(ban_members=True) + async def massban(self, ctx, *, args): + """Mass bans multiple members from the server. + This command has a powerful "command line" syntax. To use this command + you and the bot must both have Ban Members permission. **Every option is optional.** + Users are only banned **if and only if** all conditions are met. + The following options are valid. + `--channel` or `-c`: Channel to search for message history. + `--reason` or `-r`: The reason for the ban. + `--regex`: Regex that usernames must match. + `--created`: Matches users whose accounts were created less than specified minutes ago. + `--joined`: Matches users that joined less than specified minutes ago. + `--joined-before`: Matches users who joined before the member ID given. + `--joined-after`: Matches users who joined after the member ID given. + `--no-avatar`: Matches users who have no avatar. (no arguments) + `--no-roles`: Matches users that have no role. (no arguments) + `--show`: Show members instead of banning them (no arguments). + Message history filters (Requires `--channel`): + `--contains`: A substring to search for in the message. + `--starts`: A substring to search if the message starts with. + `--ends`: A substring to search if the message ends with. + `--match`: A regex to match the message content to. + `--search`: How many messages to search. Default 100. Max 2000. + `--after`: Messages must come after this message ID. + `--before`: Messages must come before this message ID. + `--files`: Checks if the message has attachments (no arguments). + `--embeds`: Checks if the message has embeds (no arguments). + """ + + if not await ctx.prompt( + f"Are you sure that you want to **massban**?", + author_id=ctx.author.id): + return + + # For some reason there are cases due to caching that ctx.author + # can be a User even in a guild only context + # Rather than trying to work out the kink with it + # Just upgrade the member itself. + if not isinstance(ctx.author, discord.Member): + try: + author = await ctx.guild.fetch_member(ctx.author.id) + except discord.HTTPException: + return await ctx.send('Somehow, Discord does not seem to think you are in this server.') + else: + author = ctx.author + + parser = Arguments(add_help=False, allow_abbrev=False) + parser.add_argument('--channel', '-c') + parser.add_argument('--reason', '-r') + parser.add_argument('--search', type=int, default=100) + parser.add_argument('--regex') + parser.add_argument('--no-avatar', action='store_true') + parser.add_argument('--no-roles', action='store_true') + parser.add_argument('--created', type=int) + parser.add_argument('--joined', type=int) + parser.add_argument('--joined-before', type=int) + parser.add_argument('--joined-after', type=int) + parser.add_argument('--contains') + parser.add_argument('--starts') + parser.add_argument('--ends') + parser.add_argument('--match') + parser.add_argument('--show', action='store_true') + parser.add_argument('--embeds', action='store_const', const=lambda m: len(m.embeds)) + parser.add_argument('--files', action='store_const', const=lambda m: len(m.attachments)) + parser.add_argument('--after', type=int) + parser.add_argument('--before', type=int) + + try: + args = parser.parse_args(shlex.split(args)) + except Exception as e: + return await ctx.send(str(e)) + + members = [] + + if args.channel: + channel = await commands.TextChannelConverter().convert(ctx, args.channel) + before = args.before and discord.Object(id=args.before) + after = args.after and discord.Object(id=args.after) + predicates = [] + if args.contains: + predicates.append(lambda m: args.contains in m.content) + if args.starts: + predicates.append(lambda m: m.content.startswith(args.starts)) + if args.ends: + predicates.append(lambda m: m.content.endswith(args.ends)) + if args.match: + try: + _match = re.compile(args.match) + except re.error as e: + return await ctx.send(f'Invalid regex passed to `--match`: {e}') + else: + predicates.append(lambda m, x=_match: x.match(m.content)) + if args.embeds: + predicates.append(args.embeds) + if args.files: + predicates.append(args.files) + + async for message in channel.history(limit=min(max(1, args.search), 2000), before=before, after=after): + if all(p(message) for p in predicates): + members.append(message.author) + else: + if ctx.guild.chunked: + members = ctx.guild.members + else: + async with ctx.typing(): + await ctx.guild.chunk(cache=True) + members = ctx.guild.members + + # member filters + predicates = [ + lambda m: isinstance(m, discord.Member) and can_execute_action(ctx, author, m), # Only if applicable + lambda m: not m.bot, # No bots + lambda m: m.discriminator != '0000', # No deleted users + ] + + converter = commands.MemberConverter() + + if args.regex: + try: + _regex = re.compile(args.regex) + except re.error as e: + return await ctx.send(f'Invalid regex passed to `--regex`: {e}') + else: + predicates.append(lambda m, x=_regex: x.match(m.name)) + + if args.no_avatar: + predicates.append(lambda m: m.avatar is None) + if args.no_roles: + predicates.append(lambda m: len(getattr(m, 'roles', [])) <= 1) + + now = discord.utils.utcnow() + if args.created: + def created(member, *, offset=now - datetime.timedelta(minutes=args.created)): + return member.created_at > offset + predicates.append(created) + if args.joined: + def joined(member, *, offset=now - datetime.timedelta(minutes=args.joined)): + if isinstance(member, discord.User): + # If the member is a user then they left already + return True + return member.joined_at and member.joined_at > offset + predicates.append(joined) + if args.joined_after: + _joined_after_member = await converter.convert(ctx, str(args.joined_after)) + def joined_after(member, *, _other=_joined_after_member): + return member.joined_at and _other.joined_at and member.joined_at > _other.joined_at + predicates.append(joined_after) + if args.joined_before: + _joined_before_member = await converter.convert(ctx, str(args.joined_before)) + def joined_before(member, *, _other=_joined_before_member): + return member.joined_at and _other.joined_at and member.joined_at < _other.joined_at + predicates.append(joined_before) + + members = {m for m in members if all(p(m) for p in predicates)} + if len(members) == 0: + return await ctx.send('No members found matching criteria.') + + if args.show: + members = sorted(members, key=lambda m: m.joined_at or now) + fmt = "\n".join(f'{m.id}\tJoined: {m.joined_at}\tCreated: {m.created_at}\t{m}' for m in members) + content = f'Current Time: {discord.utils.utcnow()}\nTotal members: {len(members)}\n{fmt}' + file = discord.File(io.BytesIO(content.encode('utf-8')), filename='members.txt') + return await ctx.send(file=file) + + if args.reason is None: + return await ctx.send('--reason flag is required.') + else: + reason = await ActionReason().convert(ctx, args.reason) + + confirm = await ctx.prompt(f'This will ban **{plural(len(members)):member}**. Are you sure?') + if not confirm: + return await ctx.send('Aborting.') + + count = 0 + for member in members: + try: + await ctx.guild.ban(member, reason=reason) + except discord.HTTPException: + pass + else: + count += 1 + + await ctx.send(f'Banned {count}/{len(members)}') # Warn @commands.command(pass_context=True, @@ -359,7 +579,7 @@ async def warn(self, *, reason: str = None): """Warn a user""" - member = ctx.get_user(member) + if not await ctx.prompt( f"Are you sure that you want to **warn** {member}?", author_id=ctx.author.id): @@ -833,11 +1053,17 @@ def predicate(m): before=args.before, after=args.after) - @commands.command() + @commands.command(aliases=['mute']) @commands.guild_only() @commands.has_guild_permissions(timeout_members=True) - async def timeout(self, ctx): - pass + @has_permissions(timeout_members=True) + async def timeout(self, ctx, duration: FutureTime, member: Union[MemberID, discord.Member], *, reason: ActionReason = None): + if not await ctx.prompt( + f"Are you sure that you want to **time out** {member} until {format_relative(duration.dt)}?", + author_id=ctx.author.id): + return + await member.edit(timed_out_until=duration.dt) + await ctx.send(embed=discord.Embed(description=f'**Timed out** {member} until {format_relative(duration.dt)}')) def setup(bot): diff --git a/minato_namikaze/bot_files/cogs/setup_server.py b/minato_namikaze/bot_files/cogs/setup_server.py deleted file mode 100644 index 3bdcb209..00000000 --- a/minato_namikaze/bot_files/cogs/setup_server.py +++ /dev/null @@ -1,173 +0,0 @@ -import asyncio -from os.path import join - -import discord -from discord.ext import commands - -from ..lib import * - - -class ServerSetup(commands.Cog, name="Server Setup"): - def __init__(self, bot): - self.bot = bot - self.description = "Do some necessary setup through an interactive mode." - - @property - def display_emoji(self) -> discord.PartialEmoji: - return discord.PartialEmoji(name="\N{HAMMER AND WRENCH}") - - def pin_img(self): - file_pin = discord.File(join(self.bot.minato_dir, "discord", - "pin.png"), - filename="pin.png") - return file_pin - - @commands.command(name="setup", description="Easy setup for the server") - @commands.has_permissions(manage_guild=True) - @commands.guild_only() - async def _setup(self, ctx): - "A command that setups up the server for feedback, ban and unban logs and also setups the channel and roles to create a support management system for the server." - await ctx.message.delete() - commanwaitingtime = 60.0 - waitingtime_bet_mesg = 3.0 - - embed = Embed(description="Please **check** the **channel pins**") - embed.set_image(url="attachment://pin.png") - - admin_roles, overwrite_dict = perms_dict(ctx) - ( - feed_channel, - support_channel, - support_channel_roles, - ban_channel, - unban_channel, - warns_channel, - botask, - ) = await channel_creation(ctx) - - if (feed_channel and support_channel and ban_channel and unban_channel - and warns_channel): - e = Embed( - title="You have already configured your server mate!", - description=f"Please head over to {feed_channel.mention} {support_channel.mention} {ban_channel.mention} {unban_channel.mention} {warns_channel.mention}", - ) - - if not support_channel and support_channel_roles: - await ctx.guild.create_role(name="SupportRequired") - - await feed_channel.send(file=self.pin_img(), embed=embed) - await asyncio.sleep(1) - await support_channel.send(file=self.pin_img(), embed=embed) - await asyncio.sleep(1) - await ban_channel.send(file=self.pin_img(), embed=embed) - await asyncio.sleep(1) - await unban_channel.send(file=self.pin_img(), embed=embed) - await asyncio.sleep(1) - await warns_channel.send(file=self.pin_img(), embed=embed) - await asyncio.sleep(1) - await ctx.send(embed=e) - - else: - # Bot Setup Channel - if not botask: - botask = await ctx.guild.create_text_channel( - "Bot Setup", - category=discord.utils.get(ctx.guild.categories, - name="Admin / Feedback"), - ) - await ctx.send( - f"**{botask.mention}** channel is **created** please head over there for the server setup of the bot!", - delete_after=8, - ) - await ctx.send(f"Roles having access to {botask.mention} are", - delete_after=8) - for i in admin_roles: - await ctx.send(f"{i.mention}", delete_after=10) - else: - await ctx.send(f"Please head over the {botask.mention}", - delete_after=10) - - # Feedback - if not feed_channel: - m = Feedback(bot=self.bot, - timeout=commanwaitingtime, - channel=botask) - await m.start(ctx, channel=botask) - await asyncio.sleep(waitingtime_bet_mesg) - else: - await ctx.send( - f"The channel for logging of feedback is already there {feed_channel.mention}", - delete_after=5, - ) - await feed_channel.send(file=self.pin_img(), embed=embed) - - # Support - if not support_channel: - m = Support(bot=self.bot, - timeout=commanwaitingtime, - channel=botask) - await m.start(ctx, channel=botask) - await asyncio.sleep(waitingtime_bet_mesg) - else: - await ctx.send( - f"The channel for support is already there {support_channel.mention}", - delete_after=5, - ) - await support_channel.send(file=self.pin_img(), embed=embed) - - # Ban - if not ban_channel: - m = Ban(bot=self.bot, - timeout=commanwaitingtime, - channel=botask) - await m.start(ctx, channel=botask) - await asyncio.sleep(waitingtime_bet_mesg) - else: - await ban_channel.send(file=self.pin_img(), embed=embed) - await ctx.send( - f"Channel for **logging bans already there**! {ban_channel.mention}", - delete_after=5, - ) - - # UnBan - if not unban_channel: - m = Unban(bot=self.bot, - timeout=commanwaitingtime, - channel=botask) - await m.start(ctx, channel=botask) - await asyncio.sleep(waitingtime_bet_mesg) - else: - file = discord.File(join(self.bot.minato_dir, "discord", - "pin.png"), - filename="pin.png") - await unban_channel.send(file=self.pin_img(), embed=embed) - await ctx.send( - f"Channel for **logging unbans already there**! {unban_channel.mention}", - delete_after=5, - ) - - # Warns - if not warns_channel: - m = Warns(bot=self.bot, - timeout=commanwaitingtime, - channel=botask) - await m.start(ctx, channel=botask) - await asyncio.sleep(waitingtime_bet_mesg) - else: - await warns_channel.send(file=self.pin_img(), embed=embed) - await ctx.send( - f"Channel for **logging warns already there**! {warns_channel.mention}", - delete_after=5, - ) - - # Setup Finish - await botask.send("**Deleting this setup channel in**") - gb = await botask.send(30) - for i in range(1, 30): - await gb.edit(content=30 - i) - await asyncio.sleep(1) - await botask.delete() - - -def setup(bot): - bot.add_cog(ServerSetup(bot)) diff --git a/minato_namikaze/bot_files/lib/classes/setup/__init__.py b/minato_namikaze/bot_files/lib/classes/setup/__init__.py index dc9ddbef..ecd5c3aa 100644 --- a/minato_namikaze/bot_files/lib/classes/setup/__init__.py +++ b/minato_namikaze/bot_files/lib/classes/setup/__init__.py @@ -1,4 +1,3 @@ -from .ban import * from .feedback import * from .support import * from .unban import * diff --git a/minato_namikaze/bot_files/lib/classes/setup/ban.py b/minato_namikaze/bot_files/lib/classes/setup_classes.py similarity index 56% rename from minato_namikaze/bot_files/lib/classes/setup/ban.py rename to minato_namikaze/bot_files/lib/classes/setup_classes.py index be7f26f0..81c037b3 100644 --- a/minato_namikaze/bot_files/lib/classes/setup/ban.py +++ b/minato_namikaze/bot_files/lib/classes/setup_classes.py @@ -1,27 +1,31 @@ import discord from discord.ext import menus -from discord.ext.menus.views import ViewMenu -from ...util import SetupVars -from ..embed import Embed +from ..util import SetupVars +from .embed import Embed -ban = SetupVars.ban.value +ban_topic = SetupVars.ban.value +feedback_topic = SetupVars.feedback.value +support_topic = SetupVars.support.value +unban_topic = SetupVars.unban.value +warns_topic = SetupVars.warns.value -class Ban(ViewMenu): - def __init__(self, bot, timeout, channel): - super().__init__(timeout=timeout) - self.bot = bot - self.channel = channel - - async def send_initial_message(self, ctx, channel): - embed = Embed(title=f"Want to log bans for the **{ctx.guild.name}** ?") - embed.add_field(name="Yes", value=":white_check_mark:") - embed.add_field(name="No", value=":negative_squared_cross_mark:") - return await channel.send(embed=embed) +async def check():pass - @menus.button("\N{WHITE HEAVY CHECK MARK}") - async def on_add(self, payload): +class Ban: + def __init__(self, ctx, timeout: int, channel: discord.TextChannel): + self.ctx = ctx + self.channel = channel + self.timeout = timeout + + async def start(self): + if not await self.ctx.prompt( + f"Want to **log bans** for the *{self.ctx.guild.name}* ?", + timeout=self.timeout, + author_id=ctx.author.id, + channel=self.channel): + return bingo = (discord.utils.get( self.ctx.guild.categories, name="Bingo Book") if discord.utils.get( self.ctx.guild.categories, name="Bingo Book") else False) @@ -31,7 +35,7 @@ async def on_add(self, payload): reason="To log the the bans and unban events + warns") ban_channel = await self.ctx.guild.create_text_channel( "ban", - topic=ban, + topic=ban_topic, overwrites={ self.ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False, @@ -49,10 +53,3 @@ async def on_add(self, payload): a = await ban_channel.send(embed=e) await a.pin() return - - @menus.button("\N{NEGATIVE SQUARED CROSS MARK}") - async def on_stop(self, payload): - await self.channel.send( - f"**Okay** no logging system for the **{self.ctx.guild.name}** bans will be there" - ) - return diff --git a/minato_namikaze/bot_files/lib/util/context.py b/minato_namikaze/bot_files/lib/util/context.py index ce5f0e5f..ee7297b9 100644 --- a/minato_namikaze/bot_files/lib/util/context.py +++ b/minato_namikaze/bot_files/lib/util/context.py @@ -104,6 +104,7 @@ async def prompt( delete_after: bool = True, reacquire: bool = True, author_id: Optional[int] = None, + channel: Optional[discord.TextChannel] = None ) -> Optional[bool]: """An interactive reaction confirmation dialog. Parameters @@ -136,7 +137,10 @@ async def prompt( ctx=self, author_id=author_id, ) - view.message = await self.send(message, view=view) + if channel is not None: + view.message = await channel.send(message, view=view) + else: + view.message = await self.send(message, view=view) await view.wait() return view.value