From f2d591367202adbeac2876995f08fe500748bcb3 Mon Sep 17 00:00:00 2001 From: Luna Mendes Date: Sun, 28 Oct 2018 17:29:43 -0300 Subject: [PATCH] blueprints: split channels to channel.messages bp --- litecord/blueprints/channel/__init__.py | 1 + litecord/blueprints/channel/messages.py | 212 ++++++++++++++++++++++++ litecord/blueprints/channels.py | 206 +---------------------- litecord/blueprints/guild/mod.py | 1 - run.py | 6 + 5 files changed, 223 insertions(+), 203 deletions(-) create mode 100644 litecord/blueprints/channel/__init__.py create mode 100644 litecord/blueprints/channel/messages.py diff --git a/litecord/blueprints/channel/__init__.py b/litecord/blueprints/channel/__init__.py new file mode 100644 index 0000000..999ed70 --- /dev/null +++ b/litecord/blueprints/channel/__init__.py @@ -0,0 +1 @@ +from .messages import bp as channel_messages diff --git a/litecord/blueprints/channel/messages.py b/litecord/blueprints/channel/messages.py new file mode 100644 index 0000000..bdcff92 --- /dev/null +++ b/litecord/blueprints/channel/messages.py @@ -0,0 +1,212 @@ +from quart import Blueprint, request, current_app as app, jsonify + +from logbook import Logger + + +from litecord.blueprints.auth import token_check +from litecord.blueprints.checks import channel_check +from litecord.blueprints.dms import try_dm_state +from litecord.errors import MessageNotFound, Forbidden +from litecord.enums import MessageType, ChannelType, GUILD_CHANS +from litecord.snowflake import get_snowflake +from litecord.schemas import validate, MESSAGE_CREATE + + +log = Logger(__name__) +bp = Blueprint('channel_messages', __name__) + + +@bp.route('//messages', methods=['GET']) +async def get_messages(channel_id): + user_id = await token_check() + await channel_check(user_id, channel_id) + + # TODO: before, after, around keys + + message_ids = await app.db.fetch(f""" + SELECT id + FROM messages + WHERE channel_id = $1 + ORDER BY id DESC + LIMIT 100 + """, channel_id) + + result = [] + + for message_id in message_ids: + msg = await app.storage.get_message(message_id['id']) + + if msg is None: + continue + + result.append(msg) + + log.info('Fetched {} messages', len(result)) + return jsonify(result) + + +@bp.route('//messages/', methods=['GET']) +async def get_single_message(channel_id, message_id): + user_id = await token_check() + await channel_check(user_id, channel_id) + + # TODO: check READ_MESSAGE_HISTORY permissions + message = await app.storage.get_message(message_id) + + if not message: + raise MessageNotFound() + + return jsonify(message) + + +async def _dm_pre_dispatch(channel_id, peer_id): + """Do some checks pre-MESSAGE_CREATE so we + make sure the receiving party will handle everything.""" + + # check the other party's dm_channel_state + + dm_state = await app.db.fetchval(""" + SELECT dm_id + FROM dm_channel_state + WHERE user_id = $1 AND dm_id = $2 + """, peer_id, channel_id) + + if dm_state: + # the peer already has the channel + # opened, so we don't need to do anything + return + + dm_chan = await app.storage.get_channel(channel_id) + + # dispatch CHANNEL_CREATE so the client knows which + # channel the future event is about + await app.dispatcher.dispatch_user(peer_id, 'CHANNEL_CREATE', dm_chan) + + # subscribe the peer to the channel + await app.dispatcher.sub('channel', channel_id, peer_id) + + # insert it on dm_channel_state so the client + # is subscribed on the future + await try_dm_state(peer_id, channel_id) + + +@bp.route('//messages', methods=['POST']) +async def create_message(channel_id): + user_id = await token_check() + ctype, guild_id = await channel_check(user_id, channel_id) + + j = validate(await request.get_json(), MESSAGE_CREATE) + message_id = get_snowflake() + + # TODO: check SEND_MESSAGES permission + # TODO: check connection to the gateway + + await app.db.execute( + """ + INSERT INTO messages (id, channel_id, author_id, content, tts, + mention_everyone, nonce, message_type) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + """, + message_id, + channel_id, + user_id, + j['content'], + + # TODO: check SEND_TTS_MESSAGES + j.get('tts', False), + + # TODO: check MENTION_EVERYONE permissions + '@everyone' in j['content'], + int(j.get('nonce', 0)), + MessageType.DEFAULT.value + ) + + payload = await app.storage.get_message(message_id) + + if ctype == ChannelType.DM: + # guild id here is the peer's ID. + await _dm_pre_dispatch(channel_id, guild_id) + + await app.dispatcher.dispatch('channel', channel_id, + 'MESSAGE_CREATE', payload) + + # TODO: dispatch the MESSAGE_CREATE to any mentioning user. + + if ctype == ChannelType.GUILD_TEXT: + for str_uid in payload['mentions']: + uid = int(str_uid) + + await app.db.execute(""" + UPDATE user_read_state + SET mention_count += 1 + WHERE user_id = $1 AND channel_id = $2 + """, uid, channel_id) + + return jsonify(payload) + + +@bp.route('//messages/', methods=['PATCH']) +async def edit_message(channel_id, message_id): + user_id = await token_check() + _ctype, guild_id = await channel_check(user_id, channel_id) + + author_id = await app.db.fetchval(""" + SELECT author_id FROM messages + WHERE messages.id = $1 + """, message_id) + + if not author_id == user_id: + raise Forbidden('You can not edit this message') + + j = await request.get_json() + updated = 'content' in j or 'embed' in j + + if 'content' in j: + await app.db.execute(""" + UPDATE messages + SET content=$1 + WHERE messages.id = $2 + """, j['content'], message_id) + + # TODO: update embed + + message = await app.storage.get_message(message_id) + + # only dispatch MESSAGE_UPDATE if we actually had any update to start with + if updated: + await app.dispatcher.dispatch('channel', channel_id, + 'MESSAGE_UPDATE', message) + + return jsonify(message) + + +@bp.route('//messages/', methods=['DELETE']) +async def delete_message(channel_id, message_id): + user_id = await token_check() + _ctype, guild_id = await channel_check(user_id, channel_id) + + author_id = await app.db.fetchval(""" + SELECT author_id FROM messages + WHERE messages.id = $1 + """, message_id) + + # TODO: MANAGE_MESSAGES permission check + if author_id != user_id: + raise Forbidden('You can not delete this message') + + await app.db.execute(""" + DELETE FROM messages + WHERE messages.id = $1 + """, message_id) + + await app.dispatcher.dispatch( + 'channel', channel_id, + 'MESSAGE_DELETE', { + 'id': str(message_id), + 'channel_id': str(channel_id), + + # for lazy guilds + 'guild_id': str(guild_id), + }) + + return '', 204 diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 2ab084f..e982007 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -4,13 +4,11 @@ from quart import Blueprint, request, current_app as app, jsonify from logbook import Logger from ..auth import token_check -from ..snowflake import get_snowflake, snowflake_datetime -from ..enums import ChannelType, MessageType, GUILD_CHANS -from ..errors import Forbidden, ChannelNotFound, MessageNotFound -from ..schemas import validate, MESSAGE_CREATE +from ..snowflake import snowflake_datetime +from ..enums import ChannelType, GUILD_CHANS +from ..errors import ChannelNotFound -from .checks import channel_check, guild_check -from .dms import try_dm_state +from .checks import channel_check log = Logger(__name__) bp = Blueprint('channels', __name__) @@ -215,202 +213,6 @@ async def close_channel(channel_id): return '', 404 -@bp.route('//messages', methods=['GET']) -async def get_messages(channel_id): - user_id = await token_check() - await channel_check(user_id, channel_id) - - # TODO: before, after, around keys - - message_ids = await app.db.fetch(f""" - SELECT id - FROM messages - WHERE channel_id = $1 - ORDER BY id DESC - LIMIT 100 - """, channel_id) - - result = [] - - for message_id in message_ids: - msg = await app.storage.get_message(message_id['id']) - - if msg is None: - continue - - result.append(msg) - - log.info('Fetched {} messages', len(result)) - return jsonify(result) - - -@bp.route('//messages/', methods=['GET']) -async def get_single_message(channel_id, message_id): - user_id = await token_check() - await channel_check(user_id, channel_id) - - # TODO: check READ_MESSAGE_HISTORY permissions - message = await app.storage.get_message(message_id) - - if not message: - raise MessageNotFound() - - return jsonify(message) - - -async def _dm_pre_dispatch(channel_id, peer_id): - """Do some checks pre-MESSAGE_CREATE so we - make sure the receiving party will handle everything.""" - - # check the other party's dm_channel_state - - dm_state = await app.db.fetchval(""" - SELECT dm_id - FROM dm_channel_state - WHERE user_id = $1 AND dm_id = $2 - """, peer_id, channel_id) - - if dm_state: - # the peer already has the channel - # opened, so we don't need to do anything - return - - dm_chan = await app.storage.get_channel(channel_id) - - # dispatch CHANNEL_CREATE so the client knows which - # channel the future event is about - await app.dispatcher.dispatch_user(peer_id, 'CHANNEL_CREATE', dm_chan) - - # subscribe the peer to the channel - await app.dispatcher.sub('channel', channel_id, peer_id) - - # insert it on dm_channel_state so the client - # is subscribed on the future - await try_dm_state(peer_id, channel_id) - - -@bp.route('//messages', methods=['POST']) -async def create_message(channel_id): - user_id = await token_check() - ctype, guild_id = await channel_check(user_id, channel_id) - - j = validate(await request.get_json(), MESSAGE_CREATE) - message_id = get_snowflake() - - # TODO: check SEND_MESSAGES permission - # TODO: check connection to the gateway - - await app.db.execute( - """ - INSERT INTO messages (id, channel_id, author_id, content, tts, - mention_everyone, nonce, message_type) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - """, - message_id, - channel_id, - user_id, - j['content'], - - # TODO: check SEND_TTS_MESSAGES - j.get('tts', False), - - # TODO: check MENTION_EVERYONE permissions - '@everyone' in j['content'], - int(j.get('nonce', 0)), - MessageType.DEFAULT.value - ) - - payload = await app.storage.get_message(message_id) - - if ctype == ChannelType.DM: - # guild id here is the peer's ID. - await _dm_pre_dispatch(channel_id, guild_id) - - await app.dispatcher.dispatch('channel', channel_id, - 'MESSAGE_CREATE', payload) - - # TODO: dispatch the MESSAGE_CREATE to any mentioning user. - - if ctype == ChannelType.GUILD_TEXT: - for str_uid in payload['mentions']: - uid = int(str_uid) - - await app.db.execute(""" - UPDATE user_read_state - SET mention_count += 1 - WHERE user_id = $1 AND channel_id = $2 - """, uid, channel_id) - - return jsonify(payload) - - -@bp.route('//messages/', methods=['PATCH']) -async def edit_message(channel_id, message_id): - user_id = await token_check() - _ctype, guild_id = await channel_check(user_id, channel_id) - - author_id = await app.db.fetchval(""" - SELECT author_id FROM messages - WHERE messages.id = $1 - """, message_id) - - if not author_id == user_id: - raise Forbidden('You can not edit this message') - - j = await request.get_json() - updated = 'content' in j or 'embed' in j - - if 'content' in j: - await app.db.execute(""" - UPDATE messages - SET content=$1 - WHERE messages.id = $2 - """, j['content'], message_id) - - # TODO: update embed - - message = await app.storage.get_message(message_id) - - # only dispatch MESSAGE_UPDATE if we actually had any update to start with - if updated: - await app.dispatcher.dispatch('channel', channel_id, - 'MESSAGE_UPDATE', message) - - return jsonify(message) - - -@bp.route('//messages/', methods=['DELETE']) -async def delete_message(channel_id, message_id): - user_id = await token_check() - _ctype, guild_id = await channel_check(user_id, channel_id) - - author_id = await app.db.fetchval(""" - SELECT author_id FROM messages - WHERE messages.id = $1 - """, message_id) - - # TODO: MANAGE_MESSAGES permission check - if author_id != user_id: - raise Forbidden('You can not delete this message') - - await app.db.execute(""" - DELETE FROM messages - WHERE messages.id = $1 - """, message_id) - - await app.dispatcher.dispatch( - 'channel', channel_id, - 'MESSAGE_DELETE', { - 'id': str(message_id), - 'channel_id': str(channel_id), - - # for lazy guilds - 'guild_id': str(guild_id), - }) - - return '', 204 - - @bp.route('//pins', methods=['GET']) async def get_pins(channel_id): user_id = await token_check() diff --git a/litecord/blueprints/guild/mod.py b/litecord/blueprints/guild/mod.py index 1c865af..0461da3 100644 --- a/litecord/blueprints/guild/mod.py +++ b/litecord/blueprints/guild/mod.py @@ -183,4 +183,3 @@ async def begin_guild_prune(guild_id): return jsonify({ 'pruned': len(member_ids) }) - diff --git a/run.py b/run.py index 2144937..1e5c936 100644 --- a/run.py +++ b/run.py @@ -22,6 +22,10 @@ from litecord.blueprints.guild import ( guild_roles, guild_members, guild_channels, guild_mod ) +from litecord.blueprints.channel import ( + channel_messages +) + from litecord.gateway import websocket_handler from litecord.errors import LitecordError from litecord.gateway.state_manager import StateManager @@ -68,6 +72,8 @@ bps = { guild_mod: '/guilds', channels: '/channels', + channel_messages: '/channels', + webhooks: None, science: None, voice: '/voice',