Beispiel #1
0
    async def cases(self,
                    ctx: context.Context,
                    user: typing.Union[discord.Member, int] = None):
        """Show list of cases of a user (mod only)

        Example usage
        --------------
        !cases <@user/ID>

        Parameters
        ----------
        user : typing.Union[discord.Member,int]
            "User we want to get cases of, doesn't have to be in guild"

        """

        if user is None:
            # hack workaround
            user = ctx.author
            ctx.args[2] = user

        # users can only check their own cases if they aren't mods
        if not isinstance(user, int):
            if not ctx.permissions.hasAtLeast(ctx.guild, ctx.author,
                                              5) and user.id != ctx.author.id:
                raise commands.BadArgument(
                    f"You don't have permissions to check others' cases.")
        else:
            if not ctx.permissions.hasAtLeast(ctx.guild, ctx.author, 5):
                raise commands.BadArgument(
                    f"You don't have permissions to check others' cases.")

        # if user not in guild, fetch their profile from the Discord API
        if isinstance(user, int):
            try:
                user = await self.bot.fetch_user(user)
            except Exception:
                raise commands.BadArgument(
                    f"Couldn't find user with ID {user}")
            ctx.args[2] = user

        # fetch user's cases from our database
        results = await ctx.settings.cases(user.id)
        if len(results.cases) == 0:
            if isinstance(user, int):
                raise commands.BadArgument(
                    f'User with ID {user.id} had no cases.')
            else:
                raise commands.BadArgument(f'{user.mention} had no cases.')

        # filter out unmute cases because they are irrelevant
        cases = [case for case in results.cases if case._type != "UNMUTE"]
Beispiel #2
0
    async def repo(self, ctx: context.Context, *, repo):
        async with ctx.typing():
            data = await self.repo_request(repo)

        if data is None:
            embed = discord.Embed(title="Error", color=discord.Color.red())
            embed.description = f'An error occurred while searching for that repo'
            await ctx.message.delete(delay=5)
            await ctx.send(embed=embed, delete_after=5)
            return

        if not isinstance(data, list):
            data = [data]

        if len(data) == 0:
            embed = discord.Embed(title=":(\nYour command ran into a problem",
                                  color=discord.Color.red())
            embed.description = f"Sorry, I couldn't find a repo by that name."
            await ctx.message.delete(delay=5)
            await ctx.send(embed=embed, delete_after=5)
            return

        menus = MenuPages(source=ReposSource(data, key=lambda t: 1,
                                             per_page=1),
                          clear_reactions_after=True)
        await menus.start(ctx)
    async def cases(self,
                    ctx: context.Context,
                    user: typing.Union[discord.Member, int] = None):
        """Show list of cases of a user (mod only)

        Example usage
        --------------
        !cases <@user/ID>

        Parameters
        ----------
        user : typing.Union[discord.Member,int]
            "User we want to get cases of, doesn't have to be in guild"

        """

        if user is None:
            user = ctx.author
            ctx.args[2] = user

        if not isinstance(user, int):
            if not ctx.settings.permissions.hasAtLeast(
                    ctx.guild, ctx.author, 2) and user.id != ctx.author.id:
                raise commands.BadArgument(
                    f"You don't have permissions to check others' cases.")
        else:
            if not ctx.settings.permissions.hasAtLeast(ctx.guild, ctx.author,
                                                       2):
                raise commands.BadArgument(
                    f"You don't have permissions to check others' cases.")

        if isinstance(user, int):
            try:
                user = await self.bot.fetch_user(user)
            except Exception:
                raise commands.BadArgument(
                    f"Couldn't find user with ID {user}")
            ctx.args[2] = user

        results = await ctx.settings.cases(user.id)
        if len(results.cases) == 0:
            if isinstance(user, int):
                raise commands.BadArgument(
                    f'User with ID {user.id} had no cases.')
            else:
                raise commands.BadArgument(f'{user.mention} had no cases.')
        cases = [case for case in results.cases if case._type != "UNMUTE"]
Beispiel #4
0
    async def reset(self, ctx: context.Context):
        """Reset all points (mod only)
        """

        async with ctx.typing():
            members_reset = await ctx.settings.reset_trivia_points()

        if not members_reset:
            raise commands.BadArgument("There were no points to reset!")

        await ctx.message.delete(delay=5)
        await ctx.send_success(
            title="Done!",
            description=f"I reset {members_reset} users' points.",
            delete_after=5)
Beispiel #5
0
    async def jumbo(self, ctx: context.Context,
                    emoji: typing.Union[discord.Emoji, discord.PartialEmoji,
                                        str]):
        """Post large version of a given emoji

        Example usage
        -------------
        !jumbo :ntwerk:

        Parameters
        ----------
        emoji : typing.Union[discord.Emoji, discord.PartialEmoji]
            "Emoji to post"
        """

        # non-mod users will be ratelimited
        bot_chan = self.bot.settings.guild().channel_botspam
        if not self.bot.settings.permissions.hasAtLeast(
                ctx.guild, ctx.author, 5) and ctx.channel.id != bot_chan:
            if await self.ratelimit(ctx.message):
                raise commands.BadArgument("This command is on cooldown.")

        # is this a regular Unicode emoji?
        if isinstance(emoji, str):
            # yes, read the bytes from our JSON file and turn it into an image
            async with ctx.typing():
                emoji_url_file = self.emojis.get(emoji)
                if emoji_url_file is None:
                    raise commands.BadArgument(
                        "Couldn't find a suitable emoji.")

            im = Image.open(BytesIO(base64.b64decode(emoji_url_file)))
            image_container = BytesIO()
            im.save(image_container, 'png')
            image_container.seek(0)
            _file = discord.File(image_container, filename="image.png")
            await ctx.message.reply(file=_file, mention_author=False)
        else:
            # no, this is a custom emoji. send its URL
            await ctx.message.reply(emoji.url, mention_author=False)
    async def jumbo(self, ctx: context.Context,
                    emoji: typing.Union[discord.Emoji, discord.PartialEmoji,
                                        str]):
        """Post large version of a given emoji

        Example usage
        -------------
        !jumbo :ntwerk:

        Parameters
        ----------
        emoji : typing.Union[discord.Emoji, discord.PartialEmoji]
            "Emoji to post"
        """

        bot_chan = ctx.settings.guild().channel_offtopic
        if not ctx.settings.permissions.hasAtLeast(
                ctx.guild, ctx.author, 2) and ctx.channel.id != bot_chan:
            if await self.ratelimit(ctx.message):
                raise commands.BadArgument("This command is on cooldown.")

        if isinstance(emoji, str):
            async with ctx.typing():
                emoji_url_file = self.emojis.get(emoji)
                if emoji_url_file is None:
                    raise commands.BadArgument(
                        "Couldn't find a suitable emoji.")

            im = Image.open(BytesIO(base64.b64decode(emoji_url_file)))
            image_container = BytesIO()
            im.save(image_container, 'png')
            image_container.seek(0)
            _file = discord.File(image_container, filename="image.png")
            await ctx.message.reply(file=_file, mention_author=False)

        else:
            await ctx.message.reply(emoji.url, mention_author=False)
Beispiel #7
0
    async def batchraid(self, ctx: context.Context, *, phrases: str) -> None:
        """Add a list of (newline-separated) phrases to the raid filter.

        Example usage
        --------------
        !raid <phrase>

        Parameters
        ----------
        phrases : str
            "Phrases to add, separated with enter"
        """

        async with ctx.typing():
            phrases = list(set(phrases.split("\n")))
            phrases = [phrase.strip() for phrase in phrases]

            phrases_contenders = set(phrases)
            phrases_already_in_db = set(
                [phrase.word for phrase in ctx.settings.guild().raid_phrases])

            duplicate_count = len(
                phrases_already_in_db
                & phrases_contenders)  # count how many duplicates we have
            new_phrases = list(phrases_contenders - phrases_already_in_db)

        if not new_phrases:
            raise commands.BadArgument(
                "All the phrases you supplied are already in the database.")

        phrases_prompt_string = "\n".join(
            [f"**{i+1}**. {phrase}" for i, phrase in enumerate(new_phrases)])
        if len(phrases_prompt_string) > 3900:
            phrases_prompt_string = phrases_prompt_string[:3500] + "\n... (and some more)"

        embed = Embed(
            title="Confirm raidphrase batch",
            color=discord.Color.dark_orange(),
            description=
            f"{phrases_prompt_string}\n\nShould we add these {len(new_phrases)} phrases?"
        )

        if duplicate_count > 0:
            embed.set_footer(
                text=
                f"Note: we found {duplicate_count} duplicates in your list.")

        message = await ctx.send(embed=embed)

        prompt_data = context.PromptDataReaction(message=message,
                                                 reactions=['✅', '❌'],
                                                 timeout=120,
                                                 delete_after=True)
        response, _ = await ctx.prompt_reaction(info=prompt_data)

        if response == '✅':
            async with ctx.typing():
                for phrase in new_phrases:
                    await ctx.settings.add_raid_phrase(phrase)

            await ctx.send_success(
                f"Added {len(new_phrases)} phrases to the raid filter.",
                delete_after=5)
        else:
            await ctx.send_warning("Cancelled.", delete_after=5)

        await ctx.message.delete(delay=5)
Beispiel #8
0
    async def ban(self,
                  ctx: context.Context,
                  user: permissions.ModsAndAboveExternal,
                  *,
                  reason: str = "No reason."):
        """Ban a user (mod only)

        Example usage
        --------------
        !ban <@user/ID> <reason (optional)>

        Parameters
        ----------
        user : typing.Union[discord.Member, int]
            "The user to be banned, doesn't have to be part of the guild"
        reason : str, optional
            "Reason for ban, by default 'No reason.'"
        """

        reason = discord.utils.escape_markdown(reason)
        reason = discord.utils.escape_mentions(reason)

        member_is_external = ctx.guild.get_member(user.id) is None

        if self.ban_list_cache is None:
            self.ban_list_cache = {
                user.id
                for _, user in await ctx.guild.bans()
            }
        # if the ID given is of a user who isn't in the guild, try to fetch the profile
        if member_is_external:
            async with ctx.typing():
                # previous_bans = [user for _, user in await ctx.guild.bans()]

                if user.id in self.ban_list_cache:
                    raise commands.BadArgument("That user is already banned!")

        self.ban_list_cache.add(user.id)
        log = await self.add_ban_case(ctx, user, reason)

        if not member_is_external:
            try:
                await user.send(f"You were banned from {ctx.guild.name}",
                                embed=log)
            except Exception:
                pass

        if not member_is_external:
            await user.ban(reason=reason)
        else:
            # hackban for user not currently in guild
            await ctx.guild.ban(discord.Object(id=user.id))

        await ctx.message.reply(embed=log, delete_after=10)
        await ctx.message.delete(delay=10)

        public_chan = ctx.guild.get_channel(
            ctx.settings.guild().channel_public)
        if public_chan:
            log.remove_author()
            log.set_thumbnail(url=user.avatar_url)
            await public_chan.send(embed=log)
Beispiel #9
0
    async def editreason(self, ctx: context.Context,
                         user: permissions.ModsAndAboveExternal, case_id: int,
                         *, new_reason: str) -> None:
        """Edit case reason and the embed in #public-mod-logs. (mod only)

        Example usage
        --------------
        !editreason <@user/ID> <case ID> <reason>

        Parameters
        ----------
        user : discord.Member
            "User to edit case of"
        case_id : int
            "The ID of the case for which we want to edit reason"
        new_reason : str
            "New reason"

        """

        # retrieve user's case with given ID
        cases = await ctx.settings.get_case(user.id, case_id)
        case = cases.cases.filter(_id=case_id).first()

        new_reason = discord.utils.escape_markdown(new_reason)
        new_reason = discord.utils.escape_mentions(new_reason)

        # sanity checks
        if case is None:
            raise commands.BadArgument(
                message=f"{user} has no case with ID {case_id}")

        old_reason = case.reason
        case.reason = new_reason
        case.date = datetime.datetime.now()
        cases.save()

        dmed = True
        log = await logging.prepare_editreason_log(ctx.author, user, case,
                                                   old_reason)
        if isinstance(user, discord.Member):
            try:
                await user.send(f"Your case was updated in {ctx.guild.name}.",
                                embed=log)
            except Exception:
                dmed = False

        public_chan = ctx.guild.get_channel(
            ctx.settings.guild().channel_public)

        found = False
        async with ctx.typing():
            async for message in public_chan.history(limit=200):
                if message.author.id != ctx.me.id:
                    continue
                if len(message.embeds) == 0:
                    continue
                embed = message.embeds[0]
                # print(embed.footer.text)
                if embed.footer.text == discord.Embed.Empty:
                    continue
                if len(embed.footer.text.split(" ")) < 2:
                    continue

                if f"#{case_id}" == embed.footer.text.split(" ")[1]:
                    for i, field in enumerate(embed.fields):
                        if field.name == "Reason":
                            embed.set_field_at(i,
                                               name="Reason",
                                               value=new_reason)
                            await message.edit(embed=embed)
                            found = True
        if found:
            await ctx.message.reply(
                f"We updated the case and edited the embed in {public_chan.mention}.",
                embed=log,
                delete_after=10)
        else:
            await ctx.message.reply(
                f"We updated the case but weren't able to find a corresponding message in {public_chan.mention}!",
                embed=log,
                delete_after=10)
            log.remove_author()
            log.set_thumbnail(url=user.avatar_url)
            await public_chan.send(user.mention if not dmed else "", embed=log)

        await ctx.message.delete(delay=10)
    async def leaderboard(self, ctx: context.Context, full: str = None):
        """Get karma leaderboard for the server
        """

        ctx.user_cache = self.user_cache

        class Source(menus.GroupByPageSource):
            async def format_page(self, menu, entry):
                embed = discord.Embed(
                    title=
                    f'Leaderboard: Page {menu.current_page +1}/{self.get_max_pages()}',
                    color=discord.Color(value=0xfcba03))
                embed.set_footer(
                    icon_url=ctx.author.avatar_url,
                    text=
                    "Note: Nerds and Moderators were excluded from these results."
                )
                embed.description = ""

                for i, user in enumerate(entry.items):
                    member = menu.ctx.guild.get_member(user._id)
                    member_found = member is not None
                    if not member_found:
                        if user._id not in menu.ctx.user_cache:
                            try:
                                member = await menu.ctx.bot.fetch_user(user._id
                                                                       )
                                menu.ctx.user_cache[user._id] = member
                            except Exception:
                                member = None

                        else:
                            member = menu.ctx.user_cache[user._id]

                    embed.add_field(
                        name=f"Rank {i+1}",
                        value=
                        f"{member.mention} ({member})\n{user.karma} karma",
                        inline=False)

                return embed

        data = await ctx.settings.leaderboard()

        if (len(data) == 0):
            raise commands.BadArgument("No history in this guild!")
        else:
            data_final = []
            for u in data:
                member = ctx.guild.get_member(u._id)
                if member:
                    if full is None:
                        if not ctx.settings.permissions.hasAtLeast(
                                member.guild, member, 1):
                            data_final.append(u)
                    else:
                        data_final.append(u)
                else:
                    data_final.append(u)

            pages = NewMenuPages(source=Source(data_final,
                                               key=lambda t: 1,
                                               per_page=10),
                                 clear_reactions_after=True)
            await pages.start(ctx)