Repository: Cog-Creators/Red-DiscordBot

Issue:
[Modlog API] Allow lookup via UserID
# Feature request
#### Select the type of feature you are requesting:

- [ ] Cog
- [ ] Command
- [x] API functionality

#### Describe your requested feature

Allow the modlog API to look up cases by userID, this can help with various cases whereby a cog creator wants to look back at the past history of a member in a guild. Such as having automated tools decide then next action for the member.



Discussion:
I'm going to do this unless someone else was already working on it. I don't know if there's a better place to discuss who is doing something.
@DiscordLiz This is the best place to ask :smiley: As far as I know, no one is currently working on this. So we'd be grateful if you're willing to try it out!

Resolving patch:
diff --git a/redbot/cogs/modlog/modlog.py b/redbot/cogs/modlog/modlog.py
--- a/redbot/cogs/modlog/modlog.py
+++ b/redbot/cogs/modlog/modlog.py
@@ -1,4 +1,4 @@
-from typing import Optional
+from typing import Optional, Union
 
 import discord
 
@@ -6,6 +6,8 @@
 from redbot.core.bot import Red
 from redbot.core.i18n import Translator, cog_i18n
 from redbot.core.utils.chat_formatting import box
+from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
+
 
 _ = Translator("ModLog", __file__)
 
@@ -105,6 +107,37 @@ async def case(self, ctx: commands.Context, number: int):
             else:
                 await ctx.send(await case.message_content(embed=False))
 
+    @commands.command()
+    @commands.guild_only()
+    async def casesfor(self, ctx: commands.Context, member: Union[discord.Member, int]):
+        """
+        Displays the cases for the specified member
+        """
+        try:
+            if isinstance(member, int):
+                cases = await modlog.get_cases_for_member(
+                    bot=ctx.bot, guild=ctx.guild, member_id=member
+                )
+            else:
+                cases = await modlog.get_cases_for_member(
+                    bot=ctx.bot, guild=ctx.guild, member=member
+                )
+        except discord.NotFound:
+            return await ctx.send(_("That user does not exist."))
+        except discord.HTTPException:
+            return await ctx.send(
+                _("Something unexpected went wrong while fetching that user by ID.")
+            )
+
+        if not cases:
+            return await ctx.send(_("That user does not have any cases."))
+
+        embed_requested = await ctx.embed_requested()
+
+        rendered_cases = [await case.message_content(embed=embed_requested) for case in cases]
+
+        await menu(ctx, rendered_cases, DEFAULT_CONTROLS)
+
     @commands.command()
     @commands.guild_only()
     async def reason(self, ctx: commands.Context, case: Optional[int], *, reason: str):
diff --git a/redbot/core/modlog.py b/redbot/core/modlog.py
--- a/redbot/core/modlog.py
+++ b/redbot/core/modlog.py
@@ -15,6 +15,7 @@
     "get_next_case_number",
     "get_case",
     "get_all_cases",
+    "get_cases_for_member",
     "create_case",
     "get_casetype",
     "get_all_casetypes",
@@ -422,6 +423,92 @@ async def get_all_cases(guild: discord.Guild, bot: Red) -> List[Case]:
     return case_list
 
 
+async def get_cases_for_member(
+    guild: discord.Guild, bot: Red, *, member: discord.Member = None, member_id: int = None
+) -> List[Case]:
+    """
+    Gets all cases for the specified member or member id in a guild.
+
+    Parameters
+    ----------
+    guild: `discord.Guild`
+        The guild to get the cases from
+    bot: Red
+        The bot's instance
+    member: `discord.Member`
+        The member to get cases about
+    member_id: int
+        The id of the member to get cases about
+
+    Returns
+    -------
+    list
+        A list of all matching cases.
+
+    Raises
+    ------
+    ValueError
+        If at least one of member or member_id is not provided
+    `discord.NotFound`
+        A user with this ID does not exist.
+    `discord.Forbidden`
+        The bot does not have permission to fetch the modlog message which was sent.
+    `discord.HTTPException`
+        Fetching the user failed.
+    """
+
+    cases = await _conf.guild(guild).get_raw("cases")
+
+    if not (member_id or member):
+        raise ValueError("Expected a member or a member id to be provided.") from None
+
+    if not member_id:
+        member_id = member.id
+
+    if not member:
+        member = guild.get_member(member_id)
+        if not member:
+            member = await bot.get_user_info(member_id)
+
+    try:
+        mod_channel = await get_modlog_channel(guild)
+    except RuntimeError:
+        mod_channel = None
+
+    async def make_case(data: dict) -> Case:
+
+        message = None
+        if data["message"] and mod_channel:
+            try:
+                message = await mod_channel.get_message(data["message"])
+            except discord.NotFound:
+                pass
+
+        return Case(
+            bot=bot,
+            guild=bot.get_guild(data["guild"]),
+            created_at=data["created_at"],
+            action_type=data["action_type"],
+            user=member,
+            moderator=guild.get_member(data["moderator"]),
+            case_number=data["case_number"],
+            reason=data["reason"],
+            until=data["until"],
+            channel=guild.get_channel(data["channel"]),
+            amended_by=guild.get_member(data["amended_by"]),
+            modified_at=data["modified_at"],
+            message=message,
+        )
+
+    cases = [
+        await make_case(case_data)
+        for case_data in cases.values()
+        if case_data["user"] == member_id
+    ]
+
+    return cases
+
+
 async def create_case(
     bot: Red,
     guild: discord.Guild,


Test patch: