From 93237e34f8039f6ffe0ae64d7ccc1e944bb4876e Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 27 Aug 2019 22:14:38 -0300 Subject: [PATCH 1/5] add bulk delete endpoint - schemas: add BULK_DELETE --- litecord/blueprints/channels.py | 37 ++++++++++++++++++++++++++++++++- litecord/schemas.py | 7 +++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 6711181..b1de863 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -27,7 +27,8 @@ from litecord.auth import token_check from litecord.enums import ChannelType, GUILD_CHANS, MessageType from litecord.errors import ChannelNotFound, Forbidden from litecord.schemas import ( - validate, CHAN_UPDATE, CHAN_OVERWRITE, SEARCH_CHANNEL, GROUP_DM_UPDATE + validate, CHAN_UPDATE, CHAN_OVERWRITE, SEARCH_CHANNEL, GROUP_DM_UPDATE, + BULK_DELETE, ) from litecord.blueprints.checks import channel_check, channel_perm_check @@ -664,3 +665,37 @@ async def suppress_embeds(channel_id: int, message_id: int): ) return '', 204 + + +@bp.route('//messages/bulk-delete', methods=['POST']) +async def bulk_delete(channel_id: int): + user_id = await token_check() + ctype, guild_id = await channel_check(user_id, channel_id) + guild_id = guild_id if ctype in GUILD_CHANS else None + + await channel_perm_check(user_id, channel_id, 'manage_messages') + + j = validate(await request.get_json(), BULK_DELETE) + message_ids = j['messages'] + + # as per discord behavior, if any id here is older than two weeks, + # we must error. a cuter behavior would be returning the message ids + # that were deleted, ignoring the 2 week+ old ones. + for message_id in message_ids: + # TODO + pass + + payload = { + 'guild_id': str(guild_id), + 'channel_id': str(channel_id), + 'ids': message_ids + } + + # payload.guild_id is optional in the event, not nullable. + if guild_id is None: + payload.pop('guild_id') + + # TODO delete messages + + await app.dispatcher.dispatch_channel('MESSAGE_DELETE_BULK', payload) + return '', 204 diff --git a/litecord/schemas.py b/litecord/schemas.py index 50e6d09..60eca24 100644 --- a/litecord/schemas.py +++ b/litecord/schemas.py @@ -737,3 +737,10 @@ WEBHOOK_MESSAGE_CREATE = { 'schema': {'type': 'dict', 'schema': EMBED_OBJECT} } } + +BULK_DELETE = { + 'messages': { + 'type': 'list', 'required': True, + 'schema': {'coerce': int} + } +} From e4386e8656d88e99d8c4e99a443e7b33c3d00779 Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 27 Aug 2019 22:32:38 -0300 Subject: [PATCH 2/5] channels: add bulk delete sql query - schemas: add minlength, maxlength to BULK_DELETE --- litecord/blueprints/channels.py | 9 +++++++-- litecord/schemas.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index b1de863..a923f90 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -688,14 +688,19 @@ async def bulk_delete(channel_id: int): payload = { 'guild_id': str(guild_id), 'channel_id': str(channel_id), - 'ids': message_ids + 'ids': list(map(str, message_ids)), } # payload.guild_id is optional in the event, not nullable. if guild_id is None: payload.pop('guild_id') - # TODO delete messages + await app.db.execute(""" + DELETE FROM messages + WHERE + channel_id = $1 + AND ARRAY[message_id] <@ $2::bigint[] + """, channel_id, message_ids) await app.dispatcher.dispatch_channel('MESSAGE_DELETE_BULK', payload) return '', 204 diff --git a/litecord/schemas.py b/litecord/schemas.py index 60eca24..de5aa21 100644 --- a/litecord/schemas.py +++ b/litecord/schemas.py @@ -741,6 +741,7 @@ WEBHOOK_MESSAGE_CREATE = { BULK_DELETE = { 'messages': { 'type': 'list', 'required': True, + 'minlength': 2, 'maxlength': 100, 'schema': {'coerce': int} } } From 00f7e076acbfed6afc38cd92ba0ad6389aafd0bd Mon Sep 17 00:00:00 2001 From: Luna Date: Wed, 28 Aug 2019 15:22:34 -0300 Subject: [PATCH 3/5] channels: add checking of message id's age in bulk delete --- litecord/blueprints/channels.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index a923f90..2c13a39 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -18,6 +18,7 @@ along with this program. If not, see . """ import time +import datetime from typing import List, Optional from quart import Blueprint, request, current_app as app, jsonify @@ -25,7 +26,7 @@ from logbook import Logger from litecord.auth import token_check from litecord.enums import ChannelType, GUILD_CHANS, MessageType -from litecord.errors import ChannelNotFound, Forbidden +from litecord.errors import ChannelNotFound, Forbidden, BadRequest from litecord.schemas import ( validate, CHAN_UPDATE, CHAN_OVERWRITE, SEARCH_CHANNEL, GROUP_DM_UPDATE, BULK_DELETE, @@ -38,6 +39,7 @@ from litecord.blueprints.dm_channels import ( ) from litecord.utils import search_result_from_list from litecord.embed.messages import process_url_embed, msg_update_embeds +from litecord.snowflake import snowflake_datetime log = Logger(__name__) bp = Blueprint('channels', __name__) @@ -682,8 +684,11 @@ async def bulk_delete(channel_id: int): # we must error. a cuter behavior would be returning the message ids # that were deleted, ignoring the 2 week+ old ones. for message_id in message_ids: - # TODO - pass + message_dt = snowflake_datetime(message_id) + delta = datetime.datetime.utcnow() - message_dt + + if delta.weeks > 2: + raise BadRequest(50034) payload = { 'guild_id': str(guild_id), From 1b4c39fe23d264f21888365032d0fea72e3a94c6 Mon Sep 17 00:00:00 2001 From: Luna Date: Sun, 1 Sep 2019 15:25:28 -0300 Subject: [PATCH 4/5] errors: check args[0] for integer values and convert to ERR_MSG_MAP - channels: s/weeks/days - schemas: handle reqjson being None on validate() --- litecord/blueprints/channels.py | 2 +- litecord/errors.py | 20 +++++++++++++------- litecord/schemas.py | 7 +++++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 2c13a39..d53cbeb 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -687,7 +687,7 @@ async def bulk_delete(channel_id: int): message_dt = snowflake_datetime(message_id) delta = datetime.datetime.utcnow() - message_dt - if delta.weeks > 2: + if delta.days > 14: raise BadRequest(50034) payload = { diff --git a/litecord/errors.py b/litecord/errors.py index 76f937c..c171b15 100644 --- a/litecord/errors.py +++ b/litecord/errors.py @@ -74,18 +74,24 @@ class LitecordError(Exception): """Base class for litecord errors""" status_code = 500 + def _get_err_msg(self, err_code: int) -> str: + if err_code is not None: + return ERR_MSG_MAP.get(err_code) or self.args[0] + + return repr(self) + @property def message(self) -> str: """Get an error's message string.""" try: - return self.args[0] + message = self.args[0] + + if isinstance(message, int): + return self._get_err_msg(message) + + return message except IndexError: - err_code = getattr(self, 'error_code', None) - - if err_code is not None: - return ERR_MSG_MAP.get(err_code) or self.args[0] - - return repr(self) + return self._get_err_msg(getattr(self, 'error_code', None)) @property def json(self): diff --git a/litecord/schemas.py b/litecord/schemas.py index de5aa21..bc9cce7 100644 --- a/litecord/schemas.py +++ b/litecord/schemas.py @@ -18,7 +18,7 @@ along with this program. If not, see . """ import re -from typing import Union, Dict, List +from typing import Union, Dict, List, Optional from cerberus import Validator from logbook import Logger @@ -158,7 +158,7 @@ class LitecordValidator(Validator): return isinstance(value, str) and (len(value) < 32) -def validate(reqjson: Union[Dict, List], schema: Dict, +def validate(reqjson: Optional[Union[Dict, List]], schema: Dict, raise_err: bool = True) -> Dict: """Validate the given user-given data against a schema, giving the "correct" version of the document, with all defaults applied. @@ -175,6 +175,9 @@ def validate(reqjson: Union[Dict, List], schema: Dict, """ validator = LitecordValidator(schema) + if reqjson is None: + raise BadRequest('No JSON provided') + try: valid = validator.validate(reqjson) except Exception: From 9b1f5cbb60075151e1ef5f9fae9cc071730a3194 Mon Sep 17 00:00:00 2001 From: Luna Date: Sun, 1 Sep 2019 15:30:21 -0300 Subject: [PATCH 5/5] channels: use a set for message_ids - channels: check result of db call on bulk delete --- litecord/blueprints/channels.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index d53cbeb..11c18a5 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -678,7 +678,7 @@ async def bulk_delete(channel_id: int): await channel_perm_check(user_id, channel_id, 'manage_messages') j = validate(await request.get_json(), BULK_DELETE) - message_ids = j['messages'] + message_ids = set(j['messages']) # as per discord behavior, if any id here is older than two weeks, # we must error. a cuter behavior would be returning the message ids @@ -700,12 +700,16 @@ async def bulk_delete(channel_id: int): if guild_id is None: payload.pop('guild_id') - await app.db.execute(""" + res = await app.db.execute(""" DELETE FROM messages WHERE channel_id = $1 - AND ARRAY[message_id] <@ $2::bigint[] - """, channel_id, message_ids) + AND ARRAY[id] <@ $2::bigint[] + """, channel_id, list(message_ids)) - await app.dispatcher.dispatch_channel('MESSAGE_DELETE_BULK', payload) + if res == 'DELETE 0': + raise BadRequest('No messages were removed') + + await app.dispatcher.dispatch( + 'channel', channel_id, 'MESSAGE_DELETE_BULK', payload) return '', 204