From a67b6580ba10717b862d46d024227b7e1ec2d4cd Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 25 Oct 2019 13:33:52 -0300 Subject: [PATCH] make other blueprints use common, etc --- litecord/blueprints/admin_api/guilds.py | 2 +- litecord/blueprints/channel/reactions.py | 11 ++-- litecord/blueprints/channels.py | 51 ++-------------- litecord/blueprints/dms.py | 19 +----- litecord/blueprints/guild/channels.py | 68 +-------------------- litecord/blueprints/guild/mod.py | 41 +------------ litecord/blueprints/guild/roles.py | 77 ++---------------------- litecord/blueprints/users.py | 3 +- litecord/blueprints/webhooks.py | 2 +- litecord/errors.py | 4 ++ litecord/utils.py | 53 +++++++++++++++- 11 files changed, 78 insertions(+), 253 deletions(-) diff --git a/litecord/blueprints/admin_api/guilds.py b/litecord/blueprints/admin_api/guilds.py index 1cf792b..15f2647 100644 --- a/litecord/blueprints/admin_api/guilds.py +++ b/litecord/blueprints/admin_api/guilds.py @@ -22,7 +22,7 @@ from quart import Blueprint, jsonify, current_app as app, request from litecord.auth import admin_check from litecord.schemas import validate from litecord.admin_schemas import GUILD_UPDATE -from litecord.blueprints.guilds import delete_guild +from litecord.common.guilds import delete_guild from litecord.errors import GuildNotFound bp = Blueprint("guilds_admin", __name__) diff --git a/litecord/blueprints/channel/reactions.py b/litecord/blueprints/channel/reactions.py index 4c8dabd..ac1440f 100644 --- a/litecord/blueprints/channel/reactions.py +++ b/litecord/blueprints/channel/reactions.py @@ -23,10 +23,9 @@ from quart import Blueprint, request, current_app as app, jsonify from logbook import Logger -from litecord.utils import async_map +from litecord.utils import async_map, query_tuple_from_args, extract_limit from litecord.blueprints.auth import token_check from litecord.blueprints.checks import channel_check, channel_perm_check -from litecord.blueprints.channel.messages import query_tuple_from_args, extract_limit from litecord.enums import GUILD_CHANS @@ -165,7 +164,8 @@ def _emoji_sql_simple(emoji: str, param=4): return emoji_sql(emoji_type, emoji_id, emoji_name, param) -async def remove_reaction(channel_id: int, message_id: int, user_id: int, emoji: str): +async def _remove_reaction(channel_id: int, message_id: int, user_id: int, emoji: str): + """Remove given reaction from a message.""" ctype, guild_id = await channel_check(user_id, channel_id) emoji_type, emoji_id, emoji_name = emoji_info_from_str(emoji) @@ -201,8 +201,7 @@ async def remove_own_reaction(channel_id, message_id, emoji): """Remove a reaction.""" user_id = await token_check() - await remove_reaction(channel_id, message_id, user_id, emoji) - + await _remove_reaction(channel_id, message_id, user_id, emoji) return "", 204 @@ -212,7 +211,7 @@ async def remove_user_reaction(channel_id, message_id, emoji, other_id): user_id = await token_check() await channel_perm_check(user_id, channel_id, "manage_messages") - await remove_reaction(channel_id, message_id, other_id, emoji) + await _remove_reaction(channel_id, message_id, other_id, emoji) return "", 204 diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 98ec291..701c141 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -42,6 +42,7 @@ from litecord.blueprints.dm_channels import gdm_remove_recipient, gdm_destroy 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 +from litecord.common.channels import channel_ack log = Logger(__name__) bp = Blueprint("channels", __name__) @@ -136,7 +137,7 @@ async def _update_guild_chan_cat(guild_id: int, channel_id: int): await app.dispatcher.dispatch_guild(guild_id, "CHANNEL_UPDATE", child) -async def delete_messages(channel_id): +async def _delete_messages(channel_id): await app.db.execute( """ DELETE FROM channel_pins @@ -162,7 +163,7 @@ async def delete_messages(channel_id): ) -async def guild_cleanup(channel_id): +async def _guild_cleanup(channel_id): await app.db.execute( """ DELETE FROM channel_overwrites @@ -220,8 +221,8 @@ async def close_channel(channel_id): # didn't work on my setup, so I delete # everything before moving to the main # channel table deletes - await delete_messages(channel_id) - await guild_cleanup(channel_id) + await _delete_messages(channel_id) + await _guild_cleanup(channel_id) await app.db.execute( f""" @@ -595,48 +596,6 @@ async def trigger_typing(channel_id): return "", 204 -async def channel_ack(user_id, guild_id, channel_id, message_id: int = None): - """ACK a channel.""" - - if not message_id: - message_id = await app.storage.chan_last_message(channel_id) - - await app.db.execute( - """ - INSERT INTO user_read_state - (user_id, channel_id, last_message_id, mention_count) - VALUES - ($1, $2, $3, 0) - ON CONFLICT ON CONSTRAINT user_read_state_pkey - DO - UPDATE - SET last_message_id = $3, mention_count = 0 - WHERE user_read_state.user_id = $1 - AND user_read_state.channel_id = $2 - """, - user_id, - channel_id, - message_id, - ) - - if guild_id: - await app.dispatcher.dispatch_user_guild( - user_id, - guild_id, - "MESSAGE_ACK", - {"message_id": str(message_id), "channel_id": str(channel_id)}, - ) - else: - # we don't use ChannelDispatcher here because since - # guild_id is None, all user devices are already subscribed - # to the given channel (a dm or a group dm) - await app.dispatcher.dispatch_user( - user_id, - "MESSAGE_ACK", - {"message_id": str(message_id), "channel_id": str(channel_id)}, - ) - - @bp.route("//messages//ack", methods=["POST"]) async def ack_channel(channel_id, message_id): """Acknowledge a channel.""" diff --git a/litecord/blueprints/dms.py b/litecord/blueprints/dms.py index 975d7ea..2a50837 100644 --- a/litecord/blueprints/dms.py +++ b/litecord/blueprints/dms.py @@ -31,6 +31,7 @@ from ..snowflake import get_snowflake from .auth import token_check from litecord.blueprints.dm_channels import gdm_create, gdm_add_recipient +from litecord.common.channels import try_dm_state log = Logger(__name__) bp = Blueprint("dms", __name__) @@ -44,24 +45,6 @@ async def get_dms(): return jsonify(dms) -async def try_dm_state(user_id: int, dm_id: int): - """Try inserting the user into the dm state - for the given DM. - - Does not do anything if the user is already - in the dm state. - """ - await app.db.execute( - """ - INSERT INTO dm_channel_state (user_id, dm_id) - VALUES ($1, $2) - ON CONFLICT DO NOTHING - """, - user_id, - dm_id, - ) - - async def jsonify_dm(dm_id: int, user_id: int): dm_chan = await app.storage.get_dm(dm_id, user_id) return jsonify(dm_chan) diff --git a/litecord/blueprints/guild/channels.py b/litecord/blueprints/guild/channels.py index c8a2d2e..de20b95 100644 --- a/litecord/blueprints/guild/channels.py +++ b/litecord/blueprints/guild/channels.py @@ -27,77 +27,11 @@ from litecord.blueprints.guild.roles import gen_pairs from litecord.schemas import validate, ROLE_UPDATE_POSITION, CHAN_CREATE from litecord.blueprints.checks import guild_check, guild_owner_check, guild_perm_check - +from litecord.common.guilds import create_guild_channel bp = Blueprint("guild_channels", __name__) -async def _specific_chan_create(channel_id, ctype, **kwargs): - if ctype == ChannelType.GUILD_TEXT: - await app.db.execute( - """ - INSERT INTO guild_text_channels (id, topic) - VALUES ($1, $2) - """, - channel_id, - kwargs.get("topic", ""), - ) - elif ctype == ChannelType.GUILD_VOICE: - await app.db.execute( - """ - INSERT INTO guild_voice_channels (id, bitrate, user_limit) - VALUES ($1, $2, $3) - """, - channel_id, - kwargs.get("bitrate", 64), - kwargs.get("user_limit", 0), - ) - - -async def create_guild_channel( - guild_id: int, channel_id: int, ctype: ChannelType, **kwargs -): - """Create a channel in a guild.""" - await app.db.execute( - """ - INSERT INTO channels (id, channel_type) - VALUES ($1, $2) - """, - channel_id, - ctype.value, - ) - - # calc new pos - max_pos = await app.db.fetchval( - """ - SELECT MAX(position) - FROM guild_channels - WHERE guild_id = $1 - """, - guild_id, - ) - - # account for the first channel in a guild too - max_pos = max_pos or 0 - - # all channels go to guild_channels - await app.db.execute( - """ - INSERT INTO guild_channels (id, guild_id, name, position) - VALUES ($1, $2, $3, $4) - """, - channel_id, - guild_id, - kwargs["name"], - max_pos + 1, - ) - - # the rest of sql magic is dependant on the channel - # we're creating (a text or voice or category), - # so we use this function. - await _specific_chan_create(channel_id, ctype, **kwargs) - - @bp.route("//channels", methods=["GET"]) async def get_guild_channels(guild_id): """Get the list of channels in a guild.""" diff --git a/litecord/blueprints/guild/mod.py b/litecord/blueprints/guild/mod.py index 5949fd0..4032bb0 100644 --- a/litecord/blueprints/guild/mod.py +++ b/litecord/blueprints/guild/mod.py @@ -23,47 +23,11 @@ from litecord.blueprints.auth import token_check from litecord.blueprints.checks import guild_perm_check from litecord.schemas import validate, GUILD_PRUNE +from litecord.common.guilds import remove_member, remove_member_multi bp = Blueprint("guild_moderation", __name__) -async def remove_member(guild_id: int, member_id: int): - """Do common tasks related to deleting a member from the guild, - such as dispatching GUILD_DELETE and GUILD_MEMBER_REMOVE.""" - - await app.db.execute( - """ - DELETE FROM members - WHERE guild_id = $1 AND user_id = $2 - """, - guild_id, - member_id, - ) - - await app.dispatcher.dispatch_user_guild( - member_id, - guild_id, - "GUILD_DELETE", - {"guild_id": str(guild_id), "unavailable": False}, - ) - - await app.dispatcher.unsub("guild", guild_id, member_id) - - await app.dispatcher.dispatch("lazy_guild", guild_id, "remove_member", member_id) - - await app.dispatcher.dispatch_guild( - guild_id, - "GUILD_MEMBER_REMOVE", - {"guild_id": str(guild_id), "user": await app.storage.get_user(member_id)}, - ) - - -async def remove_member_multi(guild_id: int, members: list): - """Remove multiple members.""" - for member_id in members: - await remove_member(guild_id, member_id) - - @bp.route("//members/", methods=["DELETE"]) async def kick_guild_member(guild_id, member_id): """Remove a member from a guild.""" @@ -221,6 +185,5 @@ async def begin_guild_prune(guild_id): days = j["days"] member_ids = await get_prune(guild_id, days) - app.loop.create_task(remove_member_multi(guild_id, member_ids)) - + app.sched.spawn(remove_member_multi(guild_id, member_ids)) return jsonify({"pruned": len(member_ids)}) diff --git a/litecord/blueprints/guild/roles.py b/litecord/blueprints/guild/roles.py index 9516aa4..98440fa 100644 --- a/litecord/blueprints/guild/roles.py +++ b/litecord/blueprints/guild/roles.py @@ -27,11 +27,9 @@ from litecord.auth import token_check from litecord.blueprints.checks import guild_check, guild_perm_check from litecord.schemas import validate, ROLE_CREATE, ROLE_UPDATE, ROLE_UPDATE_POSITION -from litecord.snowflake import get_snowflake -from litecord.utils import dict_get -from litecord.permissions import get_role_perms +from litecord.utils import maybe_lazy_guild_dispatch +from litecord.common.guilds import create_role -DEFAULT_EVERYONE_PERMS = 104324161 log = Logger(__name__) bp = Blueprint("guild_roles", __name__) @@ -45,71 +43,6 @@ async def get_guild_roles(guild_id): return jsonify(await app.storage.get_role_data(guild_id)) -async def _maybe_lg(guild_id: int, event: str, role, force: bool = False): - # sometimes we want to dispatch an event - # even if the role isn't hoisted - - # an example of such a case is when a role loses - # its hoist status. - - # check if is a dict first because role_delete - # only receives the role id. - if isinstance(role, dict) and not role["hoist"] and not force: - return - - await app.dispatcher.dispatch("lazy_guild", guild_id, event, role) - - -async def create_role(guild_id, name: str, **kwargs): - """Create a role in a guild.""" - new_role_id = get_snowflake() - - everyone_perms = await get_role_perms(guild_id, guild_id) - default_perms = dict_get(kwargs, "default_perms", everyone_perms.binary) - - # update all roles so that we have space for pos 1, but without - # sending GUILD_ROLE_UPDATE for everyone - await app.db.execute( - """ - UPDATE roles - SET - position = position + 1 - WHERE guild_id = $1 - AND NOT (position = 0) - """, - guild_id, - ) - - await app.db.execute( - """ - INSERT INTO roles (id, guild_id, name, color, - hoist, position, permissions, managed, mentionable) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - """, - new_role_id, - guild_id, - name, - dict_get(kwargs, "color", 0), - dict_get(kwargs, "hoist", False), - # always set ourselves on position 1 - 1, - int(dict_get(kwargs, "permissions", default_perms)), - False, - dict_get(kwargs, "mentionable", False), - ) - - role = await app.storage.get_role(new_role_id, guild_id) - - # we need to update the lazy guild handlers for the newly created group - await _maybe_lg(guild_id, "new_role", role) - - await app.dispatcher.dispatch_guild( - guild_id, "GUILD_ROLE_CREATE", {"guild_id": str(guild_id), "role": role} - ) - - return role - - @bp.route("//roles", methods=["POST"]) async def create_guild_role(guild_id: int): """Add a role to a guild""" @@ -132,7 +65,7 @@ async def _role_update_dispatch(role_id: int, guild_id: int): """Dispatch a GUILD_ROLE_UPDATE with updated information on a role.""" role = await app.storage.get_role(role_id, guild_id) - await _maybe_lg(guild_id, "role_pos_upd", role) + await maybe_lazy_guild_dispatch(guild_id, "role_pos_upd", role) await app.dispatcher.dispatch_guild( guild_id, "GUILD_ROLE_UPDATE", {"guild_id": str(guild_id), "role": role} @@ -343,7 +276,7 @@ async def update_guild_role(guild_id, role_id): ) role = await _role_update_dispatch(role_id, guild_id) - await _maybe_lg(guild_id, "role_update", role, True) + await maybe_lazy_guild_dispatch(guild_id, "role_update", role, True) return jsonify(role) @@ -369,7 +302,7 @@ async def delete_guild_role(guild_id, role_id): if res == "DELETE 0": return "", 204 - await _maybe_lg(guild_id, "role_delete", role_id, True) + await maybe_lazy_guild_dispatch(guild_id, "role_delete", role_id, True) await app.dispatcher.dispatch_guild( guild_id, diff --git a/litecord/blueprints/users.py b/litecord/blueprints/users.py index 763f18e..667e143 100644 --- a/litecord/blueprints/users.py +++ b/litecord/blueprints/users.py @@ -28,7 +28,7 @@ from ..schemas import validate, USER_UPDATE, GET_MENTIONS from .guilds import guild_check from litecord.auth import token_check, hash_data, check_username_usage, roll_discrim -from litecord.blueprints.guild.mod import remove_member +from litecord.common.guilds import remove_member from litecord.enums import PremiumType from litecord.images import parse_data_uri @@ -319,7 +319,6 @@ async def leave_guild(guild_id: int): await guild_check(user_id, guild_id) await remove_member(guild_id, user_id) - return "", 204 diff --git a/litecord/blueprints/webhooks.py b/litecord/blueprints/webhooks.py index 47a1336..bb3668b 100644 --- a/litecord/blueprints/webhooks.py +++ b/litecord/blueprints/webhooks.py @@ -43,7 +43,7 @@ from litecord.snowflake import get_snowflake from litecord.utils import async_map from litecord.errors import WebhookNotFound, Unauthorized, ChannelNotFound, BadRequest -from litecord.blueprints.channel.messages import ( +from litecord.common.messages import ( msg_create_request, msg_create_check_content, msg_add_attachment, diff --git a/litecord/errors.py b/litecord/errors.py index d2a72c2..1789a5e 100644 --- a/litecord/errors.py +++ b/litecord/errors.py @@ -116,6 +116,10 @@ class Forbidden(LitecordError): status_code = 403 +class ForbiddenDM(Forbidden): + error_code = 50007 + + class NotFound(LitecordError): status_code = 404 diff --git a/litecord/utils.py b/litecord/utils.py index a0f5587..15b89d2 100644 --- a/litecord/utils.py +++ b/litecord/utils.py @@ -23,7 +23,9 @@ from typing import Any, Iterable, Optional, Sequence, List, Dict, Union from logbook import Logger from quart.json import JSONEncoder -from quart import current_app as app +from quart import current_app as app, request + +from .errors import BadRequest log = Logger(__name__) @@ -233,3 +235,52 @@ def maybe_int(val: Any) -> Union[int, Any]: return int(val) except (ValueError, TypeError): return val + + +async def maybe_lazy_guild_dispatch( + guild_id: int, event: str, role, force: bool = False +): + # sometimes we want to dispatch an event + # even if the role isn't hoisted + + # an example of such a case is when a role loses + # its hoist status. + + # check if is a dict first because role_delete + # only receives the role id. + if isinstance(role, dict) and not role["hoist"] and not force: + return + + await app.dispatcher.dispatch("lazy_guild", guild_id, event, role) + + +def extract_limit(request_, default: int = 50, max_val: int = 100): + """Extract a limit kwarg.""" + try: + limit = int(request_.args.get("limit", default)) + + if limit not in range(0, max_val + 1): + raise ValueError() + except (TypeError, ValueError): + raise BadRequest("limit not int") + + return limit + + +def query_tuple_from_args(args: dict, limit: int) -> tuple: + """Extract a 2-tuple out of request arguments.""" + before, after = None, None + + if "around" in request.args: + average = int(limit / 2) + around = int(args["around"]) + + after = around - average + before = around + average + + elif "before" in args: + before = int(args["before"]) + elif "after" in args: + before = int(args["after"]) + + return before, after