aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Johannes Christ <[email protected]>2019-01-06 20:18:51 +0100
committerGravatar Johannes Christ <[email protected]>2019-01-06 20:19:06 +0100
commit6e640122cca34bd8a060f75f44a878a00f2b30c5 (patch)
tree36dcef814729ab18574d6092060d5b68fe4e4192
parentMove note and warning creation to Django API backend. (diff)
Move all moderation commands to the Django API.
-rw-r--r--Pipfile3
-rw-r--r--Pipfile.lock100
-rw-r--r--bot/api.py4
-rw-r--r--bot/cogs/moderation.py349
-rw-r--r--bot/converters.py21
-rw-r--r--bot/utils/moderation.py7
-rw-r--r--bot/utils/time.py2
7 files changed, 291 insertions, 195 deletions
diff --git a/Pipfile b/Pipfile
index aafc9107a..703057af5 100644
--- a/Pipfile
+++ b/Pipfile
@@ -19,6 +19,7 @@ aio-pika = "*"
python-dateutil = "*"
deepdiff = "*"
requests = "*"
+dateparser = "*"
[dev-packages]
"flake8" = ">=3.6"
@@ -32,7 +33,7 @@ dodgy = "*"
pytest = "*"
[requires]
-python_version = "3.6"
+python_version = "3.7"
[scripts]
start = "python -m bot"
diff --git a/Pipfile.lock b/Pipfile.lock
index e273e5276..6c565c85f 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "4f40c03d9fa30eb15e0bc6b962d42d3185e2b2cdf52d9b31969bbd119b9ed61e"
+ "sha256": "52bb2561c3036c40f44d3c5da359a5089e7a543cb05d9c8525553207697c98a1"
},
"pipfile-spec": 6,
"requires": {
@@ -147,6 +147,14 @@
],
"version": "==3.0.4"
},
+ "dateparser": {
+ "hashes": [
+ "sha256:940828183c937bcec530753211b70f673c0a9aab831e43273489b310538dff86",
+ "sha256:b452ef8b36cd78ae86a50721794bc674aa3994e19b570f7ba92810f4e0a2ae03"
+ ],
+ "index": "pypi",
+ "version": "==0.7.0"
+ },
"deepdiff": {
"hashes": [
"sha256:152b29dd9cd97cc78403121fb394925ec47377d4a410751e56547c3930ba2b39",
@@ -232,39 +240,35 @@
},
"lxml": {
"hashes": [
- "sha256:16cf8bac33ec17049617186d63006ba49da7c5be417042877a49f0ef6d7a195d",
- "sha256:18f2d8f14cc61e66e8a45f740d15b6fc683c096f733db1f8d0ee15bcac9843de",
- "sha256:260868f69d14a64dd1de9cf92e133d2f71514d288de4906f109bdf48ca9b756a",
- "sha256:29b8acd8ecdf772266dbac491f203c71664b0b07ad4309ba2c3bb131306332fc",
- "sha256:2b05e5e06f8e8c63595472dc887d0d6e0250af754a35ba690f6a6abf2ef85691",
- "sha256:30d6ec05fb607a5b7345549f642c7c7a5b747b634f6d5e935596b910f243f96f",
- "sha256:3bf683f0237449ebc1851098f664410e3c99ba3faa8c9cc82c6acfe857df1767",
- "sha256:3ce5488121eb15513c4b239dadd67f9e7959511bd766aac6be0c35e80274f298",
- "sha256:48be0c375350a5519bb9474b42a9c0e7ab709fb45f11bfcd33de876791137896",
- "sha256:49bc343ca3b30cd860845433bb9f62448a54ff87b632175108bacbc5dc63e49e",
- "sha256:4cc7531e86a43ea66601763c5914c3d3adb297f32e4284957609b90d41825fca",
- "sha256:4e9822fad564d82035f0b6d701a890444560210f8a8648b8f15850f8fe883cd9",
- "sha256:51a9a441aefc8c93512bad5efe867d2ff086e7249ce0fc3b47c310644b352936",
- "sha256:5bbed9efc8aeb69929140f71a30e655bf496b45b766861513960e1b11168d475",
- "sha256:60a5323b2bc893ca1059d283d6695a172d51cc95a70c25b3e587e1aad5459c38",
- "sha256:7035d9361f3ceec9ccc1dd3482094d1174580e7e1bf6870b77ea758f7cad15d2",
- "sha256:76d62cc048bda0ebf476689ad3eb8e65e6827e43a7521be3b163071020667b8c",
- "sha256:78163b578e6d1836012febaa1865e095ccc7fc826964dd69a2dbfe401618a1f7",
- "sha256:83b58b2b5904d50de03a47e2f56d24e9da4cf7e3b0d66fb4510b18fca0faf910",
- "sha256:a07447e46fffa5bb4d7a0af0a6505c8517e9bd197cfd2aec79e499b6e86cde49",
- "sha256:a17d808b3edca4aaf6b295b5a388c844a0b7f79aca2d79eec5acc1461db739e3",
- "sha256:a378fd61022cf4d3b492134c3bc48204ac2ff19e0813b23e07c3dd95ae8df0bc",
- "sha256:aa7d096a44ae3d475c5ed763e24cf302d32462e78b61bba73ce1ad0efb8f522a",
- "sha256:ade8785c93a985956ba6499d5ea6d0a362e24b4a9ba07dd18920fd67cccf63ea",
- "sha256:cc039668f91d8af8c4094cfb5a67c7ae733967fdc84c0507fe271db81480d367",
- "sha256:d89f1ffe98744c4b5c11f00fb843a4e72f68a6279b5e38168167f1b3c0fdd84c",
- "sha256:e691b6ef6e27437860016bd6c32e481bdc2ed3af03289707a38b9ca422105f40",
- "sha256:e750da6ac3ca624ae3303df448664012f9b6f9dfbc5d50048ea8a12ce2f8bc29",
- "sha256:eca305b200549906ea25648463aeb1b3b220b716415183eaa99c998a846936d9",
- "sha256:f52fe795e08858192eea167290033b5ff24f50f51781cb78d989e8d63cfe73d1"
+ "sha256:0dd6589fa75d369ba06d2b5f38dae107f76ea127f212f6a7bee134f6df2d1d21",
+ "sha256:1afbac344aa68c29e81ab56c1a9411c3663157b5aee5065b7fa030b398d4f7e0",
+ "sha256:1baad9d073692421ad5dbbd81430aba6c7f5fdc347f03537ae046ddf2c9b2297",
+ "sha256:1d8736421a2358becd3edf20260e41a06a0bf08a560480d3a5734a6bcbacf591",
+ "sha256:1e1d9bddc5afaddf0de76246d3f2152f961697ad7439c559f179002682c45801",
+ "sha256:1f179dc8b2643715f020f4d119d5529b02cd794c1c8f305868b73b8674d2a03f",
+ "sha256:241fb7bdf97cb1df1edfa8f0bcdfd80525d4023dac4523a241907c8b2f44e541",
+ "sha256:2f9765ee5acd3dbdcdc0d0c79309e01f7c16bc8d39b49250bf88de7b46daaf58",
+ "sha256:312e1e1b1c3ce0c67e0b8105317323e12807955e8186872affb667dbd67971f6",
+ "sha256:3273db1a8055ca70257fd3691c6d2c216544e1a70b673543e15cc077d8e9c730",
+ "sha256:34dfaa8c02891f9a246b17a732ca3e99c5e42802416628e740a5d1cb2f50ff49",
+ "sha256:3aa3f5288af349a0f3a96448ebf2e57e17332d99f4f30b02093b7948bd9f94cc",
+ "sha256:51102e160b9d83c1cc435162d90b8e3c8c93b28d18d87b60c56522d332d26879",
+ "sha256:56115fc2e2a4140e8994eb9585119a1ae9223b506826089a3ba753a62bd194a6",
+ "sha256:69d83de14dbe8fe51dccfd36f88bf0b40f5debeac763edf9f8325180190eba6e",
+ "sha256:99fdce94aeaa3ccbdfcb1e23b34273605c5853aa92ec23d84c84765178662c6c",
+ "sha256:a7c0cd5b8a20f3093ee4a67374ccb3b8a126743b15a4d759e2a1bf098faac2b2",
+ "sha256:abe12886554634ed95416a46701a917784cb2b4c77bfacac6916681d49bbf83d",
+ "sha256:b4f67b5183bd5f9bafaeb76ad119e977ba570d2b0e61202f534ac9b5c33b4485",
+ "sha256:bdd7c1658475cc1b867b36d5c4ed4bc316be8d3368abe03d348ba906a1f83b0e",
+ "sha256:c6f24149a19f611a415a51b9bc5f17b6c2f698e0d6b41ffb3fa9f24d35d05d73",
+ "sha256:d1e111b3ab98613115a208c1017f266478b0ab224a67bc8eac670fa0bad7d488",
+ "sha256:d6520aa965773bbab6cb7a791d5895b00d02cf9adc93ac2bf4edb9ac1a6addc5",
+ "sha256:dd185cde2ccad7b649593b0cda72021bc8a91667417001dbaf24cd746ecb7c11",
+ "sha256:de2e5b0828a9d285f909b5d2e9d43f1cf6cf21fe65bc7660bdaa1780c7b58298",
+ "sha256:f726444b8e909c4f41b4fde416e1071cf28fa84634bfb4befdf400933b6463af"
],
"index": "pypi",
- "version": "==4.2.6"
+ "version": "==4.3.0"
},
"markdownify": {
"hashes": [
@@ -506,6 +510,20 @@
"index": "pypi",
"version": "==3.13"
},
+ "regex": {
+ "hashes": [
+ "sha256:15b4a185ae9782133f398f8ab7c29612a6e5f34ea9411e4cd36e91e78c347ebe",
+ "sha256:3852b76f0b6d7bd98d328d548716c151b79017f2b81347360f26e5db10fb6503",
+ "sha256:79a6a60ed1ee3b12eb0e828c01d75e3b743af6616d69add6c2fde1d425a4ba3f",
+ "sha256:a2938c290b3be2c7cadafa21de3051f2ed23bfaf88728a1fe5dc552cbfdb0326",
+ "sha256:aff7414712c9e6d260609da9c9af3aacebfbc307a4abe3376c7736e2a6c8563f",
+ "sha256:d03782f0b0fa34f8f1dbdc94e27cf193b83c6105307a8c10563938c6d85180d9",
+ "sha256:db79ac3d81e655dc12d38a865dd6d1b569a28fab4c53749051cd599a6eb7614f",
+ "sha256:e803b3646c3f9c47f1f3dc870173c5d79c0fd2fd8e40bf917b97c7b56701baff",
+ "sha256:e9660ccca360b6bd79606aab3672562ebb14bce6af6c501107364668543f4bef"
+ ],
+ "version": "==2018.11.22"
+ },
"requests": {
"hashes": [
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
@@ -536,10 +554,10 @@
},
"soupsieve": {
"hashes": [
- "sha256:057e08f362a255b457a5781675211556799ed3bb8807506eaac3809390bc304b",
- "sha256:f7d99b41637be2f249dfcc06ae93c13fcbbdfa7bb68b15308cdd0734e58146f1"
+ "sha256:638535780f7b966411123d56eb3b89cd1d2e42d707270c6d7d053c7720a238f3",
+ "sha256:cb61b59c55f9f6e91928a03fe4b500ac1fcef6f8e68082a630db098ab33e2126"
],
- "version": "==1.6.1"
+ "version": "==1.6.2"
},
"sphinx": {
"hashes": [
@@ -556,6 +574,12 @@
],
"version": "==1.1.0"
},
+ "tzlocal": {
+ "hashes": [
+ "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"
+ ],
+ "version": "==1.5.1"
+ },
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
@@ -769,11 +793,11 @@
},
"pytest": {
"hashes": [
- "sha256:f689bf2fc18c4585403348dd56f47d87780bf217c53ed9ae7a3e2d7faa45f8e9",
- "sha256:f812ea39a0153566be53d88f8de94839db1e8a05352ed8a49525d7d7f37861e9"
+ "sha256:3e65a22eb0d4f1bdbc1eacccf4a3198bf8d4049dea5112d70a0c61b00e748d02",
+ "sha256:5924060b374f62608a078494b909d341720a050b5224ff87e17e12377486a71d"
],
"index": "pypi",
- "version": "==4.0.2"
+ "version": "==4.1.0"
},
"pyyaml": {
"hashes": [
diff --git a/bot/api.py b/bot/api.py
index 0a2c192ce..2e1a239ba 100644
--- a/bot/api.py
+++ b/bot/api.py
@@ -29,6 +29,10 @@ class APIClient:
async with self.session.get(self._url_for(endpoint), *args, **kwargs) as resp:
return await resp.json()
+ async def patch(self, endpoint: str, *args, **kwargs):
+ async with self.session.patch(self._url_for(endpoint), *args, **kwargs) as resp:
+ return await resp.json()
+
async def post(self, endpoint: str, *args, **kwargs):
async with self.session.post(self._url_for(endpoint), *args, **kwargs) as resp:
return await resp.json()
diff --git a/bot/cogs/moderation.py b/bot/cogs/moderation.py
index d725755cd..51f4a3d79 100644
--- a/bot/cogs/moderation.py
+++ b/bot/cogs/moderation.py
@@ -1,6 +1,7 @@
import asyncio
import logging
import textwrap
+from datetime import datetime
from typing import Union
from discord import (
@@ -13,12 +14,12 @@ from discord.ext.commands import (
from bot import constants
from bot.cogs.modlog import ModLog
from bot.constants import Colours, Event, Icons, Keys, Roles, URLs
-from bot.converters import InfractionSearchQuery
+from bot.converters import ExpirationDate, InfractionSearchQuery
from bot.decorators import with_role
from bot.pagination import LinePaginator
from bot.utils.moderation import post_infraction
from bot.utils.scheduling import Scheduler, create_task
-from bot.utils.time import parse_rfc1123, wait_until
+from bot.utils.time import wait_until
log = logging.getLogger(__name__)
@@ -59,16 +60,13 @@ class Moderation(Scheduler):
async def on_ready(self):
# Schedule expiration for previous infractions
- response = await self.bot.http_session.get(
- URLs.site_infractions,
- params={"dangling": "true"},
- headers=self.headers
+ infractions = await self.bot.api_client.get(
+ 'bot/infractions', params={'active': 'true'}
)
- infraction_list = await response.json()
loop = asyncio.get_event_loop()
- for infraction_object in infraction_list:
- if infraction_object["expires_at"] is not None:
- self.schedule_task(loop, infraction_object["id"], infraction_object)
+ for infraction in infractions:
+ if infraction["expires_at"] is not None:
+ self.schedule_task(loop, infraction["id"], infraction)
# region: Permanent infractions
@@ -172,10 +170,23 @@ class Moderation(Scheduler):
:param reason: The reason for the ban.
"""
+ active_bans = await self.bot.api_client.get(
+ 'bot/infractions',
+ params={
+ 'active': 'true',
+ 'type': 'ban',
+ 'user__id': str(user.id)
+ }
+ )
+ if active_bans:
+ return await ctx.send(
+ ":x: According to my records, this user is already banned. "
+ f"See infraction **#{active_bans[0]['id']}**."
+ )
+
notified = await self.notify_infraction(
user=user,
infr_type="Ban",
- duration="Permanent",
reason=reason
)
@@ -220,11 +231,23 @@ class Moderation(Scheduler):
:param reason: The reason for the mute.
"""
+ active_mutes = await self.bot.api_client.get(
+ 'bot/infractions',
+ params={
+ 'active': 'true',
+ 'type': 'mute',
+ 'user__id': str(user.id)
+ }
+ )
+ if active_mutes:
+ return await ctx.send(
+ ":x: According to my records, this user is already muted. "
+ f"See infraction **#{active_mutes[0]['id']}**."
+ )
+
notified = await self.notify_infraction(
- user=user,
- infr_type="Mute",
- duration="Permanent",
- reason=reason
+ user=user, infr_type="Mute",
+ expires_at="Permanent", reason=reason
)
response_object = await post_infraction(ctx, user, type="mute", reason=reason)
@@ -264,7 +287,10 @@ class Moderation(Scheduler):
@with_role(*MODERATION_ROLES)
@command(name="tempmute")
- async def tempmute(self, ctx: Context, user: Member, duration: str, *, reason: str = None):
+ async def tempmute(
+ self, ctx: Context, user: Member, expiration: ExpirationDate,
+ *, reason: str = None
+ ):
"""
Create a temporary mute infraction in the database for a user.
:param user: Accepts user mention, ID, etc.
@@ -272,25 +298,42 @@ class Moderation(Scheduler):
:param reason: The reason for the temporary mute.
"""
+ active_mutes = await self.bot.api_client.get(
+ 'bot/infractions',
+ params={
+ 'active': 'true',
+ 'type': 'mute',
+ 'user__id': str(user.id)
+ }
+ )
+ if active_mutes:
+ return await ctx.send(
+ ":x: According to my records, this user is already muted. "
+ f"See infraction **#{active_mutes[0]['id']}**."
+ )
+
notified = await self.notify_infraction(
- user=user,
- infr_type="Mute",
- duration=duration,
- reason=reason
+ user=user, infr_type="Mute",
+ expires_at=expiration, reason=reason
)
- response_object = await post_infraction(ctx, user, type="mute", reason=reason, duration=duration)
- if response_object is None:
- return
+ infraction = await post_infraction(
+ ctx, user,
+ type="mute", reason=reason,
+ expires_at=expiration
+ )
self.mod_log.ignore(Event.member_update, user.id)
await user.add_roles(self._muted_role, reason=reason)
- infraction_object = response_object["infraction"]
- infraction_expiration = infraction_object["expires_at"]
+ infraction_expiration = (
+ datetime
+ .fromisoformat(infraction["expires_at"][:-1])
+ .strftime('%c')
+ )
loop = asyncio.get_event_loop()
- self.schedule_task(loop, infraction_object["id"], infraction_object)
+ self.schedule_task(loop, infraction["id"], infraction)
dm_result = ":incoming_envelope: " if notified else ""
action = f"{dm_result}:ok_hand: muted {user.mention} until {infraction_expiration}"
@@ -313,30 +356,47 @@ class Moderation(Scheduler):
Member: {user.mention} (`{user.id}`)
Actor: {ctx.message.author}
Reason: {reason}
- Duration: {duration}
Expires: {infraction_expiration}
""")
)
@with_role(*MODERATION_ROLES)
@command(name="tempban")
- async def tempban(self, ctx: Context, user: Union[User, proxy_user], duration: str, *, reason: str = None):
+ async def tempban(
+ self, ctx: Context, user: Union[User, proxy_user], expiry: ExpirationDate,
+ *, reason: str = None
+ ):
"""
Create a temporary ban infraction in the database for a user.
:param user: Accepts user mention, ID, etc.
- :param duration: The duration for the temporary ban infraction
+ :param expiry: The duration for the temporary ban infraction
:param reason: The reason for the temporary ban.
"""
+ active_bans = await self.bot.api_client.get(
+ 'bot/infractions',
+ params={
+ 'active': 'true',
+ 'type': 'ban',
+ 'user__id': str(user.id)
+ }
+ )
+ if active_bans:
+ return await ctx.send(
+ ":x: According to my records, this user is already banned. "
+ f"See infraction **#{active_bans[0]['id']}**."
+ )
+
notified = await self.notify_infraction(
- user=user,
- infr_type="Ban",
- duration=duration,
- reason=reason
+ user=user, infr_type="Ban",
+ expires_at=expiry, reason=reason
)
- response_object = await post_infraction(ctx, user, type="ban", reason=reason, duration=duration)
- if response_object is None:
+ infraction = await post_infraction(
+ ctx, user, type="ban",
+ reason=reason, expires_at=expiry
+ )
+ if infraction is None:
return
self.mod_log.ignore(Event.member_ban, user.id)
@@ -344,11 +404,14 @@ class Moderation(Scheduler):
guild: Guild = ctx.guild
await guild.ban(user, reason=reason, delete_message_days=0)
- infraction_object = response_object["infraction"]
- infraction_expiration = infraction_object["expires_at"]
+ infraction_expiration = (
+ datetime
+ .fromisoformat(infraction["expires_at"][:-1])
+ .strftime('%c')
+ )
loop = asyncio.get_event_loop()
- self.schedule_task(loop, infraction_object["id"], infraction_object)
+ self.schedule_task(loop, infraction["id"], infraction)
dm_result = ":incoming_envelope: " if notified else ""
action = f"{dm_result}:ok_hand: banned {user.mention} until {infraction_expiration}"
@@ -371,7 +434,6 @@ class Moderation(Scheduler):
Member: {user.mention} (`{user.id}`)
Actor: {ctx.message.author}
Reason: {reason}
- Duration: {duration}
Expires: {infraction_expiration}
""")
)
@@ -635,27 +697,26 @@ class Moderation(Scheduler):
try:
# check the current active infraction
- response = await self.bot.http_session.get(
- URLs.site_infractions_user_type_current.format(
- user_id=user.id,
- infraction_type="mute"
- ),
- headers=self.headers
+ response = await self.bot.api_client.get(
+ 'bot/infractions',
+ params={
+ 'active': 'true',
+ 'type': 'mute',
+ 'user__id': user.id
+ }
)
- response_object = await response.json()
- if "error_code" in response_object:
- await ctx.send(f":x: There was an error removing the infraction: {response_object['error_message']}")
- return
+ if len(response) > 1:
+ log.warning("Found more than one active mute infraction for user `%d`", user.id)
- infraction_object = response_object["infraction"]
- if infraction_object is None:
+ if not response:
# no active infraction
await ctx.send(f":x: There is no active mute infraction for user {user.mention}.")
return
- await self._deactivate_infraction(infraction_object)
- if infraction_object["expires_at"] is not None:
- self.cancel_expiration(infraction_object["id"])
+ infraction = response[0]
+ await self._deactivate_infraction(infraction)
+ if infraction["expires_at"] is not None:
+ self.cancel_expiration(infraction["id"])
notified = await self.notify_pardon(
user=user,
@@ -679,7 +740,7 @@ class Moderation(Scheduler):
text=textwrap.dedent(f"""
Member: {user.mention} (`{user.id}`)
Actor: {ctx.message.author}
- Intended expiry: {infraction_object['expires_at']}
+ Intended expiry: {infraction['expires_at']}
""")
)
except Exception:
@@ -697,27 +758,29 @@ class Moderation(Scheduler):
try:
# check the current active infraction
- response = await self.bot.http_session.get(
- URLs.site_infractions_user_type_current.format(
- user_id=user.id,
- infraction_type="ban"
- ),
- headers=self.headers
+ response = await self.bot.api_client.get(
+ 'bot/infractions',
+ params={
+ 'active': 'true',
+ 'type': 'ban',
+ 'user__id': str(user.id)
+ }
)
- response_object = await response.json()
- if "error_code" in response_object:
- await ctx.send(f":x: There was an error removing the infraction: {response_object['error_message']}")
- return
+ if len(response) > 1:
+ log.warning(
+ "More than one active ban infraction found for user `%d`.",
+ user.id
+ )
- infraction_object = response_object["infraction"]
- if infraction_object is None:
+ if not response:
# no active infraction
await ctx.send(f":x: There is no active ban infraction for user {user.mention}.")
return
- await self._deactivate_infraction(infraction_object)
- if infraction_object["expires_at"] is not None:
- self.cancel_expiration(infraction_object["id"])
+ infraction = response[0]
+ await self._deactivate_infraction(infraction)
+ if infraction["expires_at"] is not None:
+ self.cancel_expiration(infraction["id"])
await ctx.send(f":ok_hand: Un-banned {user.mention}.")
@@ -730,7 +793,7 @@ class Moderation(Scheduler):
text=textwrap.dedent(f"""
Member: {user.mention} (`{user.id}`)
Actor: {ctx.message.author}
- Intended expiry: {infraction_object['expires_at']}
+ Intended expiry: {infraction['expires_at']}
""")
)
except Exception:
@@ -757,60 +820,64 @@ class Moderation(Scheduler):
@with_role(*MODERATION_ROLES)
@infraction_edit_group.command(name="duration")
- async def edit_duration(self, ctx: Context, infraction_id: str, duration: str):
+ async def edit_duration(
+ self, ctx: Context,
+ infraction_id: int, expires_at: Union[ExpirationDate, str]
+ ):
"""
Sets the duration of the given infraction, relative to the time of updating.
- :param infraction_id: the id (UUID) of the infraction
- :param duration: the new duration of the infraction, relative to the time of updating. Use "permanent" to mark
- the infraction as permanent.
+ :param infraction_id: the id of the infraction
+ :param expires_at: the new expiration date of the infraction.
+ Use "permanent" to mark the infraction as permanent.
"""
- try:
- previous = await self.bot.http_session.get(
- URLs.site_infractions_by_id.format(
- infraction_id=infraction_id
- ),
- headers=self.headers
+ if isinstance(expires_at, str) and expires_at != 'permanent':
+ raise BadArgument(
+ "If `expires_at` is given as a non-datetime, "
+ "it must be `permanent`."
)
+ if expires_at == 'permanent':
+ expires_at = None
- previous_object = await previous.json()
+ try:
+ previous_infraction = await self.bot.api_client.get(
+ 'bot/infractions/' + str(infraction_id)
+ )
- if duration == "permanent":
- duration = None
# check the current active infraction
- response = await self.bot.http_session.patch(
- URLs.site_infractions,
+ infraction = await self.bot.api_client.patch(
+ 'bot/infractions/' + str(infraction_id),
json={
- "id": infraction_id,
- "duration": duration
- },
- headers=self.headers
+ 'expires_at': (
+ expires_at.isoformat()
+ if expires_at is not None
+ else None
+ )
+ }
)
- response_object = await response.json()
- if "error_code" in response_object or response_object.get("success") is False:
- await ctx.send(f":x: There was an error updating the infraction: {response_object['error_message']}")
- return
- infraction_object = response_object["infraction"]
# Re-schedule
- self.cancel_task(infraction_id)
+ self.cancel_task(infraction['id'])
loop = asyncio.get_event_loop()
- self.schedule_task(loop, infraction_object["id"], infraction_object)
+ self.schedule_task(loop, infraction['id'], infraction)
- if duration is None:
+ if expires_at is None:
await ctx.send(f":ok_hand: Updated infraction: marked as permanent.")
else:
- await ctx.send(f":ok_hand: Updated infraction: set to expire on {infraction_object['expires_at']}.")
+ human_expiry = (
+ datetime
+ .fromisoformat(infraction['expires_at'][:-1])
+ .strftime('%c')
+ )
+ await ctx.send(f":ok_hand: Updated infraction: set to expire on {human_expiry}.")
except Exception:
log.exception("There was an error updating an infraction.")
await ctx.send(":x: There was an error updating the infraction.")
return
- prev_infraction = previous_object["infraction"]
-
# Get information about the infraction's user
- user_id = int(infraction_object["user"]["user_id"])
+ user_id = infraction["user"]
user = ctx.guild.get_member(user_id)
if user:
@@ -821,7 +888,7 @@ class Moderation(Scheduler):
thumbnail = None
# The infraction's actor
- actor_id = int(infraction_object["actor"]["user_id"])
+ actor_id = infraction["actor"]
actor = ctx.guild.get_member(actor_id) or f"`{actor_id}`"
await self.mod_log.send_log_message(
@@ -833,54 +900,38 @@ class Moderation(Scheduler):
Member: {member_text}
Actor: {actor}
Edited by: {ctx.message.author}
- Previous expiry: {prev_infraction['expires_at']}
- New expiry: {infraction_object['expires_at']}
+ Previous expiry: {previous_infraction['expires_at']}
+ New expiry: {infraction['expires_at']}
""")
)
@with_role(*MODERATION_ROLES)
@infraction_edit_group.command(name="reason")
- async def edit_reason(self, ctx: Context, infraction_id: str, *, reason: str):
+ async def edit_reason(self, ctx: Context, infraction_id: int, *, reason: str):
"""
Sets the reason of the given infraction.
- :param infraction_id: the id (UUID) of the infraction
+ :param infraction_id: the id of the infraction
:param reason: The new reason of the infraction
"""
try:
- previous = await self.bot.http_session.get(
- URLs.site_infractions_by_id.format(
- infraction_id=infraction_id
- ),
- headers=self.headers
+ old_infraction = await self.bot.api_client.get(
+ 'bot/infractions/' + str(infraction_id)
)
- previous_object = await previous.json()
-
- response = await self.bot.http_session.patch(
- URLs.site_infractions,
- json={
- "id": infraction_id,
- "reason": reason
- },
- headers=self.headers
+ updated_infraction = await self.bot.api_client.patch(
+ 'bot/infractions/' + str(infraction_id),
+ json={'reason': reason}
)
- response_object = await response.json()
- if "error_code" in response_object or response_object.get("success") is False:
- await ctx.send(f":x: There was an error updating the infraction: {response_object['error_message']}")
- return
-
await ctx.send(f":ok_hand: Updated infraction: set reason to \"{reason}\".")
+
except Exception:
log.exception("There was an error updating an infraction.")
await ctx.send(":x: There was an error updating the infraction.")
return
- new_infraction = response_object["infraction"]
- prev_infraction = previous_object["infraction"]
-
# Get information about the infraction's user
- user_id = int(new_infraction["user"]["user_id"])
+ user_id = updated_infraction['user']
user = ctx.guild.get_member(user_id)
if user:
@@ -891,7 +942,7 @@ class Moderation(Scheduler):
thumbnail = None
# The infraction's actor
- actor_id = int(new_infraction["actor"]["user_id"])
+ actor_id = updated_infraction['actor']
actor = ctx.guild.get_member(actor_id) or f"`{actor_id}`"
await self.mod_log.send_log_message(
@@ -903,8 +954,8 @@ class Moderation(Scheduler):
Member: {user_text}
Actor: {actor}
Edited by: {ctx.message.author}
- Previous reason: {prev_infraction['reason']}
- New reason: {new_infraction['reason']}
+ Previous reason: {old_infraction['reason']}
+ New reason: {updated_infraction['reason']}
""")
)
@@ -1023,7 +1074,7 @@ class Moderation(Scheduler):
infraction_id = infraction_object["id"]
# transform expiration to delay in seconds
- expiration_datetime = parse_rfc1123(infraction_object["expires_at"])
+ expiration_datetime = datetime.fromisoformat(infraction_object["expires_at"][:-1])
await wait_until(expiration_datetime)
log.debug(f"Marking infraction {infraction_id} as inactive (expired).")
@@ -1032,7 +1083,7 @@ class Moderation(Scheduler):
self.cancel_task(infraction_object["id"])
# Notify the user that they've been unmuted.
- user_id = int(infraction_object["user"]["user_id"])
+ user_id = infraction_object["user"]
guild = self.bot.get_guild(constants.Guild.id)
await self.notify_pardon(
user=guild.get_member(user_id),
@@ -1043,13 +1094,13 @@ class Moderation(Scheduler):
async def _deactivate_infraction(self, infraction_object):
"""
- A co-routine which marks an infraction as inactive on the website. This co-routine does not cancel or
- un-schedule an expiration task.
+ A co-routine which marks an infraction as inactive on the website.
+ This co-routine does not cancel or un-schedule an expiration task.
:param infraction_object: the infraction in question
"""
guild: Guild = self.bot.get_guild(constants.Guild.id)
- user_id = int(infraction_object["user"]["user_id"])
+ user_id = infraction_object["user"]
infraction_type = infraction_object["type"]
if infraction_type == "mute":
@@ -1064,13 +1115,9 @@ class Moderation(Scheduler):
user: Object = Object(user_id)
await guild.unban(user)
- await self.bot.http_session.patch(
- URLs.site_infractions,
- headers=self.headers,
- json={
- "id": infraction_object["id"],
- "active": False
- }
+ await self.bot.api_client.patch(
+ 'bot/infractions/' + str(infraction_object['id']),
+ json={"active": False}
)
def _infraction_to_string(self, infraction_object):
@@ -1098,7 +1145,8 @@ class Moderation(Scheduler):
return lines.strip()
async def notify_infraction(
- self, user: Union[User, Member], infr_type: str, duration: str = None, reason: str = None
+ self, user: Union[User, Member], infr_type: str,
+ expires_at: Union[datetime, str] = 'N/A', reason: str = "No reason provided."
):
"""
Notify a user of their fresh infraction :)
@@ -1109,16 +1157,13 @@ class Moderation(Scheduler):
:param reason: The reason for the infraction.
"""
- if duration is None:
- duration = "N/A"
-
- if reason is None:
- reason = "No reason provided."
+ if isinstance(expires_at, datetime):
+ expires_at = expires_at.strftime('%c')
embed = Embed(
description=textwrap.dedent(f"""
**Type:** {infr_type}
- **Duration:** {duration}
+ **Expires:** {expires_at}
**Reason:** {reason}
"""),
colour=Colour(Colours.soft_red)
diff --git a/bot/converters.py b/bot/converters.py
index 069e841f9..1100b502c 100644
--- a/bot/converters.py
+++ b/bot/converters.py
@@ -1,8 +1,10 @@
import logging
import random
import socket
+from datetime import datetime
from ssl import CertificateError
+import dateparser
import discord
from aiohttp import AsyncResolver, ClientConnectorError, ClientSession, TCPConnector
from discord.ext.commands import BadArgument, Context, Converter
@@ -254,3 +256,22 @@ class TagContentConverter(Converter):
raise BadArgument("Tag contents should not be empty, or filled with whitespace.")
return tag_content
+
+
+class ExpirationDate(Converter):
+ DATEPARSER_SETTINGS = {
+ 'PREFER_DATES_FROM': 'future',
+ 'TIMEZONE': 'UTC',
+ 'TO_TIMEZONE': 'UTC'
+ }
+
+ async def convert(self, ctx, expiration_string: str):
+ expiry = dateparser.parse(expiration_string, settings=self.DATEPARSER_SETTINGS)
+ if expiry is None:
+ raise BadArgument(f"Failed to parse expiration date from `{expiration_string}`")
+
+ now = datetime.utcnow()
+ if expiry < now:
+ expiry = now + (now - expiry)
+
+ return expiry
diff --git a/bot/utils/moderation.py b/bot/utils/moderation.py
index 459fe6eb3..2611ee993 100644
--- a/bot/utils/moderation.py
+++ b/bot/utils/moderation.py
@@ -1,4 +1,5 @@
import logging
+from datetime import datetime
from typing import Union
from aiohttp import ClientError
@@ -14,7 +15,7 @@ HEADERS = {"X-API-KEY": Keys.site_api}
async def post_infraction(
ctx: Context, user: Union[Member, Object, User],
- type: str, reason: str, duration: str = None, hidden: bool = False
+ type: str, reason: str, expires_at: datetime = None, hidden: bool = False
):
payload = {
@@ -24,8 +25,8 @@ async def post_infraction(
"type": type,
"user": user.id
}
- if duration:
- payload['duration'] = duration
+ if expires_at:
+ payload['expires_at'] = expires_at.isoformat()
try:
response = await ctx.bot.api_client.post(
diff --git a/bot/utils/time.py b/bot/utils/time.py
index 8e5d4e1bd..a330c9cd8 100644
--- a/bot/utils/time.py
+++ b/bot/utils/time.py
@@ -106,7 +106,7 @@ async def wait_until(time: datetime.datetime):
:param time: A datetime.datetime object to wait until.
"""
- delay = time - datetime.datetime.now(tz=datetime.timezone.utc)
+ delay = time - datetime.datetime.utcnow()
delay_seconds = delay.total_seconds()
if delay_seconds > 1.0: