Compare commits
174 commits
main
...
aurora-pyd
Author | SHA1 | Date | |
---|---|---|---|
027144f35d | |||
0d64e3f652 | |||
8591649465 | |||
78630dc317 | |||
74d122a2e7 | |||
ce48c1e889 | |||
ff34310113 | |||
3d2dabae08 | |||
3fdc54b7cb | |||
b252343dc0 | |||
720e100a20 | |||
ff66006b8a | |||
fdb96539c3 | |||
7a664ce9c3 | |||
3168c42787 | |||
5d53eec2f1 | |||
166421b6ba | |||
0a207b66e4 | |||
5151f65317 | |||
0089625ef3 | |||
8ac735dafe | |||
21fa3d9eb0 | |||
38180f5ccd | |||
19e50c25e3 | |||
a10af37f14 | |||
a5b344a323 | |||
cb420d2fc4 | |||
04223c3c55 | |||
1c6d2456ed | |||
9b0f977016 | |||
460d5a31fc | |||
bbe8b281d1 | |||
be253b668b | |||
22f9ce52d1 | |||
499cfbe8a9 | |||
99d95afe07 | |||
028e22ebec | |||
4c28453173 | |||
d646754466 | |||
76f176d4cc | |||
c35580c576 | |||
7fc6235abe | |||
21e51dc320 | |||
db477c4744 | |||
1990b97518 | |||
641f45d126 | |||
73c9104882 | |||
5b64ee9578 | |||
bfb4d8768d | |||
7f71ca3d6d | |||
3dcc637920 | |||
9a4f19f4a1 | |||
7a9c9846de | |||
0cc7d6079d | |||
ed923f1d9b | |||
39cb5feb50 | |||
c90796f6b3 | |||
0b1d1d29e6 | |||
599ab8c51d | |||
797fd561c9 | |||
51d3245703 | |||
0411e3dab7 | |||
67b33a2eb8 | |||
dc51aa7bdc | |||
904fd1c914 | |||
92d221ff70 | |||
d91a4f49f9 | |||
ab878739c4 | |||
946e14ee3c | |||
bcc4aa384f | |||
260dd3ef4c | |||
c69b3cd032 | |||
2b79e3b6a8 | |||
7dfe94869c | |||
ac8cefd779 | |||
09471a4027 | |||
15ccd5530a | |||
bb5ab8e61b | |||
c2e017339e | |||
d7ca5cab46 | |||
53b67e1c95 | |||
d7a8fbe367 | |||
f8968e8e9e | |||
834d116b20 | |||
d375716fbf | |||
dcda128f11 | |||
16af26f93e | |||
5be5a39c54 | |||
e14845ef85 | |||
8d1a57165d | |||
a4e11fd828 | |||
9c7e0b0b89 | |||
1e865643a0 | |||
b7b6dc2f2e | |||
84235d6504 | |||
70c00a59d6 | |||
0386cb346e | |||
335d1a2a4c | |||
1b322dfe50 | |||
5b6b04dfe0 | |||
158e7560f8 | |||
52fcdcc96a | |||
65bb9af7a6 | |||
e591b2c4a5 | |||
a86348fae3 | |||
37e471fbaa | |||
0553856aa9 | |||
6745f0a486 | |||
85a935f9b3 | |||
4c8cd7bd16 | |||
278bd98349 | |||
cad24d852c | |||
de90f6a8b7 | |||
293f77c228 | |||
a6371fd367 | |||
8433c946fd | |||
300d26dc7e | |||
e8d210df2a | |||
acf3b0c68f | |||
04d10d2cf8 | |||
0b697f9f50 | |||
557ac45fcc | |||
3f8cdf2012 | |||
b03ce04245 | |||
dc44e8c6de | |||
fc15b434c7 | |||
e89db3de5a | |||
a8414a7918 | |||
6eeab9ed96 | |||
94f6d6c3b5 | |||
ee331b7544 | |||
50c2db80d9 | |||
23eba46948 | |||
0b31e70089 | |||
3f6aec0a82 | |||
a22de1d2c2 | |||
6a7758e8f9 | |||
a7d8f452d1 | |||
6520f4f2b9 | |||
ea280c2d62 | |||
d70f2bf5f1 | |||
356d58f9d7 | |||
74a0983419 | |||
1313834ea5 | |||
b752181781 | |||
39479eb216 | |||
e46612dc08 | |||
e7e8d60f3b | |||
eea66607d9 | |||
2c336ff70c | |||
a3a208b38e | |||
26bf5920c6 | |||
d13ad88f16 | |||
6147c8c6d5 | |||
2dfc9d9824 | |||
ca7c0d8d7c | |||
98f3e5943b | |||
7dfd9c607a | |||
df87612055 | |||
58303b8e9c | |||
ca1722fee3 | |||
69805b276f | |||
25d7101cb5 | |||
92f9619cea | |||
1d825625f3 | |||
ea65816c32 | |||
afed1d6a37 | |||
e8ca0aeb1c | |||
2da76eb51a | |||
e5cdd3893f | |||
14a04cff59 | |||
f3d6244a17 | |||
b6d1510698 | |||
c0969ea947 |
21 changed files with 1650 additions and 1191 deletions
|
@ -58,7 +58,7 @@ jobs:
|
||||||
npx -p "@getmeli/cli" meli upload ./site \
|
npx -p "@getmeli/cli" meli upload ./site \
|
||||||
--url "https://pages.coastalcommits.com" \
|
--url "https://pages.coastalcommits.com" \
|
||||||
--site "${{ vars.MELI_SITE_ID }}" \
|
--site "${{ vars.MELI_SITE_ID }}" \
|
||||||
--token "${{ secrets.MELI_SECRET }}" \
|
--token "${{ secrets.MELI_SITE_SECRET }}" \
|
||||||
--release "$CI_ACTION_REF_NAME_SLUG/${{ env.GITHUB_SHA }}" \
|
--release "$CI_ACTION_REF_NAME_SLUG/${{ env.GITHUB_SHA }}" \
|
||||||
--branch "$CI_ACTION_REF_NAME_SLUG"
|
--branch "$CI_ACTION_REF_NAME_SLUG"
|
||||||
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
.vscode
|
.vscode
|
||||||
site
|
site
|
||||||
.venv
|
.venv
|
||||||
|
__pycache__
|
||||||
|
|
855
aurora/aurora.py
855
aurora/aurora.py
File diff suppressed because it is too large
Load diff
|
@ -1,13 +1,15 @@
|
||||||
# pylint: disable=duplicate-code
|
# pylint: disable=duplicate-code
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
from time import time
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import box, warning
|
from redbot.core.utils.chat_formatting import box, warning
|
||||||
|
|
||||||
from ..utilities.database import connect, create_guild_table, mysql_log
|
from ..models.moderation import Moderation
|
||||||
|
from ..utilities.database import connect, create_guild_table
|
||||||
|
from ..utilities.utils import timedelta_from_string
|
||||||
|
|
||||||
|
|
||||||
class ImportAuroraView(ui.View):
|
class ImportAuroraView(ui.View):
|
||||||
|
@ -62,10 +64,23 @@ class ImportAuroraView(ui.View):
|
||||||
case["target_type"] = "USER"
|
case["target_type"] = "USER"
|
||||||
|
|
||||||
if "role_id" not in case or not case["role_id"]:
|
if "role_id" not in case or not case["role_id"]:
|
||||||
case["role_id"] = 0
|
case["role_id"] = None
|
||||||
|
else:
|
||||||
|
case["role_id"] = int(case["role_id"])
|
||||||
|
|
||||||
|
case["target_id"] = int(case["target_id"])
|
||||||
|
case["moderator_id"] = int(case["moderator_id"])
|
||||||
|
|
||||||
if "changes" not in case or not case["changes"]:
|
if "changes" not in case or not case["changes"]:
|
||||||
case["changes"] = []
|
changes = []
|
||||||
|
else:
|
||||||
|
changes = json.loads(case["changes"])
|
||||||
|
if isinstance(changes, str):
|
||||||
|
changes: list[dict] = json.loads(changes)
|
||||||
|
|
||||||
|
for change in changes:
|
||||||
|
if change.get("bot"):
|
||||||
|
del change["bot"]
|
||||||
|
|
||||||
if "metadata" not in case:
|
if "metadata" not in case:
|
||||||
metadata = {}
|
metadata = {}
|
||||||
|
@ -73,31 +88,36 @@ class ImportAuroraView(ui.View):
|
||||||
metadata: Dict[str, any] = json.loads(case["metadata"])
|
metadata: Dict[str, any] = json.loads(case["metadata"])
|
||||||
if not metadata.get("imported_from"):
|
if not metadata.get("imported_from"):
|
||||||
metadata.update({"imported_from": "Aurora"})
|
metadata.update({"imported_from": "Aurora"})
|
||||||
|
metadata.update({"imported_timestamp": int(time())})
|
||||||
|
|
||||||
if case["duration"] != "NULL":
|
if case["duration"] != "NULL" and case["duration"] is not None:
|
||||||
hours, minutes, seconds = map(int, case["duration"].split(":"))
|
duration = timedelta_from_string(case["duration"])
|
||||||
duration = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
|
||||||
else:
|
else:
|
||||||
duration = "NULL"
|
duration = None
|
||||||
|
|
||||||
await mysql_log(
|
try:
|
||||||
self.ctx.guild.id,
|
Moderation.log(
|
||||||
case["moderator_id"],
|
bot=interaction.client,
|
||||||
case["moderation_type"],
|
guild_id=self.ctx.guild.id,
|
||||||
case["target_type"],
|
moderator_id=case["moderator_id"],
|
||||||
case["target_id"],
|
moderation_type=case["moderation_type"],
|
||||||
case["role_id"],
|
target_type=case["target_type"],
|
||||||
duration,
|
target_id=case["target_id"],
|
||||||
case["reason"],
|
role_id=case["role_id"],
|
||||||
timestamp=case["timestamp"],
|
duration=duration,
|
||||||
resolved=case["resolved"],
|
reason=case["reason"],
|
||||||
resolved_by=case["resolved_by"],
|
timestamp=case["timestamp"],
|
||||||
resolved_reason=case["resolve_reason"],
|
resolved=case["resolved"],
|
||||||
expired=case["expired"],
|
resolved_by=case["resolved_by"],
|
||||||
changes=case["changes"],
|
resolved_reason=case["resolve_reason"],
|
||||||
metadata=metadata,
|
expired=case["expired"],
|
||||||
database=database,
|
changes=changes,
|
||||||
)
|
metadata=metadata,
|
||||||
|
database=database,
|
||||||
|
return_obj=False
|
||||||
|
)
|
||||||
|
except Exception as e: # pylint: disable=broad-exception-caught
|
||||||
|
failed_cases.append(str(case["moderation_id"]) + f": {e}")
|
||||||
|
|
||||||
await interaction.edit_original_response(content="Import complete.")
|
await interaction.edit_original_response(content="Import complete.")
|
||||||
if failed_cases:
|
if failed_cases:
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# pylint: disable=duplicate-code
|
# pylint: disable=duplicate-code
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from time import time
|
||||||
|
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import box, warning
|
from redbot.core.utils.chat_formatting import box, warning
|
||||||
|
|
||||||
from ..utilities.database import connect, create_guild_table, mysql_log
|
from ..models.moderation import Change, Moderation
|
||||||
|
from ..utilities.database import connect, create_guild_table
|
||||||
|
|
||||||
|
|
||||||
class ImportGalacticBotView(ui.View):
|
class ImportGalacticBotView(ui.View):
|
||||||
|
@ -67,12 +69,12 @@ class ImportGalacticBotView(ui.View):
|
||||||
if case["duration"] is not None and float(case["duration"]) != 0:
|
if case["duration"] is not None and float(case["duration"]) != 0:
|
||||||
duration = timedelta(seconds=round(float(case["duration"]) / 1000))
|
duration = timedelta(seconds=round(float(case["duration"]) / 1000))
|
||||||
else:
|
else:
|
||||||
duration = "NULL"
|
duration = None
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
failed_cases.append(case["case"])
|
failed_cases.append(case["case"])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
metadata = {"imported_from": "GalacticBot"}
|
metadata = {"imported_from": "GalacticBot", "imported_timestamp": int(time())}
|
||||||
|
|
||||||
if case["type"] == "SLOWMODE":
|
if case["type"] == "SLOWMODE":
|
||||||
metadata["seconds"] = case["data"]["seconds"]
|
metadata["seconds"] = case["data"]["seconds"]
|
||||||
|
@ -98,37 +100,37 @@ class ImportGalacticBotView(ui.View):
|
||||||
if resolved_timestamp is None:
|
if resolved_timestamp is None:
|
||||||
resolved_timestamp = timestamp
|
resolved_timestamp = timestamp
|
||||||
changes = [
|
changes = [
|
||||||
{
|
Change.from_dict(interaction.client, {
|
||||||
"type": "ORIGINAL",
|
"type": "ORIGINAL",
|
||||||
"reason": case["reason"],
|
"reason": case["reason"],
|
||||||
"user_id": case["executor"],
|
"user_id": case["executor"],
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
},
|
}),
|
||||||
{
|
Change.from_dict(interaction.client, {
|
||||||
"type": "RESOLVE",
|
"type": "RESOLVE",
|
||||||
"reason": resolved_reason,
|
"reason": resolved_reason,
|
||||||
"user_id": resolved_by,
|
"user_id": resolved_by,
|
||||||
"timestamp": resolved_timestamp,
|
"timestamp": resolved_timestamp,
|
||||||
},
|
}),
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
resolved = 0
|
resolved = None
|
||||||
resolved_by = "NULL"
|
resolved_by = None
|
||||||
resolved_reason = "NULL"
|
resolved_reason = None
|
||||||
changes = []
|
changes = None
|
||||||
|
|
||||||
if case["reason"] and case["reason"] != "N/A":
|
if case["reason"] and case["reason"] != "N/A":
|
||||||
reason = case["reason"]
|
reason = case["reason"]
|
||||||
else:
|
else:
|
||||||
reason = "NULL"
|
reason = None
|
||||||
|
|
||||||
await mysql_log(
|
Moderation.log(
|
||||||
self.ctx.guild.id,
|
self.ctx.guild.id,
|
||||||
case["executor"],
|
case["executor"],
|
||||||
case["type"],
|
case["type"],
|
||||||
case["targetType"],
|
case["targetType"],
|
||||||
case["target"],
|
case["target"],
|
||||||
0,
|
None,
|
||||||
duration,
|
duration,
|
||||||
reason,
|
reason,
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"min_bot_version": "3.5.0",
|
"min_bot_version": "3.5.0",
|
||||||
"min_python_version": [3, 10, 0],
|
"min_python_version": [3, 10, 0],
|
||||||
|
"requirements": ["pydantic"],
|
||||||
"tags": [
|
"tags": [
|
||||||
"mod",
|
"mod",
|
||||||
"moderate",
|
"moderate",
|
||||||
|
|
|
@ -2,12 +2,12 @@ from discord import ButtonStyle, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import error
|
from redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from ..utilities.config import config
|
||||||
from aurora.utilities.factory import addrole_embed
|
from ..utilities.factory import addrole_embed
|
||||||
|
|
||||||
|
|
||||||
class Addrole(ui.View):
|
class Addrole(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from ..utilities.config import config
|
||||||
from aurora.utilities.factory import guild_embed
|
from ..utilities.factory import guild_embed
|
||||||
from aurora.utilities.utils import create_pagesize_options
|
from ..utilities.utils import create_pagesize_options
|
||||||
|
|
||||||
|
|
||||||
class Guild(ui.View):
|
class Guild(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
|
@ -2,12 +2,12 @@ from discord import ButtonStyle, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.utils.chat_formatting import error
|
from redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from ..utilities.config import config
|
||||||
from aurora.utilities.factory import immune_embed
|
from ..utilities.factory import immune_embed
|
||||||
|
|
||||||
|
|
||||||
class Immune(ui.View):
|
class Immune(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
from discord import ButtonStyle, Interaction, Message, ui
|
from discord import ButtonStyle, Interaction, Message, ui
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from ..utilities.config import config
|
||||||
from aurora.utilities.factory import overrides_embed
|
from ..utilities.factory import overrides_embed
|
||||||
from aurora.utilities.utils import create_pagesize_options
|
from ..utilities.utils import create_pagesize_options
|
||||||
|
|
||||||
|
|
||||||
class Overrides(ui.View):
|
class Overrides(ui.View):
|
||||||
def __init__(self, ctx: commands.Context, message: Message, timeout: int = None):
|
def __init__(self, ctx: commands.Context, message: Message, timeout: int | None = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
0
aurora/models/__init__.py
Normal file
0
aurora/models/__init__.py
Normal file
28
aurora/models/base.py
Normal file
28
aurora/models/base.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
|
||||||
|
class AuroraBaseModel(BaseModel):
|
||||||
|
"""Base class for all models in Aurora."""
|
||||||
|
model_config = ConfigDict(ignored_types=(Red,), arbitrary_types_allowed=True)
|
||||||
|
bot: Red
|
||||||
|
|
||||||
|
def dump(self) -> dict:
|
||||||
|
return self.model_dump(exclude={"bot"})
|
||||||
|
|
||||||
|
def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs):
|
||||||
|
from ..utilities.json import dump, dumps # pylint: disable=cyclic-import
|
||||||
|
return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.model_dump(exclude={"bot"}), indent=indent, **kwargs)
|
||||||
|
|
||||||
|
class AuroraGuildModel(AuroraBaseModel):
|
||||||
|
"""Subclass of AuroraBaseModel that includes a guild_id attribute, and a modified to_json() method to match."""
|
||||||
|
guild_id: int
|
||||||
|
|
||||||
|
def dump(self) -> dict:
|
||||||
|
return self.model_dump(exclude={"bot", "guild_id"})
|
||||||
|
|
||||||
|
def to_json(self, indent: int | None = None, file: Any | None = None, **kwargs):
|
||||||
|
from ..utilities.json import dump, dumps # pylint: disable=cyclic-import
|
||||||
|
return dump(self.dump(), file, indent=indent, **kwargs) if file else dumps(self.model_dump(exclude={"bot", "guild_id"}), indent=indent, **kwargs)
|
63
aurora/models/change.py
Normal file
63
aurora/models/change.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Literal, Optional
|
||||||
|
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
from ..utilities.utils import timedelta_from_string
|
||||||
|
from .base import AuroraBaseModel
|
||||||
|
from .partials import PartialUser
|
||||||
|
|
||||||
|
|
||||||
|
class Change(AuroraBaseModel):
|
||||||
|
type: Literal["ORIGINAL", "RESOLVE", "EDIT"]
|
||||||
|
timestamp: datetime
|
||||||
|
user_id: int
|
||||||
|
reason: Optional[str] = "No reason provided"
|
||||||
|
duration: Optional[timedelta] = None
|
||||||
|
end_timestamp: Optional[datetime] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unix_timestamp(self) -> int:
|
||||||
|
return int(self.timestamp.timestamp())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.type} {self.user_id} {self.reason}"
|
||||||
|
|
||||||
|
async def get_user(self) -> "PartialUser":
|
||||||
|
return await PartialUser.from_id(self.bot, self.user_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, bot: Red, data: dict) -> "Change":
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = json.loads(data)
|
||||||
|
if "duration" in data and data["duration"] and not isinstance(data["duration"], timedelta) and not data["duration"] == "NULL":
|
||||||
|
duration = timedelta_from_string(data["duration"])
|
||||||
|
elif "duration" in data and isinstance(data["duration"], timedelta):
|
||||||
|
duration = data["duration"]
|
||||||
|
else:
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
if "end_timestamp" in data and data["end_timestamp"] and not isinstance(data["end_timestamp"], datetime):
|
||||||
|
end_timestamp = datetime.fromtimestamp(data["end_timestamp"])
|
||||||
|
elif "end_timestamp" in data and isinstance(data["end_timestamp"], datetime):
|
||||||
|
end_timestamp = data["end_timestamp"]
|
||||||
|
else:
|
||||||
|
end_timestamp = None
|
||||||
|
|
||||||
|
if not isinstance(data["timestamp"], datetime):
|
||||||
|
timestamp = datetime.fromtimestamp(data["timestamp"])
|
||||||
|
else:
|
||||||
|
timestamp = data["timestamp"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
data["user_id"] = int(data["user_id"])
|
||||||
|
except ValueError:
|
||||||
|
data["user_id"] = 0
|
||||||
|
|
||||||
|
data.update({
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"end_timestamp": end_timestamp,
|
||||||
|
"duration": duration
|
||||||
|
})
|
||||||
|
return cls(bot=bot, **data)
|
396
aurora/models/moderation.py
Normal file
396
aurora/models/moderation.py
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from sqlite3 import Cursor
|
||||||
|
from time import time
|
||||||
|
from typing import Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import NotFound
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
from ..utilities.logger import logger
|
||||||
|
from ..utilities.utils import timedelta_to_string
|
||||||
|
from .base import AuroraGuildModel
|
||||||
|
from .change import Change
|
||||||
|
from .partials import PartialChannel, PartialRole, PartialUser
|
||||||
|
|
||||||
|
|
||||||
|
class Moderation(AuroraGuildModel):
|
||||||
|
moderation_id: int
|
||||||
|
timestamp: datetime
|
||||||
|
moderation_type: str
|
||||||
|
target_type: str
|
||||||
|
target_id: int
|
||||||
|
moderator_id: int
|
||||||
|
role_id: Optional[int] = None
|
||||||
|
duration: Optional[timedelta] = None
|
||||||
|
end_timestamp: Optional[datetime] = None
|
||||||
|
reason: Optional[str] = None
|
||||||
|
resolved: bool
|
||||||
|
resolved_by: Optional[int] = None
|
||||||
|
resolve_reason: Optional[str] = None
|
||||||
|
expired: bool
|
||||||
|
changes: List["Change"]
|
||||||
|
metadata: Dict
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self.moderation_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self) -> str:
|
||||||
|
return self.moderation_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unix_timestamp(self) -> int:
|
||||||
|
return int(self.timestamp.timestamp())
|
||||||
|
|
||||||
|
async def get_moderator(self) -> "PartialUser":
|
||||||
|
return await PartialUser.from_id(self.bot, self.moderator_id)
|
||||||
|
|
||||||
|
async def get_target(self) -> Union["PartialUser", "PartialChannel"]:
|
||||||
|
if self.target_type == "USER":
|
||||||
|
return await PartialUser.from_id(self.bot, self.target_id)
|
||||||
|
return await PartialChannel.from_id(self.bot, self.target_id)
|
||||||
|
|
||||||
|
async def get_resolved_by(self) -> Optional["PartialUser"]:
|
||||||
|
if self.resolved_by:
|
||||||
|
return await PartialUser.from_id(self.bot, self.resolved_by)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_role(self) -> Optional["PartialRole"]:
|
||||||
|
if self.role_id:
|
||||||
|
return await PartialRole.from_id(self.bot, self.guild_id, self.role_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.moderation_type} {self.target_type} {self.target_id} {self.reason}"
|
||||||
|
|
||||||
|
def __int__(self) -> int:
|
||||||
|
return self.moderation_id
|
||||||
|
|
||||||
|
async def resolve(self, resolved_by: int, reason: str) -> None:
|
||||||
|
if self.resolved:
|
||||||
|
raise ValueError("Case is already resolved!")
|
||||||
|
|
||||||
|
self.resolved = True
|
||||||
|
self.resolved_by = resolved_by
|
||||||
|
self.resolve_reason = reason
|
||||||
|
|
||||||
|
if self.type in ["UNMUTE", "UNBAN"]:
|
||||||
|
raise TypeError("Cannot resolve an unmute or unban case!")
|
||||||
|
|
||||||
|
if self.type == "MUTE":
|
||||||
|
try:
|
||||||
|
guild: discord.Guild = await self.bot.fetch_guild(self.guild_id)
|
||||||
|
member = await guild.fetch_member(self.target_id)
|
||||||
|
|
||||||
|
await member.timeout(
|
||||||
|
None, reason=f"Case {self.moderation_id} resolved by {resolved_by}{' for '+ reason if reason else ''}"
|
||||||
|
)
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.type in ["BAN", "TEMPBAN"]:
|
||||||
|
try:
|
||||||
|
guild: discord.Guild = await self.bot.fetch_guild(self.guild_id)
|
||||||
|
await guild.unban(await self.get_target(), reason=f"Case {self.moderation_id} resolved by {resolved_by}{' for '+ reason if reason else ''}")
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not self.changes:
|
||||||
|
self.changes.append(Change.from_dict(self.bot, {
|
||||||
|
"type": "ORIGINAL",
|
||||||
|
"timestamp": self.timestamp,
|
||||||
|
"reason": self.reason,
|
||||||
|
"user_id": self.moderator_id,
|
||||||
|
"duration": self.duration,
|
||||||
|
"end_timestamp": self.end_timestamp,
|
||||||
|
}))
|
||||||
|
self.changes.append(Change.from_dict(self.bot, {
|
||||||
|
"type": "RESOLVE",
|
||||||
|
"timestamp": datetime.now(),
|
||||||
|
"reason": reason,
|
||||||
|
"user_id": resolved_by,
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
from ..utilities.database import connect
|
||||||
|
from ..utilities.json import dumps
|
||||||
|
query = f"UPDATE moderation_{self.guild_id} SET timestamp = ?, moderation_type = ?, target_type = ?, moderator_id = ?, role_id = ?, duration = ?, end_timestamp = ?, reason = ?, resolved = ?, resolved_by = ?, resolve_reason = ?, expired = ?, changes = ?, metadata = ? WHERE moderation_id = ?;"
|
||||||
|
|
||||||
|
with connect() as database:
|
||||||
|
database.execute(query, (
|
||||||
|
self.timestamp.timestamp(),
|
||||||
|
self.moderation_type,
|
||||||
|
self.target_type,
|
||||||
|
self.moderator_id,
|
||||||
|
self.role_id,
|
||||||
|
str(self.duration) if self.duration else None,
|
||||||
|
self.end_timestamp.timestamp() if self.end_timestamp else None,
|
||||||
|
self.reason,
|
||||||
|
self.resolved,
|
||||||
|
self.resolved_by,
|
||||||
|
self.resolve_reason,
|
||||||
|
self.expired,
|
||||||
|
dumps(self.changes),
|
||||||
|
dumps(self.metadata),
|
||||||
|
self.moderation_id,
|
||||||
|
))
|
||||||
|
|
||||||
|
logger.debug("Row updated in moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
|
||||||
|
self.moderation_id,
|
||||||
|
self.guild_id,
|
||||||
|
self.timestamp.timestamp(),
|
||||||
|
self.moderation_type,
|
||||||
|
self.target_type,
|
||||||
|
self.moderator_id,
|
||||||
|
self.role_id,
|
||||||
|
str(self.duration) if self.duration else None,
|
||||||
|
self.end_timestamp.timestamp() if self.end_timestamp else None,
|
||||||
|
self.reason,
|
||||||
|
self.resolved,
|
||||||
|
self.resolved_by,
|
||||||
|
self.resolve_reason,
|
||||||
|
self.expired,
|
||||||
|
dumps(self.changes),
|
||||||
|
dumps(self.metadata),
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, bot: Red, data: dict) -> "Moderation":
|
||||||
|
return cls(bot=bot, **data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_result(cls, bot: Red, result: Iterable, guild_id: int) -> "Moderation":
|
||||||
|
if result[7] is not None and result[7] != "NULL":
|
||||||
|
hours, minutes, seconds = map(int, result[7].split(':'))
|
||||||
|
duration = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
||||||
|
else:
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
if result[14] is not None:
|
||||||
|
changes = json.loads(result[14])
|
||||||
|
change_obj_list = []
|
||||||
|
if changes:
|
||||||
|
for change in changes:
|
||||||
|
change_obj_list.append(Change.from_dict(bot=bot, data=change))
|
||||||
|
|
||||||
|
if result[15] is not None:
|
||||||
|
metadata = json.loads(result[15])
|
||||||
|
else:
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
|
case = {
|
||||||
|
"moderation_id": int(result[0]),
|
||||||
|
"guild_id": int(guild_id),
|
||||||
|
"timestamp": datetime.fromtimestamp(result[1]),
|
||||||
|
"moderation_type": str(result[2]),
|
||||||
|
"target_type": str(result[3]),
|
||||||
|
"target_id": int(result[4]),
|
||||||
|
"moderator_id": int(result[5]),
|
||||||
|
"role_id": int(result[6]) if result[6] is not None else None,
|
||||||
|
"duration": duration,
|
||||||
|
"end_timestamp": datetime.fromtimestamp(result[8]) if result[8] is not None else None,
|
||||||
|
"reason": result[9],
|
||||||
|
"resolved": bool(result[10]),
|
||||||
|
"resolved_by": result[11],
|
||||||
|
"resolve_reason": result[12],
|
||||||
|
"expired": bool(result[13]),
|
||||||
|
"changes": change_obj_list,
|
||||||
|
"metadata": metadata if metadata else {},
|
||||||
|
}
|
||||||
|
return cls.from_dict(bot=bot, data=case)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def execute(cls, bot: Red, guild_id: int, query: str, parameters: tuple | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||||
|
from ..utilities.database import connect
|
||||||
|
logger.trace("Executing query: %s", query)
|
||||||
|
logger.trace("With parameters: %s", parameters)
|
||||||
|
if not parameters:
|
||||||
|
parameters = ()
|
||||||
|
if not cursor:
|
||||||
|
no_cursor = True
|
||||||
|
database = connect()
|
||||||
|
cursor = database.cursor()
|
||||||
|
else:
|
||||||
|
no_cursor = False
|
||||||
|
|
||||||
|
cursor.execute(query, parameters)
|
||||||
|
results = cursor.fetchall()
|
||||||
|
if no_cursor:
|
||||||
|
cursor.close()
|
||||||
|
database.close()
|
||||||
|
|
||||||
|
if results:
|
||||||
|
cases = []
|
||||||
|
for result in results:
|
||||||
|
case = cls.from_result(bot=bot, result=result, guild_id=guild_id)
|
||||||
|
if case.moderation_id != 0:
|
||||||
|
cases.append(case)
|
||||||
|
return tuple(cases)
|
||||||
|
return ()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_latest(cls, bot: Red, guild_id: int, limit: int | None = None, offset: int = 0, types: Iterable | 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])})"
|
||||||
|
params.extend(types)
|
||||||
|
if limit:
|
||||||
|
query += " LIMIT ? OFFSET ?"
|
||||||
|
params.extend((limit, offset))
|
||||||
|
query += ";"
|
||||||
|
return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=tuple(params) if params else (), cursor=cursor)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_next_case_number(cls, bot: Red, guild_id: int, cursor: Cursor | None = None) -> int:
|
||||||
|
result = cls.get_latest(bot=bot, guild_id=guild_id, cursor=cursor, limit=1)
|
||||||
|
return (result[0].moderation_id + 1) if result else 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_by_id(cls, bot: Red, moderation_id: int, guild_id: int, cursor: Cursor | None = None) -> "Moderation":
|
||||||
|
query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;"
|
||||||
|
case = cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderation_id,), cursor=cursor)
|
||||||
|
if case:
|
||||||
|
return case[0]
|
||||||
|
raise ValueError(f"Case {moderation_id} not found in moderation_{guild_id}!")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_by_target(cls, bot: Red, guild_id: int, target: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||||
|
query = f"SELECT * FROM moderation_{guild_id} WHERE target_id = ?"
|
||||||
|
if types:
|
||||||
|
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
||||||
|
query += " ORDER BY moderation_id DESC;"
|
||||||
|
|
||||||
|
return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(target, *types) if types else (target,), cursor=cursor)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_by_moderator(cls, bot: Red, guild_id: int, moderator: int, types: Iterable | None = None, cursor: Cursor | None = None) -> Tuple["Moderation"]:
|
||||||
|
query = f"SELECT * FROM moderation_{guild_id} WHERE moderator_id = ?"
|
||||||
|
if types:
|
||||||
|
query += f" AND moderation_type IN ({', '.join(['?' for _ in types])})"
|
||||||
|
query += " ORDER BY moderation_id DESC;"
|
||||||
|
|
||||||
|
return cls.execute(bot=bot, guild_id=guild_id, query=query, parameters=(moderator, *types) if types else (moderator,), cursor=cursor)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def log(
|
||||||
|
cls,
|
||||||
|
bot: Red,
|
||||||
|
guild_id: int,
|
||||||
|
moderator_id: int,
|
||||||
|
moderation_type: str,
|
||||||
|
target_type: str,
|
||||||
|
target_id: int,
|
||||||
|
role_id: int | None = None,
|
||||||
|
duration: timedelta | None = None,
|
||||||
|
reason: str | None = None,
|
||||||
|
database: sqlite3.Connection | None = None,
|
||||||
|
timestamp: datetime | None = None,
|
||||||
|
resolved: bool = False,
|
||||||
|
resolved_by: int | None = None,
|
||||||
|
resolved_reason: str | None = None,
|
||||||
|
expired: bool | None = None,
|
||||||
|
changes: list | None = None,
|
||||||
|
metadata: dict | None = None,
|
||||||
|
return_obj: bool = True,
|
||||||
|
) -> Union["Moderation", int]:
|
||||||
|
from ..utilities.database import connect
|
||||||
|
from ..utilities.json import dumps
|
||||||
|
if not timestamp:
|
||||||
|
timestamp = datetime.fromtimestamp(time())
|
||||||
|
elif not isinstance(timestamp, datetime):
|
||||||
|
timestamp = datetime.fromtimestamp(timestamp)
|
||||||
|
|
||||||
|
if duration == "NULL":
|
||||||
|
duration = None
|
||||||
|
|
||||||
|
if duration is not None:
|
||||||
|
end_timestamp = timestamp + duration
|
||||||
|
else:
|
||||||
|
duration = None
|
||||||
|
end_timestamp = None
|
||||||
|
|
||||||
|
if not expired:
|
||||||
|
if end_timestamp:
|
||||||
|
expired = bool(timestamp > end_timestamp)
|
||||||
|
else:
|
||||||
|
expired = False
|
||||||
|
|
||||||
|
if reason == "NULL":
|
||||||
|
reason = None
|
||||||
|
|
||||||
|
if resolved_by in ["NULL", "?"]:
|
||||||
|
resolved_by = None
|
||||||
|
|
||||||
|
if resolved_reason == "NULL":
|
||||||
|
resolved_reason = None
|
||||||
|
|
||||||
|
if role_id == 0:
|
||||||
|
role_id = None
|
||||||
|
|
||||||
|
if not database:
|
||||||
|
database = connect()
|
||||||
|
close_db = True
|
||||||
|
else:
|
||||||
|
close_db = False
|
||||||
|
cursor = database.cursor()
|
||||||
|
|
||||||
|
moderation_id = cls.get_next_case_number(bot=bot, guild_id=guild_id, cursor=cursor)
|
||||||
|
|
||||||
|
case = {
|
||||||
|
"moderation_id": moderation_id,
|
||||||
|
"timestamp": timestamp.timestamp(),
|
||||||
|
"moderation_type": moderation_type,
|
||||||
|
"target_type": target_type,
|
||||||
|
"target_id": target_id,
|
||||||
|
"moderator_id": moderator_id,
|
||||||
|
"role_id": role_id,
|
||||||
|
"duration": timedelta_to_string(duration) if duration else None,
|
||||||
|
"end_timestamp": end_timestamp.timestamp() if end_timestamp else None,
|
||||||
|
"reason": reason,
|
||||||
|
"resolved": resolved,
|
||||||
|
"resolved_by": resolved_by,
|
||||||
|
"resolve_reason": resolved_reason,
|
||||||
|
"expired": expired,
|
||||||
|
"changes": dumps(changes),
|
||||||
|
"metadata": dumps(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||||
|
cursor.execute(sql, tuple(case.values()))
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
database.commit()
|
||||||
|
if close_db:
|
||||||
|
database.close()
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
|
||||||
|
guild_id,
|
||||||
|
case["moderation_id"],
|
||||||
|
case["timestamp"],
|
||||||
|
case["moderation_type"],
|
||||||
|
case["target_type"],
|
||||||
|
case["target_id"],
|
||||||
|
case["moderator_id"],
|
||||||
|
case["role_id"],
|
||||||
|
case["duration"],
|
||||||
|
case["end_timestamp"],
|
||||||
|
case["reason"],
|
||||||
|
case["resolved"],
|
||||||
|
case["resolved_by"],
|
||||||
|
case["resolve_reason"],
|
||||||
|
case["expired"],
|
||||||
|
case["changes"],
|
||||||
|
case["metadata"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if return_obj:
|
||||||
|
return cls.find_by_id(bot=bot, moderation_id=moderation_id, guild_id=guild_id)
|
||||||
|
return moderation_id
|
79
aurora/models/partials.py
Normal file
79
aurora/models/partials.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
from discord import Forbidden, HTTPException, InvalidData, NotFound
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
from .base import AuroraBaseModel, AuroraGuildModel
|
||||||
|
|
||||||
|
|
||||||
|
class PartialUser(AuroraBaseModel):
|
||||||
|
id: int
|
||||||
|
username: str
|
||||||
|
discriminator: int
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return f"{self.username}#{self.discriminator}" if self.discriminator != 0 else self.username
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def from_id(cls, bot: Red, user_id: int) -> "PartialUser":
|
||||||
|
user = bot.get_user(user_id)
|
||||||
|
if not user:
|
||||||
|
try:
|
||||||
|
user = await bot.fetch_user(user_id)
|
||||||
|
return cls(bot=bot, id=user.id, username=user.name, discriminator=user.discriminator)
|
||||||
|
except NotFound:
|
||||||
|
return cls(bot=bot, id=user_id, username="Deleted User", discriminator=0)
|
||||||
|
return cls(bot=bot, id=user.id, username=user.name, discriminator=user.discriminator)
|
||||||
|
|
||||||
|
|
||||||
|
class PartialChannel(AuroraGuildModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mention(self):
|
||||||
|
if self.name in ["Deleted Channel", "Forbidden Channel"]:
|
||||||
|
return self.name
|
||||||
|
return f"<#{self.id}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.mention
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def from_id(cls, bot: Red, channel_id: int) -> "PartialChannel":
|
||||||
|
channel = bot.get_channel(channel_id)
|
||||||
|
if not channel:
|
||||||
|
try:
|
||||||
|
channel = await bot.fetch_channel(channel_id)
|
||||||
|
return cls(bot=bot, guild_id=channel.guild.id, id=channel.id, name=channel.name)
|
||||||
|
except (NotFound, InvalidData, HTTPException, Forbidden) as e:
|
||||||
|
if e == Forbidden:
|
||||||
|
return cls(bot=bot, guild_id=0, id=channel_id, name="Forbidden Channel")
|
||||||
|
return cls(bot=bot, guild_id=0, id=channel_id, name="Deleted Channel")
|
||||||
|
return cls(bot=bot, guild_id=channel.guild.id, id=channel.id, name=channel.name)
|
||||||
|
|
||||||
|
class PartialRole(AuroraGuildModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mention(self):
|
||||||
|
if self.name in ["Deleted Role", "Forbidden Role"]:
|
||||||
|
return self.name
|
||||||
|
return f"<@&{self.id}>"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.mention
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def from_id(cls, bot: Red, guild_id: int, role_id: int) -> "PartialRole":
|
||||||
|
try:
|
||||||
|
guild = await bot.fetch_guild(guild_id, with_counts=False)
|
||||||
|
except (Forbidden, HTTPException):
|
||||||
|
return cls(bot=bot, guild_id=guild_id, id=role_id, name="Forbidden Role")
|
||||||
|
role = guild.get_role(role_id)
|
||||||
|
if not role:
|
||||||
|
return cls(bot=bot, guild_id=guild_id, id=role_id, name="Deleted Role")
|
||||||
|
return cls(bot=bot, guild_id=guild_id, id=role.id, name=role.name)
|
|
@ -1,15 +1,11 @@
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
from discord import Guild
|
from discord import Guild
|
||||||
from redbot.core import data_manager
|
from redbot.core import data_manager
|
||||||
|
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
from .utils import (convert_timedelta_to_str, generate_dict,
|
|
||||||
get_next_case_number)
|
|
||||||
|
|
||||||
|
|
||||||
def connect() -> sqlite3.Connection:
|
def connect() -> sqlite3.Connection:
|
||||||
|
@ -42,9 +38,9 @@ async def create_guild_table(guild: Guild):
|
||||||
timestamp INTEGER NOT NULL,
|
timestamp INTEGER NOT NULL,
|
||||||
moderation_type TEXT NOT NULL,
|
moderation_type TEXT NOT NULL,
|
||||||
target_type TEXT NOT NULL,
|
target_type TEXT NOT NULL,
|
||||||
target_id TEXT NOT NULL,
|
target_id INTEGER NOT NULL,
|
||||||
moderator_id TEXT NOT NULL,
|
moderator_id INTEGER NOT NULL,
|
||||||
role_id TEXT,
|
role_id INTEGER,
|
||||||
duration TEXT,
|
duration TEXT,
|
||||||
end_timestamp INTEGER,
|
end_timestamp INTEGER,
|
||||||
reason TEXT,
|
reason TEXT,
|
||||||
|
@ -52,8 +48,8 @@ async def create_guild_table(guild: Guild):
|
||||||
resolved_by TEXT,
|
resolved_by TEXT,
|
||||||
resolve_reason TEXT,
|
resolve_reason TEXT,
|
||||||
expired INTEGER NOT NULL,
|
expired INTEGER NOT NULL,
|
||||||
changes TEXT NOT NULL,
|
changes JSON NOT NULL,
|
||||||
metadata TEXT NOT NULL
|
metadata JSON NOT NULL
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
|
@ -79,13 +75,13 @@ async def create_guild_table(guild: Guild):
|
||||||
"NULL",
|
"NULL",
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
0,
|
0,
|
||||||
"NULL",
|
None,
|
||||||
0,
|
None,
|
||||||
"NULL",
|
|
||||||
0,
|
|
||||||
"NULL",
|
|
||||||
"NULL",
|
|
||||||
0,
|
0,
|
||||||
json.dumps([]),
|
json.dumps([]),
|
||||||
json.dumps({}),
|
json.dumps({}),
|
||||||
|
@ -102,118 +98,3 @@ async def create_guild_table(guild: Guild):
|
||||||
)
|
)
|
||||||
|
|
||||||
database.close()
|
database.close()
|
||||||
|
|
||||||
|
|
||||||
async def mysql_log(
|
|
||||||
guild_id: str,
|
|
||||||
author_id: str,
|
|
||||||
moderation_type: str,
|
|
||||||
target_type: str,
|
|
||||||
target_id: int,
|
|
||||||
role_id: int,
|
|
||||||
duration: timedelta,
|
|
||||||
reason: str,
|
|
||||||
database: sqlite3.Connection = None,
|
|
||||||
timestamp: int = None,
|
|
||||||
resolved: bool = False,
|
|
||||||
resolved_by: str = None,
|
|
||||||
resolved_reason: str = None,
|
|
||||||
expired: bool = None,
|
|
||||||
changes: list = None,
|
|
||||||
metadata: dict = None,
|
|
||||||
) -> int:
|
|
||||||
if not timestamp:
|
|
||||||
timestamp = int(time.time())
|
|
||||||
|
|
||||||
if duration != "NULL":
|
|
||||||
end_timedelta = datetime.fromtimestamp(timestamp) + duration
|
|
||||||
end_timestamp = int(end_timedelta.timestamp())
|
|
||||||
|
|
||||||
duration = convert_timedelta_to_str(duration)
|
|
||||||
else:
|
|
||||||
end_timestamp = 0
|
|
||||||
|
|
||||||
if not expired:
|
|
||||||
if int(time.time()) > end_timestamp:
|
|
||||||
expired = 1
|
|
||||||
else:
|
|
||||||
expired = 0
|
|
||||||
|
|
||||||
if resolved_by is None:
|
|
||||||
resolved_by = "NULL"
|
|
||||||
|
|
||||||
if resolved_reason is None:
|
|
||||||
resolved_reason = "NULL"
|
|
||||||
|
|
||||||
if not database:
|
|
||||||
database = connect()
|
|
||||||
close_db = True
|
|
||||||
else:
|
|
||||||
close_db = False
|
|
||||||
cursor = database.cursor()
|
|
||||||
|
|
||||||
moderation_id = await get_next_case_number(guild_id=guild_id, cursor=cursor)
|
|
||||||
|
|
||||||
sql = f"INSERT INTO `moderation_{guild_id}` (moderation_id, timestamp, moderation_type, target_type, target_id, moderator_id, role_id, duration, end_timestamp, reason, resolved, resolved_by, resolve_reason, expired, changes, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
val = (
|
|
||||||
moderation_id,
|
|
||||||
timestamp,
|
|
||||||
moderation_type,
|
|
||||||
target_type,
|
|
||||||
target_id,
|
|
||||||
author_id,
|
|
||||||
role_id,
|
|
||||||
duration,
|
|
||||||
end_timestamp,
|
|
||||||
reason,
|
|
||||||
int(resolved),
|
|
||||||
resolved_by,
|
|
||||||
resolved_reason,
|
|
||||||
expired,
|
|
||||||
json.dumps(changes if changes else []),
|
|
||||||
json.dumps(metadata if metadata else {}),
|
|
||||||
)
|
|
||||||
cursor.execute(sql, val)
|
|
||||||
|
|
||||||
cursor.close()
|
|
||||||
database.commit()
|
|
||||||
if close_db:
|
|
||||||
database.close()
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"Row inserted into moderation_%s!\n%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
|
|
||||||
guild_id,
|
|
||||||
moderation_id,
|
|
||||||
timestamp,
|
|
||||||
moderation_type,
|
|
||||||
target_type,
|
|
||||||
target_id,
|
|
||||||
author_id,
|
|
||||||
role_id,
|
|
||||||
duration,
|
|
||||||
end_timestamp,
|
|
||||||
reason,
|
|
||||||
int(resolved),
|
|
||||||
resolved_by,
|
|
||||||
resolved_reason,
|
|
||||||
expired,
|
|
||||||
changes,
|
|
||||||
metadata,
|
|
||||||
)
|
|
||||||
|
|
||||||
return moderation_id
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_case(moderation_id: int, guild_id: str) -> dict:
|
|
||||||
"""This method fetches a case from the database and returns the case's dictionary."""
|
|
||||||
database = connect()
|
|
||||||
cursor = database.cursor()
|
|
||||||
|
|
||||||
query = f"SELECT * FROM moderation_{guild_id} WHERE moderation_id = ?;"
|
|
||||||
cursor.execute(query, (moderation_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
|
|
||||||
cursor.close()
|
|
||||||
database.close()
|
|
||||||
|
|
||||||
return generate_dict(result)
|
|
||||||
|
|
|
@ -4,25 +4,30 @@ from typing import Union
|
||||||
|
|
||||||
from discord import Color, Embed, Guild, Interaction, InteractionMessage, Member, Role, User
|
from discord import Color, Embed, Guild, Interaction, InteractionMessage, Member, Role, User
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.bot import Red
|
||||||
from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning
|
from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning
|
||||||
|
|
||||||
from aurora.utilities.config import config
|
from ..models.moderation import Moderation
|
||||||
from aurora.utilities.utils import fetch_channel_dict, fetch_user_dict, get_bool_emoji, get_next_case_number, get_pagesize_str
|
from ..models.partials import PartialUser
|
||||||
|
from .config import config
|
||||||
|
from .utils import get_bool_emoji, get_pagesize_str
|
||||||
|
|
||||||
|
|
||||||
async def message_factory(
|
async def message_factory(
|
||||||
|
bot: Red,
|
||||||
color: Color,
|
color: Color,
|
||||||
guild: Guild,
|
guild: Guild,
|
||||||
reason: str,
|
reason: str,
|
||||||
moderation_type: str,
|
moderation_type: str,
|
||||||
moderator: Union[Member, User] = None,
|
moderator: Union[Member, User] | None = None,
|
||||||
duration: timedelta = None,
|
duration: timedelta | None = None,
|
||||||
response: InteractionMessage = None,
|
response: InteractionMessage | None = None,
|
||||||
role: Role = None,
|
role: Role | None = None,
|
||||||
) -> Embed:
|
) -> Embed:
|
||||||
"""This function creates a message from set parameters, meant for contacting the moderated user.
|
"""This function creates a message from set parameters, meant for contacting the moderated user.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
bot (Red): The bot instance.
|
||||||
color (Color): The color of the embed.
|
color (Color): The color of the embed.
|
||||||
guild (Guild): The guild the moderation occurred in.
|
guild (Guild): The guild the moderation occurred in.
|
||||||
reason (str): The reason for the moderation.
|
reason (str): The reason for the moderation.
|
||||||
|
@ -86,7 +91,7 @@ async def message_factory(
|
||||||
embed.set_author(name=guild.name)
|
embed.set_author(name=guild.name)
|
||||||
|
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=f"Case #{await get_next_case_number(guild.id):,}",
|
text=f"Case #{Moderation.get_next_case_number(bot=bot, guild_id=guild.id):,}",
|
||||||
icon_url="attachment://arrow.png",
|
icon_url="attachment://arrow.png",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,256 +99,173 @@ async def message_factory(
|
||||||
|
|
||||||
|
|
||||||
async def log_factory(
|
async def log_factory(
|
||||||
interaction: Interaction, case_dict: dict, resolved: bool = False
|
interaction: Interaction, moderation: Moderation, resolved: bool = False
|
||||||
) -> Embed:
|
) -> Embed:
|
||||||
"""This function creates a log embed from set parameters, meant for moderation logging.
|
"""This function creates a log embed from set parameters, meant for moderation logging.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (Interaction): The interaction object.
|
interaction (discord.Interaction): The interaction object.
|
||||||
case_dict (dict): The case dictionary.
|
moderation (aurora.models.Moderation): The moderation object.
|
||||||
resolved (bool, optional): Whether the case is resolved or not. Defaults to False.
|
resolved (bool, optional): Whether the case is resolved or not. Defaults to False.
|
||||||
"""
|
"""
|
||||||
|
target = await moderation.get_target()
|
||||||
|
moderator = await moderation.get_moderator()
|
||||||
if resolved:
|
if resolved:
|
||||||
if case_dict["target_type"] == "USER":
|
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
|
||||||
target_name = (
|
|
||||||
f"`{target_user['name']}`"
|
|
||||||
if target_user["discriminator"] == "0"
|
|
||||||
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
|
||||||
if target_user["mention"]:
|
|
||||||
target_name = f"{target_user['mention']}"
|
|
||||||
else:
|
|
||||||
target_name = f"`{target_user['name']}`"
|
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
|
||||||
moderator_name = (
|
|
||||||
f"`{moderator_user['name']}`"
|
|
||||||
if moderator_user["discriminator"] == "0"
|
|
||||||
else f"`{moderator_user['name']}#{moderator_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,} Resolved",
|
title=f"📕 Case #{moderation.id:,} Resolved",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await interaction.client.get_embed_color(interaction.channel),
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
resolved_by = await moderation.get_resolved_by()
|
||||||
|
embed.description = f"**Type:** {str.title(moderation.moderation_type)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"
|
||||||
|
|
||||||
if case_dict["duration"] != "NULL":
|
if moderation.duration is not None:
|
||||||
td = timedelta(
|
|
||||||
**{
|
|
||||||
unit: int(val)
|
|
||||||
for unit, val in zip(
|
|
||||||
["hours", "minutes", "seconds"],
|
|
||||||
case_dict["duration"].split(":"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
duration_embed = (
|
duration_embed = (
|
||||||
f"{humanize_timedelta(timedelta=td)} | <t:{case_dict['end_timestamp']}:R>"
|
f"{humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.end_timestamp}:R>"
|
||||||
if case_dict["expired"] == "0"
|
if not moderation.expired
|
||||||
else str(humanize_timedelta(timedelta=td))
|
else str(humanize_timedelta(timedelta=moderation.duration))
|
||||||
)
|
)
|
||||||
embed.description = (
|
embed.description = (
|
||||||
embed.description
|
embed.description
|
||||||
+ f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}"
|
+ f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}"
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
if moderation.metadata.items():
|
||||||
|
for key, value in moderation.metadata.items():
|
||||||
|
embed.description += f"\n**{key.title()}:** {value}"
|
||||||
|
|
||||||
|
embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
|
||||||
|
|
||||||
resolved_user = await fetch_user_dict(interaction.client, case_dict["resolved_by"])
|
|
||||||
resolved_name = (
|
|
||||||
resolved_user["name"]
|
|
||||||
if resolved_user["discriminator"] == "0"
|
|
||||||
else f"{resolved_user['name']}#{resolved_user['discriminator']}"
|
|
||||||
)
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Resolve Reason",
|
name="Resolve Reason",
|
||||||
value=f"Resolved by `{resolved_name}` ({resolved_user['id']}) for:\n"
|
value=f"Resolved by `{resolved_by.name}` ({resolved_by.id}) for:\n"
|
||||||
+ box(case_dict["resolve_reason"]),
|
+ box(moderation.resolve_reason),
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if case_dict["target_type"] == "USER":
|
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
|
||||||
target_name = (
|
|
||||||
f"`{target_user['name']}`"
|
|
||||||
if target_user["discriminator"] == "0"
|
|
||||||
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
|
||||||
if target_user["mention"]:
|
|
||||||
target_name = target_user["mention"]
|
|
||||||
else:
|
|
||||||
target_name = f"`{target_user['name']}`"
|
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
|
||||||
moderator_name = (
|
|
||||||
f"`{moderator_user['name']}`"
|
|
||||||
if moderator_user["discriminator"] == "0"
|
|
||||||
else f"`{moderator_user['name']}#{moderator_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,}",
|
title=f"📕 Case #{moderation.id:,}",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await interaction.client.get_embed_color(interaction.channel),
|
||||||
)
|
)
|
||||||
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
embed.description = f"**Type:** {str.title(moderation.type)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"
|
||||||
|
|
||||||
if case_dict["duration"] != "NULL":
|
if moderation.duration:
|
||||||
td = timedelta(
|
|
||||||
**{
|
|
||||||
unit: int(val)
|
|
||||||
for unit, val in zip(
|
|
||||||
["hours", "minutes", "seconds"],
|
|
||||||
case_dict["duration"].split(":"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
embed.description = (
|
embed.description = (
|
||||||
embed.description
|
embed.description
|
||||||
+ f"\n**Duration:** {humanize_timedelta(timedelta=td)} | <t:{case_dict['end_timestamp']}:R>"
|
+ f"\n**Duration:** {humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.unix_timestamp}:R>"
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
if moderation.metadata.items():
|
||||||
|
for key, value in moderation.metadata.items():
|
||||||
|
embed.description += f"\n**{key.title()}:** {value}"
|
||||||
|
|
||||||
|
embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def case_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
async def case_factory(interaction: Interaction, moderation: Moderation) -> Embed:
|
||||||
"""This function creates a case embed from set parameters.
|
"""This function creates a case embed from set parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (Interaction): The interaction object.
|
interaction (discord.Interaction): The interaction object.
|
||||||
case_dict (dict): The case dictionary.
|
moderation (aurora.models.Moderation): The moderation object.
|
||||||
"""
|
"""
|
||||||
if case_dict["target_type"] == "USER":
|
target = await moderation.get_target()
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
moderator = await moderation.get_moderator()
|
||||||
target_name = (
|
|
||||||
f"`{target_user['name']}`"
|
|
||||||
if target_user["discriminator"] == "0"
|
|
||||||
else f"`{target_user['name']}#{target_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
|
||||||
if target_user["mention"]:
|
|
||||||
target_name = f"{target_user['mention']}"
|
|
||||||
else:
|
|
||||||
target_name = f"`{target_user['name']}`"
|
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
|
||||||
moderator_name = (
|
|
||||||
f"`{moderator_user['name']}`"
|
|
||||||
if moderator_user["discriminator"] == "0"
|
|
||||||
else f"`{moderator_user['name']}#{moderator_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
|
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,}",
|
title=f"📕 Case #{moderation.id:,}",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await interaction.client.get_embed_color(interaction.channel),
|
||||||
)
|
)
|
||||||
embed.description = f"**Type:** {str.title(case_dict['moderation_type'])}\n**Target:** {target_name} ({target_user['id']})\n**Moderator:** {moderator_name} ({moderator_user['id']})\n**Resolved:** {bool(case_dict['resolved'])}\n**Timestamp:** <t:{case_dict['timestamp']}> | <t:{case_dict['timestamp']}:R>"
|
embed.description = f"**Type:** {str.title(moderation.type)}\n**Target:** `{target.name}` ({target.id})\n**Moderator:** `{moderator.name}` ({moderator.id})\n**Resolved:** {moderation.resolved}\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"
|
||||||
|
|
||||||
if case_dict["duration"] != "NULL":
|
if moderation.duration:
|
||||||
td = timedelta(
|
|
||||||
**{
|
|
||||||
unit: int(val)
|
|
||||||
for unit, val in zip(
|
|
||||||
["hours", "minutes", "seconds"], case_dict["duration"].split(":")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
duration_embed = (
|
duration_embed = (
|
||||||
f"{humanize_timedelta(timedelta=td)} | <t:{case_dict['end_timestamp']}:R>"
|
f"{humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.unix_timestamp}:R>"
|
||||||
if bool(case_dict["expired"]) is False
|
if moderation.expired is False
|
||||||
else str(humanize_timedelta(timedelta=td))
|
else str(humanize_timedelta(timedelta=moderation.duration))
|
||||||
)
|
)
|
||||||
embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {bool(case_dict['expired'])}"
|
embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}"
|
||||||
|
|
||||||
embed.description += (
|
embed.description += (
|
||||||
f"\n**Changes:** {len(case_dict['changes']) - 1}"
|
f"\n**Changes:** {len(moderation.changes) - 1}"
|
||||||
if case_dict["changes"]
|
if moderation.changes
|
||||||
else "\n**Changes:** 0"
|
else "\n**Changes:** 0"
|
||||||
)
|
)
|
||||||
|
|
||||||
if case_dict["role_id"]:
|
if moderation.role_id:
|
||||||
embed.description += f"\n**Role:** <@&{case_dict['role_id']}>"
|
role = await moderation.get_role()
|
||||||
|
embed.description += f"\n**Role:** {role.name}"
|
||||||
|
|
||||||
if case_dict["metadata"]:
|
if moderation.metadata:
|
||||||
if case_dict["metadata"]["imported_from"]:
|
if moderation.metadata.get("imported_from"):
|
||||||
embed.description += (
|
embed.description += (
|
||||||
f"\n**Imported From:** {case_dict['metadata']['imported_from']}"
|
f"\n**Imported From:** {moderation.metadata['imported_from']}"
|
||||||
)
|
)
|
||||||
|
moderation.metadata.pop("imported_from")
|
||||||
|
if moderation.metadata.get("imported_timestamp"):
|
||||||
|
embed.description += (
|
||||||
|
f"\n**Imported Timestamp:** <t:{moderation.metadata['imported_timestamp']}> | <t:{moderation.metadata['imported_timestamp']}:R>"
|
||||||
|
)
|
||||||
|
moderation.metadata.pop("imported_timestamp")
|
||||||
|
if moderation.metadata.items():
|
||||||
|
for key, value in moderation.metadata.items():
|
||||||
|
embed.description += f"\n**{key.title()}:** {value}"
|
||||||
|
|
||||||
embed.add_field(name="Reason", value=box(case_dict["reason"]), inline=False)
|
embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
|
||||||
|
|
||||||
if case_dict["resolved"] == 1:
|
if moderation.resolved:
|
||||||
resolved_user = await fetch_user_dict(interaction.client, case_dict["resolved_by"])
|
resolved_user = await moderation.get_resolved_by()
|
||||||
resolved_name = (
|
|
||||||
f"`{resolved_user['name']}`"
|
|
||||||
if resolved_user["discriminator"] == "0"
|
|
||||||
else f"`{resolved_user['name']}#{resolved_user['discriminator']}`"
|
|
||||||
)
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Resolve Reason",
|
name="Resolve Reason",
|
||||||
value=f"Resolved by {resolved_name} ({resolved_user['id']}) for:\n{box(case_dict['resolve_reason'])}",
|
value=f"Resolved by `{resolved_user.name}` ({resolved_user.id}) for:\n{box(moderation.resolve_reason)}",
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
async def changes_factory(interaction: Interaction, moderation: Moderation) -> Embed:
|
||||||
"""This function creates a changes embed from set parameters.
|
"""This function creates a changes embed from set parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (Interaction): The interaction object.
|
interaction (discord.Interaction): The interaction object.
|
||||||
case_dict (dict): The case dictionary.
|
moderation (aurora.models.Moderation): The moderation object.
|
||||||
"""
|
"""
|
||||||
embed = Embed(
|
embed = Embed(
|
||||||
title=f"📕 Case #{case_dict['moderation_id']:,} Changes",
|
title=f"📕 Case #{moderation.id:,} Changes",
|
||||||
color=await interaction.client.get_embed_color(interaction.channel),
|
color=await interaction.client.get_embed_color(interaction.channel),
|
||||||
)
|
)
|
||||||
|
|
||||||
memory_dict = {}
|
memory_dict = {}
|
||||||
|
|
||||||
if case_dict["changes"]:
|
if moderation.changes:
|
||||||
for change in case_dict["changes"]:
|
for change in moderation.changes:
|
||||||
if change["user_id"] not in memory_dict:
|
if change.user_id not in memory_dict:
|
||||||
memory_dict[str(change["user_id"])] = await fetch_user_dict(
|
memory_dict[str(change.user_id)] = await change.get_user()
|
||||||
interaction.client, change["user_id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
user = memory_dict[str(change["user_id"])]
|
user: PartialUser = memory_dict[str(change.user_id)]
|
||||||
name = (
|
|
||||||
user["name"]
|
|
||||||
if user["discriminator"] == "0"
|
|
||||||
else f"{user['name']}#{user['discriminator']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
timestamp = f"<t:{change['timestamp']}> | <t:{change['timestamp']}:R>"
|
timestamp = f"<t:{change.unix_timestamp}> | <t:{change.unix_timestamp}:R>"
|
||||||
|
|
||||||
if change["type"] == "ORIGINAL":
|
if change.type == "ORIGINAL":
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Original",
|
name="Original",
|
||||||
value=f"**User:** `{name}` ({user['id']})\n**Reason:** {change['reason']}\n**Timestamp:** {timestamp}",
|
value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}",
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif change["type"] == "EDIT":
|
elif change.type == "EDIT":
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Edit",
|
name="Edit",
|
||||||
value=f"**User:** `{name}` ({user['id']})\n**Reason:** {change['reason']}\n**Timestamp:** {timestamp}",
|
value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}",
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif change["type"] == "RESOLVE":
|
elif change.type == "RESOLVE":
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="Resolve",
|
name="Resolve",
|
||||||
value=f"**User:** `{name}` ({user['id']})\n**Reason:** {change['reason']}\n**Timestamp:** {timestamp}",
|
value=f"**User:** `{user.name}` ({user.id})\n**Reason:** {change.reason}\n**Timestamp:** {timestamp}",
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -353,44 +275,29 @@ async def changes_factory(interaction: Interaction, case_dict: dict) -> Embed:
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
|
|
||||||
async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> str:
|
async def evidenceformat_factory(moderation: Moderation) -> str:
|
||||||
"""This function creates a codeblock in evidence format from set parameters.
|
"""This function creates a codeblock in evidence format from set parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
interaction (Interaction): The interaction object.
|
interaction (discord.Interaction): The interaction object.
|
||||||
case_dict (dict): The case dictionary.
|
moderation (aurora.models.Moderation): The moderation object.
|
||||||
"""
|
"""
|
||||||
if case_dict["target_type"] == "USER":
|
target = await moderation.get_target()
|
||||||
target_user = await fetch_user_dict(interaction.client, case_dict["target_id"])
|
moderator = await moderation.get_moderator()
|
||||||
target_name = (
|
|
||||||
target_user["name"]
|
|
||||||
if target_user["discriminator"] == "0"
|
|
||||||
else f"{target_user['name']}#{target_user['discriminator']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
elif case_dict["target_type"] == "CHANNEL":
|
content = f"Case: {moderation.id:,} ({str.title(moderation.type)})\nTarget: {target.name} ({target.id})\nModerator: {moderator.name} ({moderator.id})"
|
||||||
target_user = await fetch_channel_dict(interaction.guild, case_dict["target_id"])
|
|
||||||
target_name = target_user["name"]
|
|
||||||
|
|
||||||
moderator_user = await fetch_user_dict(interaction.client, case_dict["moderator_id"])
|
if moderation.duration is not None:
|
||||||
moderator_name = (
|
content += f"\nDuration: {humanize_timedelta(timedelta=moderation.duration)}"
|
||||||
moderator_user["name"]
|
|
||||||
if moderator_user["discriminator"] == "0"
|
|
||||||
else f"{moderator_user['name']}#{moderator_user['discriminator']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
content = f"Case: {case_dict['moderation_id']:,} ({str.title(case_dict['moderation_type'])})\nTarget: {target_name} ({target_user['id']})\nModerator: {moderator_name} ({moderator_user['id']})"
|
if moderation.role_id:
|
||||||
|
role = await moderation.get_role()
|
||||||
|
content += "\nRole: " + (role.name if role is not None else moderation.role_id)
|
||||||
|
|
||||||
if case_dict["role_id"] != "0":
|
content += f"\nReason: {moderation.reason}"
|
||||||
role = interaction.guild.get_role(int(case_dict["role_id"]))
|
|
||||||
content += "\nRole: " + (role.name if role is not None else case_dict["role_id"])
|
|
||||||
|
|
||||||
if case_dict["duration"] != "NULL":
|
for key, value in moderation.metadata.items():
|
||||||
hours, minutes, seconds = map(int, case_dict["duration"].split(":"))
|
content += f"\n{key.title()}: {value}"
|
||||||
td = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
|
||||||
content += f"\nDuration: {humanize_timedelta(timedelta=td)}"
|
|
||||||
|
|
||||||
content += f"\nReason: {case_dict['reason']}"
|
|
||||||
|
|
||||||
return box(content, "prolog")
|
return box(content, "prolog")
|
||||||
|
|
||||||
|
|
131
aurora/utilities/json.py
Normal file
131
aurora/utilities/json.py
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
|
from ..models.base import AuroraBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class JSONEncoder(json.JSONEncoder):
|
||||||
|
def default(self, o) -> Any:
|
||||||
|
match o:
|
||||||
|
case datetime():
|
||||||
|
return int(o.timestamp())
|
||||||
|
case timedelta():
|
||||||
|
return str(o)
|
||||||
|
case AuroraBaseModel():
|
||||||
|
return o.dump()
|
||||||
|
case Red():
|
||||||
|
return None
|
||||||
|
case _:
|
||||||
|
return super().default(o)
|
||||||
|
|
||||||
|
|
||||||
|
# This is a wrapper around the json module's dumps function that uses our custom JSONEncoder class
|
||||||
|
def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||||
|
allow_nan=True, indent=None, separators=None,
|
||||||
|
default=None, sort_keys=False, **kw) -> str:
|
||||||
|
"""Serialize ``obj`` to a JSON formatted ``str``.
|
||||||
|
|
||||||
|
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
||||||
|
(``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped
|
||||||
|
instead of raising a ``TypeError``.
|
||||||
|
|
||||||
|
If ``ensure_ascii`` is false, then the return value can contain non-ASCII
|
||||||
|
characters if they appear in strings contained in ``obj``. Otherwise, all
|
||||||
|
such characters are escaped in JSON strings.
|
||||||
|
|
||||||
|
If ``check_circular`` is false, then the circular reference check
|
||||||
|
for container types will be skipped and a circular reference will
|
||||||
|
result in an ``RecursionError`` (or worse).
|
||||||
|
|
||||||
|
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||||
|
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
|
||||||
|
strict compliance of the JSON specification, instead of using the
|
||||||
|
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||||
|
|
||||||
|
If ``indent`` is a non-negative integer, then JSON array elements and
|
||||||
|
object members will be pretty-printed with that indent level. An indent
|
||||||
|
level of 0 will only insert newlines. ``None`` is the most compact
|
||||||
|
representation.
|
||||||
|
|
||||||
|
If specified, ``separators`` should be an ``(item_separator, key_separator)``
|
||||||
|
tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
|
||||||
|
``(',', ': ')`` otherwise. To get the most compact JSON representation,
|
||||||
|
you should specify ``(',', ':')`` to eliminate whitespace.
|
||||||
|
|
||||||
|
``default(obj)`` is a function that should return a serializable version
|
||||||
|
of obj or raise TypeError. The default simply raises TypeError.
|
||||||
|
|
||||||
|
If *sort_keys* is true (default: ``False``), then the output of
|
||||||
|
dictionaries will be sorted by key.
|
||||||
|
"""
|
||||||
|
return json.dumps(
|
||||||
|
obj,
|
||||||
|
cls=JSONEncoder,
|
||||||
|
skipkeys=skipkeys,
|
||||||
|
ensure_ascii=ensure_ascii,
|
||||||
|
check_circular=check_circular,
|
||||||
|
allow_nan=allow_nan,
|
||||||
|
indent=indent,
|
||||||
|
separators=separators,
|
||||||
|
default=default,
|
||||||
|
sort_keys=sort_keys,
|
||||||
|
**kw
|
||||||
|
)
|
||||||
|
|
||||||
|
# This is a wrapper around the json module's dump function that uses our custom JSONEncoder class
|
||||||
|
def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True,
|
||||||
|
allow_nan=True, indent=None, separators=None,
|
||||||
|
default=None, sort_keys=False, **kw) -> str:
|
||||||
|
"""Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
|
||||||
|
``.write()``-supporting file-like object).
|
||||||
|
|
||||||
|
If ``skipkeys`` is true then ``dict`` keys that are not basic types
|
||||||
|
(``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped
|
||||||
|
instead of raising a ``TypeError``.
|
||||||
|
|
||||||
|
If ``ensure_ascii`` is false, then the strings written to ``fp`` can
|
||||||
|
contain non-ASCII characters if they appear in strings contained in
|
||||||
|
``obj``. Otherwise, all such characters are escaped in JSON strings.
|
||||||
|
|
||||||
|
If ``check_circular`` is false, then the circular reference check
|
||||||
|
for container types will be skipped and a circular reference will
|
||||||
|
result in an ``RecursionError`` (or worse).
|
||||||
|
|
||||||
|
If ``allow_nan`` is false, then it will be a ``ValueError`` to
|
||||||
|
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
|
||||||
|
in strict compliance of the JSON specification, instead of using the
|
||||||
|
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
|
||||||
|
|
||||||
|
If ``indent`` is a non-negative integer, then JSON array elements and
|
||||||
|
object members will be pretty-printed with that indent level. An indent
|
||||||
|
level of 0 will only insert newlines. ``None`` is the most compact
|
||||||
|
representation.
|
||||||
|
|
||||||
|
If specified, ``separators`` should be an ``(item_separator, key_separator)``
|
||||||
|
tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
|
||||||
|
``(',', ': ')`` otherwise. To get the most compact JSON representation,
|
||||||
|
you should specify ``(',', ':')`` to eliminate whitespace.
|
||||||
|
|
||||||
|
``default(obj)`` is a function that should return a serializable version
|
||||||
|
of obj or raise TypeError. The default simply raises TypeError.
|
||||||
|
|
||||||
|
If *sort_keys* is true (default: ``False``), then the output of
|
||||||
|
dictionaries will be sorted by key.
|
||||||
|
"""
|
||||||
|
return json.dump(
|
||||||
|
obj,
|
||||||
|
fp,
|
||||||
|
cls=JSONEncoder,
|
||||||
|
skipkeys=skipkeys,
|
||||||
|
ensure_ascii=ensure_ascii,
|
||||||
|
check_circular=check_circular,
|
||||||
|
allow_nan=allow_nan,
|
||||||
|
indent=indent,
|
||||||
|
separators=separators,
|
||||||
|
default=default,
|
||||||
|
sort_keys=sort_keys,
|
||||||
|
**kw
|
||||||
|
)
|
|
@ -1,23 +1,21 @@
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
import json
|
from datetime import datetime, timedelta
|
||||||
from datetime import datetime
|
from typing import Optional, Tuple, Union
|
||||||
from datetime import timedelta as td
|
|
||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta as rd
|
from dateutil.relativedelta import relativedelta as rd
|
||||||
from discord import File, Guild, Interaction, Member, SelectOption, User
|
from discord import File, Guild, Interaction, Member, SelectOption, TextChannel, User
|
||||||
from discord.errors import Forbidden, NotFound
|
from discord.errors import Forbidden
|
||||||
from redbot.core import commands, data_manager
|
from redbot.core import commands, data_manager
|
||||||
from redbot.core.utils.chat_formatting import error
|
from redbot.core.utils.chat_formatting import error
|
||||||
|
|
||||||
from .config import config
|
from ..utilities.config import config
|
||||||
|
|
||||||
|
|
||||||
def check_permissions(
|
def check_permissions(
|
||||||
user: User,
|
user: User,
|
||||||
permissions: list,
|
permissions: Tuple[str],
|
||||||
ctx: Union[commands.Context, Interaction] = None,
|
ctx: Union[commands.Context, Interaction] | None = None,
|
||||||
guild: Guild = None,
|
guild: Guild | None = None,
|
||||||
) -> Union[bool, str]:
|
) -> Union[bool, str]:
|
||||||
"""Checks if a user has a specific permission (or a list of permissions) in a channel."""
|
"""Checks if a user has a specific permission (or a list of permissions) in a channel."""
|
||||||
if ctx:
|
if ctx:
|
||||||
|
@ -42,9 +40,10 @@ def check_permissions(
|
||||||
|
|
||||||
|
|
||||||
async def check_moddable(
|
async def check_moddable(
|
||||||
target: Union[User, Member], interaction: Interaction, permissions: list
|
target: Union[User, Member, TextChannel], interaction: Interaction, permissions: Tuple[str]
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Checks if a moderator can moderate a target."""
|
"""Checks if a moderator can moderate a target."""
|
||||||
|
is_channel = isinstance(target, TextChannel)
|
||||||
if check_permissions(interaction.client.user, permissions, guild=interaction.guild):
|
if check_permissions(interaction.client.user, permissions, guild=interaction.guild):
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
error(
|
error(
|
||||||
|
@ -70,7 +69,7 @@ async def check_moddable(
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if target.bot:
|
if not is_channel and target.bot:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
content="You cannot moderate bots!", ephemeral=True
|
content="You cannot moderate bots!", ephemeral=True
|
||||||
)
|
)
|
||||||
|
@ -111,122 +110,31 @@ async def check_moddable(
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def get_next_case_number(guild_id: str, cursor=None) -> int:
|
|
||||||
"""This function returns the next case number from the MySQL table for a specific guild."""
|
|
||||||
from .database import connect
|
|
||||||
|
|
||||||
if not cursor:
|
|
||||||
database = connect()
|
|
||||||
cursor = database.cursor()
|
|
||||||
cursor.execute(
|
|
||||||
f"SELECT moderation_id FROM `moderation_{guild_id}` ORDER BY moderation_id DESC LIMIT 1"
|
|
||||||
)
|
|
||||||
result = cursor.fetchone()
|
|
||||||
return (result[0] + 1) if result else 1
|
|
||||||
|
|
||||||
|
|
||||||
def generate_dict(result) -> dict:
|
|
||||||
case = {
|
|
||||||
"moderation_id": result[0],
|
|
||||||
"timestamp": result[1],
|
|
||||||
"moderation_type": result[2],
|
|
||||||
"target_type": result[3],
|
|
||||||
"target_id": result[4],
|
|
||||||
"moderator_id": result[5],
|
|
||||||
"role_id": result[6],
|
|
||||||
"duration": result[7],
|
|
||||||
"end_timestamp": result[8],
|
|
||||||
"reason": result[9],
|
|
||||||
"resolved": result[10],
|
|
||||||
"resolved_by": result[11],
|
|
||||||
"resolve_reason": result[12],
|
|
||||||
"expired": result[13],
|
|
||||||
"changes": json.loads(result[14]),
|
|
||||||
"metadata": json.loads(result[15]),
|
|
||||||
}
|
|
||||||
return case
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_user_dict(client: commands.Bot, user_id: str) -> dict:
|
|
||||||
"""This function returns a dictionary containing either user information or a standard deleted user template."""
|
|
||||||
if user_id == "?":
|
|
||||||
user_dict = {"id": "?", "name": "Unknown User", "discriminator": "0"}
|
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
user = client.get_user(int(user_id))
|
|
||||||
if user is None:
|
|
||||||
user = await client.fetch_user(int(user_id))
|
|
||||||
|
|
||||||
user_dict = {
|
|
||||||
"id": user.id,
|
|
||||||
"name": user.name,
|
|
||||||
"discriminator": user.discriminator,
|
|
||||||
}
|
|
||||||
|
|
||||||
except NotFound:
|
|
||||||
user_dict = {
|
|
||||||
"id": user_id,
|
|
||||||
"name": "Deleted User",
|
|
||||||
"discriminator": "0",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return user_dict
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_channel_dict(guild: Guild, channel_id: int) -> dict:
|
|
||||||
"""This function returns a dictionary containing either channel information or a standard deleted channel template."""
|
|
||||||
try:
|
|
||||||
channel = guild.get_channel(int(channel_id))
|
|
||||||
if not channel:
|
|
||||||
channel = await guild.fetch_channel(channel_id)
|
|
||||||
|
|
||||||
channel_dict = {
|
|
||||||
"id": channel.id,
|
|
||||||
"name": channel.name,
|
|
||||||
"mention": channel.mention,
|
|
||||||
}
|
|
||||||
|
|
||||||
except NotFound:
|
|
||||||
channel_dict = {"id": channel_id, "name": "Deleted Channel", "mention": None}
|
|
||||||
|
|
||||||
return channel_dict
|
|
||||||
|
|
||||||
|
|
||||||
async def fetch_role_dict(guild: Guild, role_id: int) -> dict:
|
|
||||||
"""This function returns a dictionary containing either role information or a standard deleted role template."""
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if not role:
|
|
||||||
role_dict = {"id": role_id, "name": "Deleted Role"}
|
|
||||||
|
|
||||||
role_dict = {"id": role.id, "name": role.name}
|
|
||||||
|
|
||||||
return role_dict
|
|
||||||
|
|
||||||
|
|
||||||
async def log(interaction: Interaction, moderation_id: int, resolved: bool = False) -> None:
|
async def log(interaction: Interaction, moderation_id: int, resolved: bool = False) -> None:
|
||||||
"""This function sends a message to the guild's configured logging channel when an infraction takes place."""
|
"""This function sends a message to the guild's configured logging channel when an infraction takes place."""
|
||||||
from .database import fetch_case
|
from ..models.moderation import Moderation
|
||||||
from .factory import log_factory
|
from .factory import log_factory
|
||||||
|
|
||||||
logging_channel_id = await config.guild(interaction.guild).log_channel()
|
logging_channel_id = await config.guild(interaction.guild).log_channel()
|
||||||
if logging_channel_id != " ":
|
if logging_channel_id != " ":
|
||||||
logging_channel = interaction.guild.get_channel(logging_channel_id)
|
logging_channel = interaction.guild.get_channel(logging_channel_id)
|
||||||
|
|
||||||
case = await fetch_case(moderation_id, interaction.guild.id)
|
try:
|
||||||
if case:
|
moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id)
|
||||||
embed = await log_factory(
|
embed = await log_factory(
|
||||||
interaction=interaction, case_dict=case, resolved=resolved
|
interaction=interaction, moderation=moderation, resolved=resolved
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await logging_channel.send(embed=embed)
|
await logging_channel.send(embed=embed)
|
||||||
except Forbidden:
|
except Forbidden:
|
||||||
return
|
return
|
||||||
|
except ValueError:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
async def send_evidenceformat(interaction: Interaction, case_dict: dict) -> None:
|
async def send_evidenceformat(interaction: Interaction, moderation_id: int) -> None:
|
||||||
"""This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel."""
|
"""This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel."""
|
||||||
|
from ..models.moderation import Moderation
|
||||||
from .factory import evidenceformat_factory
|
from .factory import evidenceformat_factory
|
||||||
|
|
||||||
send_evidence_bool = (
|
send_evidence_bool = (
|
||||||
|
@ -237,26 +145,20 @@ async def send_evidenceformat(interaction: Interaction, case_dict: dict) -> None
|
||||||
if send_evidence_bool is False:
|
if send_evidence_bool is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
content = await evidenceformat_factory(interaction=interaction, case_dict=case_dict)
|
moderation = Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id)
|
||||||
|
content = await evidenceformat_factory(moderation=moderation)
|
||||||
await interaction.followup.send(content=content, ephemeral=True)
|
await interaction.followup.send(content=content, ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
def convert_timedelta_to_str(timedelta: td) -> str:
|
|
||||||
"""This function converts a timedelta object to a string."""
|
|
||||||
total_seconds = int(timedelta.total_seconds())
|
|
||||||
hours = total_seconds // 3600
|
|
||||||
minutes = (total_seconds % 3600) // 60
|
|
||||||
seconds = total_seconds % 60
|
|
||||||
return f"{hours}:{minutes}:{seconds}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_bool_emoji(value: Optional[bool]) -> str:
|
def get_bool_emoji(value: Optional[bool]) -> str:
|
||||||
"""Returns a unicode emoji based on a boolean value."""
|
"""Returns a unicode emoji based on a boolean value."""
|
||||||
if value is True:
|
match value:
|
||||||
return "\N{WHITE HEAVY CHECK MARK}"
|
case True:
|
||||||
if value is False:
|
return "\N{WHITE HEAVY CHECK MARK}"
|
||||||
return "\N{NO ENTRY SIGN}"
|
case False:
|
||||||
return "\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16}"
|
return "\N{NO ENTRY SIGN}"
|
||||||
|
case _:
|
||||||
|
return "\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16}"
|
||||||
|
|
||||||
|
|
||||||
def get_pagesize_str(value: Union[int, None]) -> str:
|
def get_pagesize_str(value: Union[int, None]) -> str:
|
||||||
|
@ -286,12 +188,24 @@ def create_pagesize_options() -> list[SelectOption]:
|
||||||
)
|
)
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def timedelta_from_relativedelta(relativedelta: rd) -> td:
|
def timedelta_from_relativedelta(relativedelta: rd) -> timedelta:
|
||||||
"""Converts a relativedelta object to a timedelta object."""
|
"""Converts a relativedelta object to a timedelta object."""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
then = now - relativedelta
|
then = now - relativedelta
|
||||||
return now - then
|
return now - then
|
||||||
|
|
||||||
|
def timedelta_from_string(string: str) -> timedelta:
|
||||||
|
"""Converts a string to a timedelta object."""
|
||||||
|
hours, minutes, seconds = map(int, string.split(":"))
|
||||||
|
return timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
||||||
|
|
||||||
|
def timedelta_to_string(td: timedelta) -> str:
|
||||||
|
"""Converts a timedelta object to a string."""
|
||||||
|
days = td.days * 24
|
||||||
|
hours, remainder = divmod(td.seconds, 3600)
|
||||||
|
minutes, seconds = divmod(remainder, 60)
|
||||||
|
return f"{days + hours}:{minutes:02}:{seconds:02}"
|
||||||
|
|
||||||
def get_footer_image(coginstance: commands.Cog) -> File:
|
def get_footer_image(coginstance: commands.Cog) -> File:
|
||||||
"""Returns the footer image for the embeds."""
|
"""Returns the footer image for the embeds."""
|
||||||
image_path = data_manager.bundled_data_path(coginstance) / "arrow.png"
|
image_path = data_manager.bundled_data_path(coginstance) / "arrow.png"
|
||||||
|
|
541
poetry.lock
generated
541
poetry.lock
generated
|
@ -123,6 +123,17 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
frozenlist = ">=1.1.0"
|
frozenlist = ">=1.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "Reusable constraint types to use with typing.Annotated"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||||
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "apsw"
|
name = "apsw"
|
||||||
version = "3.45.2.0"
|
version = "3.45.2.0"
|
||||||
|
@ -184,15 +195,29 @@ files = [
|
||||||
{file = "apsw-3.45.2.0.tar.gz", hash = "sha256:4fe81f5e390969d08d3048f357a68b347316b8f09455ff4657d94c56acfa255c"},
|
{file = "apsw-3.45.2.0.tar.gz", hash = "sha256:4fe81f5e390969d08d3048f357a68b347316b8f09455ff4657d94c56acfa255c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "argcomplete"
|
||||||
|
version = "3.3.0"
|
||||||
|
description = "Bash tab completion for argparse"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "argcomplete-3.3.0-py3-none-any.whl", hash = "sha256:c168c3723482c031df3c207d4ba8fa702717ccb9fc0bfe4117166c1f537b4a54"},
|
||||||
|
{file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["coverage", "mypy", "pexpect", "ruff", "wheel"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "astroid"
|
name = "astroid"
|
||||||
version = "3.1.0"
|
version = "3.2.2"
|
||||||
description = "An abstract syntax tree for Python with inference support."
|
description = "An abstract syntax tree for Python with inference support."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"},
|
{file = "astroid-3.2.2-py3-none-any.whl", hash = "sha256:e8a0083b4bb28fcffb6207a3bfc9e5d0a68be951dd7e336d5dcf639c682388c0"},
|
||||||
{file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"},
|
{file = "astroid-3.2.2.tar.gz", hash = "sha256:8ead48e31b92b2e217b6c9733a21afafe479d52d6e164dd25fb1a770c7c3cf94"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -343,13 +368,13 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cairocffi"
|
name = "cairocffi"
|
||||||
version = "1.6.1"
|
version = "1.7.0"
|
||||||
description = "cffi-based cairo bindings for Python"
|
description = "cffi-based cairo bindings for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "cairocffi-1.6.1-py3-none-any.whl", hash = "sha256:aa78ee52b9069d7475eeac457389b6275aa92111895d78fbaa2202a52dac112e"},
|
{file = "cairocffi-1.7.0-py3-none-any.whl", hash = "sha256:1f29a8d41dbda4090c0aa33bcdea64f3b493e95f74a43ea107c4a8a7b7f632ef"},
|
||||||
{file = "cairocffi-1.6.1.tar.gz", hash = "sha256:78e6bbe47357640c453d0be929fa49cd05cce2e1286f3d2a1ca9cbda7efdb8b7"},
|
{file = "cairocffi-1.7.0.tar.gz", hash = "sha256:7761863603894305f3160eca68452f373433ca8745ab7dd445bd2c6ce50dcab7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -357,7 +382,7 @@ cffi = ">=1.1.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
doc = ["sphinx", "sphinx_rtd_theme"]
|
doc = ["sphinx", "sphinx_rtd_theme"]
|
||||||
test = ["flake8", "isort", "numpy", "pikepdf", "pytest"]
|
test = ["numpy", "pikepdf", "pytest", "ruff"]
|
||||||
xcb = ["xcffib (>=1.4.0)"]
|
xcb = ["xcffib (>=1.4.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -384,13 +409,13 @@ test = ["flake8", "isort", "pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2024.2.2"
|
version = "2024.6.2"
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
files = [
|
files = [
|
||||||
{file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
|
{file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"},
|
||||||
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
|
{file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -801,30 +826,31 @@ smmap = ">=3.0.1,<6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gitpython"
|
name = "gitpython"
|
||||||
version = "3.1.42"
|
version = "3.1.43"
|
||||||
description = "GitPython is a Python library used to interact with Git repositories"
|
description = "GitPython is a Python library used to interact with Git repositories"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "GitPython-3.1.42-py3-none-any.whl", hash = "sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd"},
|
{file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"},
|
||||||
{file = "GitPython-3.1.42.tar.gz", hash = "sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb"},
|
{file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
gitdb = ">=4.0.1,<5"
|
gitdb = ">=4.0.1,<5"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar"]
|
doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"]
|
||||||
|
test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "griffe"
|
name = "griffe"
|
||||||
version = "0.42.1"
|
version = "0.45.2"
|
||||||
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "griffe-0.42.1-py3-none-any.whl", hash = "sha256:7e805e35617601355edcac0d3511cedc1ed0cb1f7645e2d336ae4b05bbae7b3b"},
|
{file = "griffe-0.45.2-py3-none-any.whl", hash = "sha256:297ec8530d0c68e5b98ff86fb588ebc3aa3559bb5dc21f3caea8d9542a350133"},
|
||||||
{file = "griffe-0.42.1.tar.gz", hash = "sha256:57046131384043ed078692b85d86b76568a686266cc036b9b56b704466f803ce"},
|
{file = "griffe-0.45.2.tar.gz", hash = "sha256:83ce7dcaafd8cb7f43cbf1a455155015a1eb624b1ffd93249e5e1c4a22b2fdb2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -857,13 +883,13 @@ colors = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.3"
|
version = "3.1.4"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
|
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||||
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
|
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1107,13 +1133,13 @@ pytz = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "9.5.15"
|
version = "9.5.18"
|
||||||
description = "Documentation that simply works"
|
description = "Documentation that simply works"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocs_material-9.5.15-py3-none-any.whl", hash = "sha256:e5c96dec3d19491de49ca643fc1dbb92b278e43cdb816c775bc47db77d9b62fb"},
|
{file = "mkdocs_material-9.5.18-py3-none-any.whl", hash = "sha256:1e0e27fc9fe239f9064318acf548771a4629d5fd5dfd45444fd80a953fe21eb4"},
|
||||||
{file = "mkdocs_material-9.5.15.tar.gz", hash = "sha256:39f03cca45e82bf54eb7456b5a18bd252eabfdd67f237a229471484a0a4d4635"},
|
{file = "mkdocs_material-9.5.18.tar.gz", hash = "sha256:a43f470947053fa2405c33995f282d24992c752a50114f23f30da9d8d0c57e62"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1529,6 +1555,24 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
|
||||||
typing = ["typing-extensions"]
|
typing = ["typing-extensions"]
|
||||||
xmp = ["defusedxml"]
|
xmp = ["defusedxml"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pipx"
|
||||||
|
version = "1.6.0"
|
||||||
|
description = "Install and Run Python Applications in Isolated Environments"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pipx-1.6.0-py3-none-any.whl", hash = "sha256:760889dc3aeed7bf4024973bf22ca0c2a891003f52389159ab5cb0c57d9ebff4"},
|
||||||
|
{file = "pipx-1.6.0.tar.gz", hash = "sha256:840610e00103e3d49ae24b6b51804b60988851a5dd65468adb71e5a97e2699b2"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
argcomplete = ">=1.9.4"
|
||||||
|
colorama = {version = ">=0.4.4", markers = "sys_platform == \"win32\""}
|
||||||
|
packaging = ">=20"
|
||||||
|
platformdirs = ">=2.1"
|
||||||
|
userpath = ">=1.6,<1.9 || >1.9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "4.2.0"
|
version = "4.2.0"
|
||||||
|
@ -1588,15 +1632,125 @@ requests = ">=2.21.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.21"
|
version = "2.22"
|
||||||
description = "C parser in Python"
|
description = "C parser in Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.7.3"
|
||||||
|
description = "Data validation using Python type hints"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic-2.7.3-py3-none-any.whl", hash = "sha256:ea91b002777bf643bb20dd717c028ec43216b24a6001a280f83877fd2655d0b4"},
|
||||||
|
{file = "pydantic-2.7.3.tar.gz", hash = "sha256:c46c76a40bb1296728d7a8b99aa73dd70a48c3510111ff290034f860c99c419e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
annotated-types = ">=0.4.0"
|
||||||
|
pydantic-core = "2.18.4"
|
||||||
|
typing-extensions = ">=4.6.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
email = ["email-validator (>=2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.18.4"
|
||||||
|
description = "Core functionality for Pydantic validation and serialization"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f76d0ad001edd426b92233d45c746fd08f467d56100fd8f30e9ace4b005266e4"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:59ff3e89f4eaf14050c8022011862df275b552caef8082e37b542b066ce1ff26"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a55b5b16c839df1070bc113c1f7f94a0af4433fcfa1b41799ce7606e5c79ce0a"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d0dcc59664fcb8974b356fe0a18a672d6d7cf9f54746c05f43275fc48636851"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8951eee36c57cd128f779e641e21eb40bc5073eb28b2d23f33eb0ef14ffb3f5d"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4701b19f7e3a06ea655513f7938de6f108123bf7c86bbebb1196eb9bd35cf724"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00a3f196329e08e43d99b79b286d60ce46bed10f2280d25a1718399457e06be"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:97736815b9cc893b2b7f663628e63f436018b75f44854c8027040e05230eeddb"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6891a2ae0e8692679c07728819b6e2b822fb30ca7445f67bbf6509b25a96332c"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bc4ff9805858bd54d1a20efff925ccd89c9d2e7cf4986144b30802bf78091c3e"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-none-win32.whl", hash = "sha256:1b4de2e51bbcb61fdebd0ab86ef28062704f62c82bbf4addc4e37fa4b00b7cbc"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp310-none-win_amd64.whl", hash = "sha256:6a750aec7bf431517a9fd78cb93c97b9b0c496090fee84a47a0d23668976b4b0"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:942ba11e7dfb66dc70f9ae66b33452f51ac7bb90676da39a7345e99ffb55402d"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2ebef0e0b4454320274f5e83a41844c63438fdc874ea40a8b5b4ecb7693f1c4"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a642295cd0c8df1b86fc3dced1d067874c353a188dc8e0f744626d49e9aa51c4"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f09baa656c904807e832cf9cce799c6460c450c4ad80803517032da0cd062e2"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98906207f29bc2c459ff64fa007afd10a8c8ac080f7e4d5beff4c97086a3dabd"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:19894b95aacfa98e7cb093cd7881a0c76f55731efad31073db4521e2b6ff5b7d"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fbbdc827fe5e42e4d196c746b890b3d72876bdbf160b0eafe9f0334525119c8"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f85d05aa0918283cf29a30b547b4df2fbb56b45b135f9e35b6807cb28bc47951"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e85637bc8fe81ddb73fda9e56bab24560bdddfa98aa64f87aaa4e4b6730c23d2"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2f5966897e5461f818e136b8451d0551a2e77259eb0f73a837027b47dc95dab9"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-none-win32.whl", hash = "sha256:44c7486a4228413c317952e9d89598bcdfb06399735e49e0f8df643e1ccd0558"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-none-win_amd64.whl", hash = "sha256:8a7164fe2005d03c64fd3b85649891cd4953a8de53107940bf272500ba8a788b"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp311-none-win_arm64.whl", hash = "sha256:4e99bc050fe65c450344421017f98298a97cefc18c53bb2f7b3531eb39bc7805"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6f5c4d41b2771c730ea1c34e458e781b18cc668d194958e0112455fff4e402b2"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2fdf2156aa3d017fddf8aea5adfba9f777db1d6022d392b682d2a8329e087cef"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4748321b5078216070b151d5271ef3e7cc905ab170bbfd27d5c83ee3ec436695"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847a35c4d58721c5dc3dba599878ebbdfd96784f3fb8bb2c356e123bdcd73f34"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c40d4eaad41f78e3bbda31b89edc46a3f3dc6e171bf0ecf097ff7a0ffff7cb1"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:21a5e440dbe315ab9825fcd459b8814bb92b27c974cbc23c3e8baa2b76890077"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01dd777215e2aa86dfd664daed5957704b769e726626393438f9c87690ce78c3"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4b06beb3b3f1479d32befd1f3079cc47b34fa2da62457cdf6c963393340b56e9"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:564d7922e4b13a16b98772441879fcdcbe82ff50daa622d681dd682175ea918c"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0eb2a4f660fcd8e2b1c90ad566db2b98d7f3f4717c64fe0a83e0adb39766d5b8"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-none-win32.whl", hash = "sha256:8b8bab4c97248095ae0c4455b5a1cd1cdd96e4e4769306ab19dda135ea4cdb07"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-none-win_amd64.whl", hash = "sha256:14601cdb733d741b8958224030e2bfe21a4a881fb3dd6fbb21f071cabd48fa0a"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp312-none-win_arm64.whl", hash = "sha256:c1322d7dd74713dcc157a2b7898a564ab091ca6c58302d5c7b4c07296e3fd00f"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:823be1deb01793da05ecb0484d6c9e20baebb39bd42b5d72636ae9cf8350dbd2"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebef0dd9bf9b812bf75bda96743f2a6c5734a02092ae7f721c048d156d5fabae"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae1d6df168efb88d7d522664693607b80b4080be6750c913eefb77e34c12c71a"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f9899c94762343f2cc2fc64c13e7cae4c3cc65cdfc87dd810a31654c9b7358cc"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99457f184ad90235cfe8461c4d70ab7dd2680e28821c29eca00252ba90308c78"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18f469a3d2a2fdafe99296a87e8a4c37748b5080a26b806a707f25a902c040a8"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cdf28938ac6b8b49ae5e92f2735056a7ba99c9b110a474473fd71185c1af5d"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:938cb21650855054dc54dfd9120a851c974f95450f00683399006aa6e8abb057"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:44cd83ab6a51da80fb5adbd9560e26018e2ac7826f9626bc06ca3dc074cd198b"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:972658f4a72d02b8abfa2581d92d59f59897d2e9f7e708fdabe922f9087773af"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-none-win32.whl", hash = "sha256:1d886dc848e60cb7666f771e406acae54ab279b9f1e4143babc9c2258213daa2"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp38-none-win_amd64.whl", hash = "sha256:bb4462bd43c2460774914b8525f79b00f8f407c945d50881568f294c1d9b4443"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:44a688331d4a4e2129140a8118479443bd6f1905231138971372fcde37e43528"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a2fdd81edd64342c85ac7cf2753ccae0b79bf2dfa063785503cb85a7d3593223"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86110d7e1907ab36691f80b33eb2da87d780f4739ae773e5fc83fb272f88825f"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46387e38bd641b3ee5ce247563b60c5ca098da9c56c75c157a05eaa0933ed154"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:123c3cec203e3f5ac7b000bd82235f1a3eced8665b63d18be751f115588fea30"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1803ac5c32ec324c5261c7209e8f8ce88e83254c4e1aebdc8b0a39f9ddb443"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53db086f9f6ab2b4061958d9c276d1dbe3690e8dd727d6abf2321d6cce37fa94"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abc267fa9837245cc28ea6929f19fa335f3dc330a35d2e45509b6566dc18be23"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0d829524aaefdebccb869eed855e2d04c21d2d7479b6cada7ace5448416597b"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:509daade3b8649f80d4e5ff21aa5673e4ebe58590b25fe42fac5f0f52c6f034a"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-none-win32.whl", hash = "sha256:ca26a1e73c48cfc54c4a76ff78df3727b9d9f4ccc8dbee4ae3f73306a591676d"},
|
||||||
|
{file = "pydantic_core-2.18.4-cp39-none-win_amd64.whl", hash = "sha256:c67598100338d5d985db1b3d21f3619ef392e185e71b8d52bceacc4a7771ea7e"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:574d92eac874f7f4db0ca653514d823a0d22e2354359d0759e3f6a406db5d55d"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1f4d26ceb5eb9eed4af91bebeae4b06c3fb28966ca3a8fb765208cf6b51102ab"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77450e6d20016ec41f43ca4a6c63e9fdde03f0ae3fe90e7c27bdbeaece8b1ed4"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d323a01da91851a4f17bf592faf46149c9169d68430b3146dcba2bb5e5719abc"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43d447dd2ae072a0065389092a231283f62d960030ecd27565672bd40746c507"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:578e24f761f3b425834f297b9935e1ce2e30f51400964ce4801002435a1b41ef"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:81b5efb2f126454586d0f40c4d834010979cb80785173d1586df845a632e4e6d"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab86ce7c8f9bea87b9d12c7f0af71102acbf5ecbc66c17796cff45dae54ef9a5"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:90afc12421df2b1b4dcc975f814e21bc1754640d502a2fbcc6d41e77af5ec312"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:51991a89639a912c17bef4b45c87bd83593aee0437d8102556af4885811d59f5"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:293afe532740370aba8c060882f7d26cfd00c94cae32fd2e212a3a6e3b7bc15e"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48ece5bde2e768197a2d0f6e925f9d7e3e826f0ad2271120f8144a9db18d5c8"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eae237477a873ab46e8dd748e515c72c0c804fb380fbe6c85533c7de51f23a8f"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:834b5230b5dfc0c1ec37b2fda433b271cbbc0e507560b5d1588e2cc1148cf1ce"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e858ac0a25074ba4bce653f9b5d0a85b7456eaddadc0ce82d3878c22489fa4ee"},
|
||||||
|
{file = "pydantic_core-2.18.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2fd41f6eff4c20778d717af1cc50eca52f5afe7805ee530a4fbd0bae284f16e9"},
|
||||||
|
{file = "pydantic_core-2.18.4.tar.gz", hash = "sha256:ec3beeada09ff865c344ff3bc2f427f5e6c26401cc6113d77e372c3fdac73864"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.17.2"
|
version = "2.17.2"
|
||||||
|
@ -1614,17 +1768,17 @@ windows-terminal = ["colorama (>=0.4.6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pylint"
|
name = "pylint"
|
||||||
version = "3.1.0"
|
version = "3.2.2"
|
||||||
description = "python code static checker"
|
description = "python code static checker"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"},
|
{file = "pylint-3.2.2-py3-none-any.whl", hash = "sha256:3f8788ab20bb8383e06dd2233e50f8e08949cfd9574804564803441a4946eab4"},
|
||||||
{file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"},
|
{file = "pylint-3.2.2.tar.gz", hash = "sha256:d068ca1dfd735fb92a07d33cb8f288adc0f6bc1287a139ca2425366f7cbe38f8"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
astroid = ">=3.1.0,<=3.2.0-dev0"
|
astroid = ">=3.2.2,<=3.3.0-dev0"
|
||||||
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
|
||||||
dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""}
|
dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""}
|
||||||
isort = ">=4.2.5,<5.13.0 || >5.13.0,<6"
|
isort = ">=4.2.5,<5.13.0 || >5.13.0,<6"
|
||||||
|
@ -1638,17 +1792,17 @@ testutils = ["gitpython (>3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pymdown-extensions"
|
name = "pymdown-extensions"
|
||||||
version = "10.7.1"
|
version = "10.8.1"
|
||||||
description = "Extension pack for Python Markdown."
|
description = "Extension pack for Python Markdown."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pymdown_extensions-10.7.1-py3-none-any.whl", hash = "sha256:f5cc7000d7ff0d1ce9395d216017fa4df3dde800afb1fb72d1c7d3fd35e710f4"},
|
{file = "pymdown_extensions-10.8.1-py3-none-any.whl", hash = "sha256:f938326115884f48c6059c67377c46cf631c733ef3629b6eed1349989d1b30cb"},
|
||||||
{file = "pymdown_extensions-10.7.1.tar.gz", hash = "sha256:c70e146bdd83c744ffc766b4671999796aba18842b268510a329f7f64700d584"},
|
{file = "pymdown_extensions-10.8.1.tar.gz", hash = "sha256:3ab1db5c9e21728dabf75192d71471f8e50f216627e9a1fa9535ecb0231b9940"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
markdown = ">=3.5"
|
markdown = ">=3.6"
|
||||||
pyyaml = "*"
|
pyyaml = "*"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
@ -1946,115 +2100,101 @@ test = ["pytest (>=7)", "pytest-asyncio (>=0.19)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "2023.12.25"
|
version = "2024.5.15"
|
||||||
description = "Alternative regular expression module, to replace re."
|
description = "Alternative regular expression module, to replace re."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0694219a1d54336fd0445ea382d49d36882415c0134ee1e8332afd1529f0baa5"},
|
{file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b014333bd0217ad3d54c143de9d4b9a3ca1c5a29a6d0d554952ea071cff0f1f8"},
|
{file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d865984b3f71f6d0af64d0d88f5733521698f6c16f445bb09ce746c92c97c586"},
|
{file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0eabac536b4cc7f57a5f3d095bfa557860ab912f25965e08fe1545e2ed8b4c"},
|
{file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c25a8ad70e716f96e13a637802813f65d8a6760ef48672aa3502f4c24ea8b400"},
|
{file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9b6d73353f777630626f403b0652055ebfe8ff142a44ec2cf18ae470395766e"},
|
{file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9cc99d6946d750eb75827cb53c4371b8b0fe89c733a94b1573c9dd16ea6c9e4"},
|
{file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d1f7bef20c721359d8675f7d9f8e414ec5003d8f642fdfd8087777ff7f94b5"},
|
{file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb3fe77aec8f1995611f966d0c656fdce398317f850d0e6e7aebdfe61f40e1cd"},
|
{file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7aa47c2e9ea33a4a2a05f40fcd3ea36d73853a2aae7b4feab6fc85f8bf2c9704"},
|
{file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:df26481f0c7a3f8739fecb3e81bc9da3fcfae34d6c094563b9d4670b047312e1"},
|
{file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c40281f7d70baf6e0db0c2f7472b31609f5bc2748fe7275ea65a0b4601d9b392"},
|
{file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:d94a1db462d5690ebf6ae86d11c5e420042b9898af5dcf278bd97d6bda065423"},
|
{file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ba1b30765a55acf15dce3f364e4928b80858fa8f979ad41f862358939bdd1f2f"},
|
{file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-win32.whl", hash = "sha256:150c39f5b964e4d7dba46a7962a088fbc91f06e606f023ce57bb347a3b2d4630"},
|
{file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"},
|
||||||
{file = "regex-2023.12.25-cp310-cp310-win_amd64.whl", hash = "sha256:09da66917262d9481c719599116c7dc0c321ffcec4b1f510c4f8a066f8768105"},
|
{file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b9d811f72210fa9306aeb88385b8f8bcef0dfbf3873410413c00aa94c56c2b6"},
|
{file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d902a43085a308cef32c0d3aea962524b725403fd9373dea18110904003bac97"},
|
{file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d166eafc19f4718df38887b2bbe1467a4f74a9830e8605089ea7a30dd4da8887"},
|
{file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7ad32824b7f02bb3c9f80306d405a1d9b7bb89362d68b3c5a9be53836caebdb"},
|
{file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:636ba0a77de609d6510235b7f0e77ec494d2657108f777e8765efc060094c98c"},
|
{file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fda75704357805eb953a3ee15a2b240694a9a514548cd49b3c5124b4e2ad01b"},
|
{file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f72cbae7f6b01591f90814250e636065850c5926751af02bb48da94dfced7baa"},
|
{file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2a0b1857f18b11e3b0e54ddfefc96af46b0896fb678c85f63fb8c37518b3e7"},
|
{file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7502534e55c7c36c0978c91ba6f61703faf7ce733715ca48f499d3dbbd7657e0"},
|
{file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e8c7e08bb566de4faaf11984af13f6bcf6a08f327b13631d41d62592681d24fe"},
|
{file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:283fc8eed679758de38fe493b7d7d84a198b558942b03f017b1f94dda8efae80"},
|
{file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f44dd4d68697559d007462b0a3a1d9acd61d97072b71f6d1968daef26bc744bd"},
|
{file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:67d3ccfc590e5e7197750fcb3a2915b416a53e2de847a728cfa60141054123d4"},
|
{file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-win32.whl", hash = "sha256:68191f80a9bad283432385961d9efe09d783bcd36ed35a60fb1ff3f1ec2efe87"},
|
{file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"},
|
||||||
{file = "regex-2023.12.25-cp311-cp311-win_amd64.whl", hash = "sha256:7d2af3f6b8419661a0c421584cfe8aaec1c0e435ce7e47ee2a97e344b98f794f"},
|
{file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8a0ccf52bb37d1a700375a6b395bff5dd15c50acb745f7db30415bae3c2b0715"},
|
{file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c3c4a78615b7762740531c27cf46e2f388d8d727d0c0c739e72048beb26c8a9d"},
|
{file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad83e7545b4ab69216cef4cc47e344d19622e28aabec61574b20257c65466d6a"},
|
{file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7a635871143661feccce3979e1727c4e094f2bdfd3ec4b90dfd4f16f571a87a"},
|
{file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d498eea3f581fbe1b34b59c697512a8baef88212f92e4c7830fcc1499f5b45a5"},
|
{file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43f7cd5754d02a56ae4ebb91b33461dc67be8e3e0153f593c509e21d219c5060"},
|
{file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51f4b32f793812714fd5307222a7f77e739b9bc566dc94a18126aba3b92b98a3"},
|
{file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba99d8077424501b9616b43a2d208095746fb1284fc5ba490139651f971d39d9"},
|
{file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4bfc2b16e3ba8850e0e262467275dd4d62f0d045e0e9eda2bc65078c0110a11f"},
|
{file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c2c19dae8a3eb0ea45a8448356ed561be843b13cbc34b840922ddf565498c1c"},
|
{file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:60080bb3d8617d96f0fb7e19796384cc2467447ef1c491694850ebd3670bc457"},
|
{file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b77e27b79448e34c2c51c09836033056a0547aa360c45eeeb67803da7b0eedaf"},
|
{file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:518440c991f514331f4850a63560321f833979d145d7d81186dbe2f19e27ae3d"},
|
{file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-win32.whl", hash = "sha256:e2610e9406d3b0073636a3a2e80db05a02f0c3169b5632022b4e81c0364bcda5"},
|
{file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"},
|
||||||
{file = "regex-2023.12.25-cp312-cp312-win_amd64.whl", hash = "sha256:cc37b9aeebab425f11f27e5e9e6cf580be7206c6582a64467a14dda211abc232"},
|
{file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:da695d75ac97cb1cd725adac136d25ca687da4536154cdc2815f576e4da11c69"},
|
{file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d126361607b33c4eb7b36debc173bf25d7805847346dd4d99b5499e1fef52bc7"},
|
{file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4719bb05094d7d8563a450cf8738d2e1061420f79cfcc1fa7f0a44744c4d8f73"},
|
{file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dd58946bce44b53b06d94aa95560d0b243eb2fe64227cba50017a8d8b3cd3e2"},
|
{file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22a86d9fff2009302c440b9d799ef2fe322416d2d58fc124b926aa89365ec482"},
|
{file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2aae8101919e8aa05ecfe6322b278f41ce2994c4a430303c4cd163fef746e04f"},
|
{file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e692296c4cc2873967771345a876bcfc1c547e8dd695c6b89342488b0ea55cd8"},
|
{file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:263ef5cc10979837f243950637fffb06e8daed7f1ac1e39d5910fd29929e489a"},
|
{file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d6f7e255e5fa94642a0724e35406e6cb7001c09d476ab5fce002f652b36d0c39"},
|
{file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:88ad44e220e22b63b0f8f81f007e8abbb92874d8ced66f32571ef8beb0643b2b"},
|
{file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:3a17d3ede18f9cedcbe23d2daa8a2cd6f59fe2bf082c567e43083bba3fb00347"},
|
{file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d15b274f9e15b1a0b7a45d2ac86d1f634d983ca40d6b886721626c47a400bf39"},
|
{file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-win32.whl", hash = "sha256:ed19b3a05ae0c97dd8f75a5d8f21f7723a8c33bbc555da6bbe1f96c470139d3c"},
|
{file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"},
|
||||||
{file = "regex-2023.12.25-cp37-cp37m-win_amd64.whl", hash = "sha256:a6d1047952c0b8104a1d371f88f4ab62e6275567d4458c1e26e9627ad489b445"},
|
{file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b43523d7bc2abd757119dbfb38af91b5735eea45537ec6ec3a5ec3f9562a1c53"},
|
{file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:efb2d82f33b2212898f1659fb1c2e9ac30493ac41e4d53123da374c3b5541e64"},
|
{file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7fca9205b59c1a3d5031f7e64ed627a1074730a51c2a80e97653e3e9fa0d415"},
|
{file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086dd15e9435b393ae06f96ab69ab2d333f5d65cbe65ca5a3ef0ec9564dfe770"},
|
{file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e81469f7d01efed9b53740aedd26085f20d49da65f9c1f41e822a33992cb1590"},
|
{file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34e4af5b27232f68042aa40a91c3b9bb4da0eeb31b7632e0091afc4310afe6cb"},
|
{file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9852b76ab558e45b20bf1893b59af64a28bd3820b0c2efc80e0a70a4a3ea51c1"},
|
{file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff100b203092af77d1a5a7abe085b3506b7eaaf9abf65b73b7d6905b6cb76988"},
|
{file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cc038b2d8b1470364b1888a98fd22d616fba2b6309c5b5f181ad4483e0017861"},
|
{file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:094ba386bb5c01e54e14434d4caabf6583334090865b23ef58e0424a6286d3dc"},
|
{file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5cd05d0f57846d8ba4b71d9c00f6f37d6b97d5e5ef8b3c3840426a475c8f70f4"},
|
{file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9aa1a67bbf0f957bbe096375887b2505f5d8ae16bf04488e8b0f334c36e31360"},
|
{file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:98a2636994f943b871786c9e82bfe7883ecdaba2ef5df54e1450fa9869d1f756"},
|
{file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37f8e93a81fc5e5bd8db7e10e62dc64261bcd88f8d7e6640aaebe9bc180d9ce2"},
|
{file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-win32.whl", hash = "sha256:d78bd484930c1da2b9679290a41cdb25cc127d783768a0369d6b449e72f88beb"},
|
{file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"},
|
||||||
{file = "regex-2023.12.25-cp38-cp38-win_amd64.whl", hash = "sha256:b521dcecebc5b978b447f0f69b5b7f3840eac454862270406a39837ffae4e697"},
|
{file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"},
|
||||||
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f7bc09bc9c29ebead055bcba136a67378f03d66bf359e87d0f7c759d6d4ffa31"},
|
{file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"},
|
||||||
{file = "regex-2023.12.25-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e14b73607d6231f3cc4622809c196b540a6a44e903bcfad940779c80dffa7be7"},
|
{file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"},
|
||||||
{file = "regex-2023.12.25-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9eda5f7a50141291beda3edd00abc2d4a5b16c29c92daf8d5bd76934150f3edc"},
|
{file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"},
|
||||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc6bb9aa69aacf0f6032c307da718f61a40cf970849e471254e0e91c56ffca95"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298dc6354d414bc921581be85695d18912bea163a8b23cac9a2562bbcd5088b1"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f4e475a80ecbd15896a976aa0b386c5525d0ed34d5c600b6d3ebac0a67c7ddf"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:531ac6cf22b53e0696f8e1d56ce2396311254eb806111ddd3922c9d937151dae"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22f3470f7524b6da61e2020672df2f3063676aff444db1daa283c2ea4ed259d6"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:89723d2112697feaa320c9d351e5f5e7b841e83f8b143dba8e2d2b5f04e10923"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0ecf44ddf9171cd7566ef1768047f6e66975788258b1c6c6ca78098b95cf9a3d"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:905466ad1702ed4acfd67a902af50b8db1feeb9781436372261808df7a2a7bca"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:4558410b7a5607a645e9804a3e9dd509af12fb72b9825b13791a37cd417d73a5"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7e316026cc1095f2a3e8cc012822c99f413b702eaa2ca5408a513609488cb62f"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3b1de218d5375cd6ac4b5493e0b9f3df2be331e86520f23382f216c137913d20"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-win32.whl", hash = "sha256:11a963f8e25ab5c61348d090bf1b07f1953929c13bd2309a0662e9ff680763c9"},
|
|
||||||
{file = "regex-2023.12.25-cp39-cp39-win_amd64.whl", hash = "sha256:e693e233ac92ba83a87024e1d32b5f9ab15ca55ddd916d878146f4e3406b5c91"},
|
|
||||||
{file = "regex-2023.12.25.tar.gz", hash = "sha256:29171aa128da69afdf4bde412d5bedc335f2ca8fcfe4489038577d05f16181e5"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.31.0"
|
version = "2.32.3"
|
||||||
description = "Python HTTP for Humans."
|
description = "Python HTTP for Humans."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
|
{file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
|
||||||
{file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
|
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -2087,28 +2227,28 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.3.4"
|
version = "0.3.7"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"},
|
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
|
||||||
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"},
|
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
|
||||||
{file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"},
|
{file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
|
||||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"},
|
{file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
|
||||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"},
|
{file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
|
||||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"},
|
{file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
|
||||||
{file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"},
|
{file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
|
||||||
{file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"},
|
{file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
|
||||||
{file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"},
|
{file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
|
||||||
{file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"},
|
{file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
|
||||||
{file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"},
|
{file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2160,13 +2300,13 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinycss2"
|
name = "tinycss2"
|
||||||
version = "1.2.1"
|
version = "1.3.0"
|
||||||
description = "A tiny CSS parser"
|
description = "A tiny CSS parser"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"},
|
{file = "tinycss2-1.3.0-py3-none-any.whl", hash = "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7"},
|
||||||
{file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"},
|
{file = "tinycss2-1.3.0.tar.gz", hash = "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -2174,17 +2314,17 @@ webencodings = ">=0.4"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
doc = ["sphinx", "sphinx_rtd_theme"]
|
doc = ["sphinx", "sphinx_rtd_theme"]
|
||||||
test = ["flake8", "isort", "pytest"]
|
test = ["pytest", "ruff"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomlkit"
|
name = "tomlkit"
|
||||||
version = "0.12.4"
|
version = "0.12.5"
|
||||||
description = "Style preserving TOML library"
|
description = "Style preserving TOML library"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"},
|
{file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"},
|
||||||
{file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"},
|
{file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2215,6 +2355,20 @@ h2 = ["h2 (>=4,<5)"]
|
||||||
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||||
zstd = ["zstandard (>=0.18.0)"]
|
zstd = ["zstandard (>=0.18.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "userpath"
|
||||||
|
version = "1.9.2"
|
||||||
|
description = "Cross-platform tool for adding locations to the user PATH"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "userpath-1.9.2-py3-none-any.whl", hash = "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d"},
|
||||||
|
{file = "userpath-1.9.2.tar.gz", hash = "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
click = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvloop"
|
name = "uvloop"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
|
@ -2261,40 +2415,43 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "watchdog"
|
name = "watchdog"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
description = "Filesystem events monitoring"
|
description = "Filesystem events monitoring"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"},
|
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"},
|
||||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"},
|
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"},
|
||||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"},
|
{file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"},
|
||||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"},
|
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"},
|
||||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"},
|
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"},
|
||||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"},
|
{file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"},
|
||||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"},
|
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"},
|
||||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"},
|
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"},
|
||||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"},
|
{file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"},
|
||||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"},
|
{file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"},
|
||||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"},
|
{file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"},
|
||||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"},
|
{file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"},
|
||||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"},
|
{file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"},
|
||||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"},
|
{file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"},
|
||||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"},
|
{file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"},
|
||||||
{file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"},
|
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"},
|
||||||
{file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"},
|
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"},
|
||||||
{file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"},
|
{file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"},
|
{file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"},
|
{file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"},
|
{file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"},
|
||||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"},
|
||||||
{file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"},
|
||||||
{file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"},
|
||||||
{file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"},
|
{file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"},
|
||||||
{file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"},
|
{file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"},
|
||||||
|
{file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"},
|
||||||
|
{file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"},
|
||||||
|
{file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
@ -2498,4 +2655,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.11,<3.12"
|
python-versions = ">=3.11,<3.12"
|
||||||
content-hash = "229d7fd39618cf708f3cd5409dde2e6e25b822e4f936e14b3ade9800bf00daab"
|
content-hash = "3f732c0b0b0eb2a31fb9484c7cf699cd3c26474d6779528102af4c509a48351e"
|
||||||
|
|
|
@ -9,11 +9,12 @@ package-mode = false
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.11,<3.12"
|
python = ">=3.11,<3.12"
|
||||||
Red-DiscordBot = "^3.5.5"
|
Red-DiscordBot = "^3.5.9"
|
||||||
py-dactyl = "^2.0.4"
|
py-dactyl = "^2.0.4"
|
||||||
websockets = "^12.0"
|
websockets = "^12.0"
|
||||||
pillow = "^10.3.0"
|
pillow = "^10.3.0"
|
||||||
numpy = "^1.26.4"
|
numpy = "^1.26.4"
|
||||||
|
pydantic = "^2.7.1"
|
||||||
colorthief = "^0.2.1"
|
colorthief = "^0.2.1"
|
||||||
beautifulsoup4 = "^4.12.3"
|
beautifulsoup4 = "^4.12.3"
|
||||||
markdownify = "^0.12.1"
|
markdownify = "^0.12.1"
|
||||||
|
@ -24,6 +25,7 @@ optional = true
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.3.1"
|
ruff = "^0.3.1"
|
||||||
pylint = "^3.1.0"
|
pylint = "^3.1.0"
|
||||||
|
pipx = "^1.5.0"
|
||||||
|
|
||||||
[tool.poetry.group.docs]
|
[tool.poetry.group.docs]
|
||||||
optional = true
|
optional = true
|
||||||
|
|
Loading…
Reference in a new issue