diff --git a/aurora/aurora.py b/aurora/aurora.py index 1e7fb11..58dcbc8 100644 --- a/aurora/aurora.py +++ b/aurora/aurora.py @@ -13,6 +13,7 @@ from datetime import datetime, timedelta, timezone from math import ceil import discord +from dateparser import parse from discord.ext import tasks from redbot.core import app_commands, commands, data_manager from redbot.core.app_commands import Choice @@ -520,6 +521,8 @@ class Aurora(commands.Cog): moderator: discord.User | None = None, pagesize: app_commands.Range[int, 1, 20] | None = None, page: int = 1, + before: str | None = None, + after: str | None = None, ephemeral: bool | None = None, inline: bool | None = None, export: bool = False, @@ -536,6 +539,10 @@ class Aurora(commands.Cog): Amount of infractions to list per page page: int Page to select + before: str + List infractions before a certain date + after: str + List infractions after a certain date ephemeral: bool Hide the command response inline: bool @@ -570,6 +577,22 @@ class Aurora(commands.Cog): or 5 ) + if before: + before = parse(before) + if before is None: + return await interaction.response.send_message( + content=error("Invalid date format for `before`."), ephemeral=True + ) + before = before.replace(tzinfo=timezone.utc) + + if after: + after = parse(after) + if after is None: + return await interaction.response.send_message( + content=error("Invalid date format for `after`."), ephemeral=True + ) + after = after.replace(tzinfo=timezone.utc) + await interaction.response.defer(ephemeral=ephemeral) permissions = check_permissions( @@ -586,13 +609,13 @@ class Aurora(commands.Cog): if target: filename = f"moderation_target_{str(target.id)}_{str(interaction.guild.id)}.json" - moderations = await Moderation.find_by_target(interaction.client, interaction.guild.id, target.id) + moderations = await Moderation.find_by_target(bot=interaction.client, guild_id=interaction.guild.id, target=target.id, before=before, after=after) elif moderator: filename = f"moderation_moderator_{str(moderator.id)}_{str(interaction.guild.id)}.json" - moderations = await Moderation.find_by_moderator(interaction.client, interaction.guild.id, moderator.id) + moderations = await Moderation.find_by_moderator(bot=interaction.client, guild_id=interaction.guild.id, moderator=moderator.id, before=before, after=after) else: filename = f"moderation_{str(interaction.guild.id)}.json" - moderations = await Moderation.get_latest(interaction.client, interaction.guild.id) + moderations = await Moderation.get_latest(bot=interaction.client, guild_id=interaction.guild.id, before=before, after=after) if export: try: diff --git a/aurora/info.json b/aurora/info.json index 00d919d..1c60b08 100644 --- a/aurora/info.json +++ b/aurora/info.json @@ -9,7 +9,7 @@ "disabled": false, "min_bot_version": "3.5.0", "min_python_version": [3, 10, 0], - "requirements": ["pydantic", "aiosqlite", "phx-class-registry"], + "requirements": ["pydantic", "aiosqlite", "phx-class-registry", "dateparser"], "tags": [ "mod", "moderate", diff --git a/aurora/models/moderation.py b/aurora/models/moderation.py index b49894b..bcadbb2 100644 --- a/aurora/models/moderation.py +++ b/aurora/models/moderation.py @@ -307,13 +307,19 @@ class Moderation(AuroraGuildModel): return results @classmethod - async def get_latest(cls, bot: Red, guild_id: int, limit: int | None = None, offset: int = 0, types: Iterable[Type] | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def get_latest(cls, bot: Red, guild_id: int, before: datetime = None, after: datetime = None, limit: int | None = None, offset: int = 0, types: Iterable[Type] | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: params = [] query = f"SELECT * FROM moderation_{guild_id} ORDER BY moderation_id DESC" if types: query += f" WHERE moderation_type IN ({', '.join(['?' for _ in types])})" for t in types: params.append(t.key) + if before: + query += " WHERE timestamp < ?" + params.append(before.timestamp()) + if after: + query += " WHERE timestamp > ?" + params.append(after.timestamp()) if limit: query += " LIMIT ? OFFSET ?" params.extend((limit, offset)) @@ -334,22 +340,40 @@ class Moderation(AuroraGuildModel): raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!") @classmethod - async def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable[Type] | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def find_by_target(cls, bot: Red, guild_id: int, target: int, before: datetime = None, after: datetime = None, types: Iterable[Type] | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: query = f"SELECT * FROM moderation_{guild_id} WHERE target_id = ?" + params = [target] if types: query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})" + for t in types: + params.append(t.key) + if before: + query += " AND timestamp < ?" + params.append(before.timestamp()) + if after: + query += " AND timestamp > ?" + params.append(after.timestamp()) query += " ORDER BY moderation_id DESC;" - return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *[t.key for t in types]) if types else (target,), cursor=cursor) + return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=params, cursor=cursor) @classmethod - async def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable[Type] | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: + async def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, before: datetime = None, after: datetime = None, types: Iterable[Type] | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]: query = f"SELECT * FROM moderation_{guild_id} WHERE moderator_id = ?" + params = [moderator] if types: query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})" + for t in types: + params.append(t.key) + if before: + query += " AND timestamp < ?" + params.append(before.timestamp()) + if after: + query += " AND timestamp > ?" + params.append(after.timestamp()) query += " ORDER BY moderation_id DESC;" - return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *[t.key for t in types]) if types else (moderator,), cursor=cursor) + return await cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=params, cursor=cursor) @classmethod async def log( diff --git a/poetry.lock b/poetry.lock index 373a6f5..e8bdc34 100644 --- a/poetry.lock +++ b/poetry.lock @@ -668,6 +668,28 @@ webencodings = "*" doc = ["sphinx", "sphinx_rtd_theme"] test = ["flake8", "isort", "pytest"] +[[package]] +name = "dateparser" +version = "1.2.0" +description = "Date parsing library designed to parse dates from HTML pages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"}, + {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"}, +] + +[package.dependencies] +python-dateutil = "*" +pytz = "*" +regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" +tzlocal = "*" + +[package.extras] +calendars = ["convertdate", "hijri-converter"] +fasttext = ["fasttext"] +langdetect = ["langdetect"] + [[package]] name = "defusedxml" version = "0.7.1" @@ -2373,6 +2395,34 @@ files = [ {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "tzlocal" +version = "5.2" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + [[package]] name = "urllib3" version = "2.2.1" @@ -2690,4 +2740,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.12" -content-hash = "6d09969e0ad7adff5aa105915c5c9e0354b4b7aceb80b9c2b3fb800bb08f21d4" +content-hash = "bc58f1f72ca73806c414042a78294e73c4119906b57fceb045ab48414928849e" diff --git a/pyproject.toml b/pyproject.toml index 39c6670..d185fe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ beautifulsoup4 = "^4.12.3" markdownify = "^0.12.1" aiosqlite = "^0.20.0" phx-class-registry = "^4.1.0" +dateparser = "^1.2.0" [tool.poetry.group.dev] optional = true