From 43b0482581d2a22016155e00b797217df3bded2a Mon Sep 17 00:00:00 2001 From: Luna Mendes Date: Sat, 27 Oct 2018 02:04:47 -0300 Subject: [PATCH] blueprints.guild: split blueprint into channels, members, roles --- litecord/blueprints/checks.py | 17 +- litecord/blueprints/guild/__init__.py | 3 + litecord/blueprints/guild/channels.py | 111 +++++++ litecord/blueprints/guild/members.py | 113 +++++++ litecord/blueprints/guild/roles.py | 222 +++++++++++++ litecord/blueprints/guilds.py | 436 +------------------------- run.py | 19 +- 7 files changed, 492 insertions(+), 429 deletions(-) create mode 100644 litecord/blueprints/guild/__init__.py create mode 100644 litecord/blueprints/guild/channels.py create mode 100644 litecord/blueprints/guild/members.py create mode 100644 litecord/blueprints/guild/roles.py diff --git a/litecord/blueprints/checks.py b/litecord/blueprints/checks.py index 5cfc225..17bd337 100644 --- a/litecord/blueprints/checks.py +++ b/litecord/blueprints/checks.py @@ -1,7 +1,7 @@ from quart import current_app as app from ..enums import ChannelType, GUILD_CHANS -from ..errors import GuildNotFound, ChannelNotFound +from ..errors import GuildNotFound, ChannelNotFound, Forbidden async def guild_check(user_id: int, guild_id: int): @@ -16,6 +16,21 @@ async def guild_check(user_id: int, guild_id: int): raise GuildNotFound('guild not found') +async def guild_owner_check(user_id: int, guild_id: int): + """Check if a user is the owner of the guild.""" + owner_id = await app.db.fetchval(""" + SELECT owner_id + FROM guilds + WHERE guilds.id = $1 + """, guild_id) + + if not owner_id: + raise GuildNotFound() + + if user_id != owner_id: + raise Forbidden('You are not the owner of the guild') + + async def channel_check(user_id, channel_id): """Check if the current user is authorized to read the channel's information.""" diff --git a/litecord/blueprints/guild/__init__.py b/litecord/blueprints/guild/__init__.py new file mode 100644 index 0000000..f4f8356 --- /dev/null +++ b/litecord/blueprints/guild/__init__.py @@ -0,0 +1,3 @@ +from .roles import bp as guild_roles +from .members import bp as guild_members +from .channels import bp as guild_channels diff --git a/litecord/blueprints/guild/channels.py b/litecord/blueprints/guild/channels.py new file mode 100644 index 0000000..f8c5132 --- /dev/null +++ b/litecord/blueprints/guild/channels.py @@ -0,0 +1,111 @@ +from quart import Blueprint, request, current_app as app, jsonify + +from litecord.blueprints.auth import token_check +from litecord.blueprints.checks import guild_check, guild_owner_check +from litecord.snowflake import get_snowflake +from litecord.errors import BadRequest +from litecord.enums import ChannelType +# from litecord.schemas import ( +# validate, CHAN_UPDATE_POSITION +# ) + + +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.""" + user_id = await token_check() + await guild_check(user_id, guild_id) + + return jsonify( + await app.storage.get_channel_data(guild_id)) + + +@bp.route('//channels', methods=['POST']) +async def create_channel(guild_id): + """Create a channel in a guild.""" + user_id = await token_check() + j = await request.get_json() + + # TODO: check permissions for MANAGE_CHANNELS + await guild_check(user_id, guild_id) + + channel_type = j.get('type', ChannelType.GUILD_TEXT) + channel_type = ChannelType(channel_type) + + if channel_type not in (ChannelType.GUILD_TEXT, + ChannelType.GUILD_VOICE): + raise BadRequest('Invalid channel type') + + new_channel_id = get_snowflake() + await create_guild_channel( + guild_id, new_channel_id, channel_type, **j) + + chan = await app.storage.get_channel(new_channel_id) + await app.dispatcher.dispatch_guild( + guild_id, 'CHANNEL_CREATE', chan) + return jsonify(chan) + + +@bp.route('//channels', methods=['PATCH']) +async def modify_channel_pos(guild_id): + user_id = await token_check() + + # TODO: check MANAGE_CHANNELS + await guild_owner_check(user_id, guild_id) + + # TODO: this route + # raw_j = await request.get_json() + # j = validate({'channels': raw_j}, CHAN_UPDATE_POSITION) + + raise NotImplementedError diff --git a/litecord/blueprints/guild/members.py b/litecord/blueprints/guild/members.py new file mode 100644 index 0000000..6fd2ad2 --- /dev/null +++ b/litecord/blueprints/guild/members.py @@ -0,0 +1,113 @@ +from quart import Blueprint, request, current_app as app, jsonify + +from litecord.blueprints.auth import token_check +from litecord.blueprints.checks import guild_check +from litecord.errors import BadRequest + + +bp = Blueprint('guild_members', __name__) + + +@bp.route('//members/', methods=['GET']) +async def get_guild_member(guild_id, member_id): + """Get a member's information in a guild.""" + user_id = await token_check() + await guild_check(user_id, guild_id) + member = await app.storage.get_single_member(guild_id, member_id) + return jsonify(member) + + +@bp.route('//members', methods=['GET']) +async def get_members(guild_id): + """Get members inside a guild.""" + user_id = await token_check() + await guild_check(user_id, guild_id) + + j = await request.get_json() + + limit, after = int(j.get('limit', 1)), j.get('after', 0) + + if limit < 1 or limit > 1000: + raise BadRequest('limit not in 1-1000 range') + + user_ids = await app.db.fetch(f""" + SELECT user_id + WHERE guild_id = $1, user_id > $2 + LIMIT {limit} + ORDER BY user_id ASC + """, guild_id, after) + + user_ids = [r[0] for r in user_ids] + members = await app.storage.get_member_multi(guild_id, user_ids) + return jsonify(members) + + +@bp.route('//members/', methods=['PATCH']) +async def modify_guild_member(guild_id, member_id): + """Modify a members' information in a guild.""" + j = await request.get_json() + + if 'nick' in j: + # TODO: check MANAGE_NICKNAMES + + await app.db.execute(""" + UPDATE members + SET nickname = $1 + WHERE user_id = $2 AND guild_id = $3 + """, j['nick'], member_id, guild_id) + + if 'mute' in j: + # TODO: check MUTE_MEMBERS + + await app.db.execute(""" + UPDATE members + SET muted = $1 + WHERE user_id = $2 AND guild_id = $3 + """, j['mute'], member_id, guild_id) + + if 'deaf' in j: + # TODO: check DEAFEN_MEMBERS + + await app.db.execute(""" + UPDATE members + SET deafened = $1 + WHERE user_id = $2 AND guild_id = $3 + """, j['deaf'], member_id, guild_id) + + if 'channel_id' in j: + # TODO: check MOVE_MEMBERS + # TODO: change the member's voice channel + pass + + member = await app.storage.get_member_data_one(guild_id, member_id) + member.pop('joined_at') + + await app.dispatcher.dispatch_guild(guild_id, 'GUILD_MEMBER_UPDATE', {**{ + 'guild_id': str(guild_id) + }, **member}) + + return '', 204 + + +@bp.route('//members/@me/nick', methods=['PATCH']) +async def update_nickname(guild_id): + """Update a member's nickname in a guild.""" + user_id = await token_check() + await guild_check(user_id, guild_id) + + j = await request.get_json() + + await app.db.execute(""" + UPDATE members + SET nickname = $1 + WHERE user_id = $2 AND guild_id = $3 + """, j['nick'], user_id, guild_id) + + member = await app.storage.get_member_data_one(guild_id, user_id) + member.pop('joined_at') + + await app.dispatcher.dispatch_guild(guild_id, 'GUILD_MEMBER_UPDATE', {**{ + 'guild_id': str(guild_id) + }, **member}) + + return j['nick'] diff --git a/litecord/blueprints/guild/roles.py b/litecord/blueprints/guild/roles.py new file mode 100644 index 0000000..d83b33f --- /dev/null +++ b/litecord/blueprints/guild/roles.py @@ -0,0 +1,222 @@ +from quart import Blueprint, request, current_app as app, jsonify + +from litecord.auth import token_check + +# from litecord.blueprints.checks import guild_check +from litecord.blueprints.checks import guild_owner_check +from litecord.snowflake import get_snowflake +from litecord.utils import dict_get + +from litecord.schemas import ( + validate, ROLE_CREATE, ROLE_UPDATE, ROLE_UPDATE_POSITION +) + +DEFAULT_EVERYONE_PERMS = 104324161 +bp = Blueprint('guild_roles', __name__) + + +async def create_role(guild_id, name: str, **kwargs): + """Create a role in a guild.""" + new_role_id = get_snowflake() + + # TODO: use @everyone's perm number + default_perms = dict_get(kwargs, 'default_perms', DEFAULT_EVERYONE_PERMS) + + max_pos = await app.db.fetchval(""" + SELECT MAX(position) + FROM roles + WHERE guild_id = $1 + """, 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), + + # set position = 0 when there isn't any + # other role (when we're creating the + # @everyone role) + max_pos + 1 if max_pos is not None else 0, + int(dict_get(kwargs, 'permissions', default_perms)), + False, + dict_get(kwargs, 'mentionable', False) + ) + + role = await app.storage.get_role(new_role_id, guild_id) + 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""" + user_id = await token_check() + + # TODO: use check_guild and MANAGE_ROLES permission + await guild_owner_check(user_id, guild_id) + + # client can just send null + j = validate(await request.get_json() or {}, ROLE_CREATE) + + role_name = j['name'] + j.pop('name') + + role = await create_role(guild_id, role_name, **j) + + return jsonify(role) + + +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 app.dispatcher.dispatch_guild(guild_id, 'GUILD_ROLE_UPDATE', { + 'guild_id': str(guild_id), + 'role': role, + }) + + return role + + +async def _role_pairs_update(guild_id: int, pairs: list): + """Update the roles' positions. + + Dispatches GUILD_ROLE_UPDATE for all roles being updated. + """ + for pair in pairs: + pair_1, pair_2 = pair + + role_1, new_pos_1 = pair_1 + role_2, new_pos_2 = pair_2 + + conn = await app.db.acquire() + async with conn.transaction(): + # update happens in a transaction + # so we don't fuck it up + await conn.execute(""" + UPDATE roles + SET position = $1 + WHERE roles.id = $2 + """, new_pos_1, role_1) + + await conn.execute(""" + UPDATE roles + SET position = $1 + WHERE roles.id = $2 + """, new_pos_2, role_2) + + await app.db.release(conn) + + # the route fires multiple Guild Role Update. + await _role_update_dispatch(role_1, guild_id) + await _role_update_dispatch(role_2, guild_id) + + +@bp.route('//roles', methods=['PATCH']) +async def update_guild_role_positions(guild_id): + """Update the positions for a bunch of roles.""" + user_id = await token_check() + + # TODO: check MANAGE_ROLES + await guild_owner_check(user_id, guild_id) + + raw_j = await request.get_json() + + # we need to do this hackiness because thats + # cerberus for ya. + j = validate({'roles': raw_j}, ROLE_UPDATE_POSITION) + + # extract the list out + j = j['roles'] + print(j) + + all_roles = await app.storage.get_role_data(guild_id) + + # we'll have to calculate pairs of changing roles, + # then do the changes, etc. + roles_pos = {role['position']: int(role['id']) for role in all_roles} + new_positions = {role['id']: role['position'] for role in j} + + # always ignore people trying to change the @everyone role + # TODO: check if the user can even change the roles in the first place, + # preferrably when we have a proper perms system. + try: + new_positions.pop(guild_id) + except KeyError: + pass + + pairs = [] + + # we want to find pairs of (role_1, new_position_1) + # where new_position_1 is actually pointing to position_2 (for a role 2) + # AND we have (role_2, new_position_2) in the list of new_positions. + + # I hope the explanation went through. + + for change in j: + role_1, new_pos_1 = change['id'], change['position'] + + # check current pairs + # so we don't repeat a role + flag = False + + for pair in pairs: + if (role_1, new_pos_1) in pair: + flag = True + + # skip if found + if flag: + continue + + # find a role that is in that new position + role_2 = roles_pos.get(new_pos_1) + + # search role_2 in the new_positions list + new_pos_2 = new_positions.get(role_2) + + # if we found it, add it to the pairs array. + if new_pos_2: + pairs.append( + ((role_1, new_pos_1), (role_2, new_pos_2)) + ) + + await _role_pairs_update(guild_id, pairs) + + # return the list of all roles back + return jsonify(await app.storage.get_role_data(guild_id)) + + +@bp.route('//roles/', methods=['PATCH']) +async def update_guild_role(guild_id, role_id): + """Update a single role's information.""" + user_id = await token_check() + + # TODO: check MANAGE_ROLES + await guild_owner_check(user_id, guild_id) + + j = validate(await request.get_json(), ROLE_UPDATE) + + # we only update ints on the db, not Permissions + j['permissions'] = int(j['permissions']) + + for field in j: + await app.db.execute(f""" + UPDATE roles + SET {field} = $1 + WHERE roles.id = $2 AND roles.guild_id = $3 + """, j[field], role_id, guild_id) + + role = await _role_update_dispatch(role_id, guild_id) + return jsonify(role) diff --git a/litecord/blueprints/guilds.py b/litecord/blueprints/guilds.py index 90b359b..23ee91c 100644 --- a/litecord/blueprints/guilds.py +++ b/litecord/blueprints/guilds.py @@ -1,34 +1,22 @@ from quart import Blueprint, request, current_app as app, jsonify +from litecord.blueprints.guild.channels import create_guild_channel +from litecord.blueprints.guild.roles import ( + create_role, DEFAULT_EVERYONE_PERMS +) + from ..auth import token_check from ..snowflake import get_snowflake from ..enums import ChannelType -from ..errors import Forbidden, GuildNotFound, BadRequest from ..schemas import ( - validate, GUILD_CREATE, GUILD_UPDATE, ROLE_CREATE, ROLE_UPDATE, - ROLE_UPDATE_POSITION + validate, GUILD_CREATE, GUILD_UPDATE ) from ..utils import dict_get from .channels import channel_ack -from .checks import guild_check +from .checks import guild_check, guild_owner_check + bp = Blueprint('guilds', __name__) -DEFAULT_EVERYONE_PERMS = 104324161 - - -async def guild_owner_check(user_id: int, guild_id: int): - """Check if a user is the owner of the guild.""" - owner_id = await app.db.fetchval(""" - SELECT owner_id - FROM guilds - WHERE guilds.id = $1 - """, guild_id) - - if not owner_id: - raise GuildNotFound() - - if user_id != owner_id: - raise Forbidden('You are not the owner of the guild') async def create_guild_settings(guild_id: int, user_id: int): @@ -63,50 +51,6 @@ async def add_member(guild_id: int, user_id: int): await create_guild_settings(guild_id, user_id) -async def create_role(guild_id, name: str, **kwargs): - """Create a role in a guild.""" - new_role_id = get_snowflake() - - # TODO: use @everyone's perm number - default_perms = dict_get(kwargs, 'default_perms', DEFAULT_EVERYONE_PERMS) - - max_pos = await app.db.fetchval(""" - SELECT MAX(position) - FROM roles - WHERE guild_id = $1 - """, 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), - - # set position = 0 when there isn't any - # other role (when we're creating the - # @everyone role) - max_pos + 1 if max_pos is not None else 0, - int(dict_get(kwargs, 'permissions', default_perms)), - False, - dict_get(kwargs, 'mentionable', False) - ) - - role = await app.storage.get_role(new_role_id, guild_id) - await app.dispatcher.dispatch_guild( - guild_id, 'GUILD_ROLE_CREATE', { - 'guild_id': str(guild_id), - 'role': role, - }) - - return role - - async def guild_create_roles_prep(guild_id: int, roles: list): """Create roles in preparation in guild create.""" # by reaching this point in the code that means @@ -134,54 +78,6 @@ async def guild_create_roles_prep(guild_id: int, roles: list): ) -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) - - async def guild_create_channels_prep(guild_id: int, channels: list): """Create channels pre-guild create""" for channel_raw in channels: @@ -255,10 +151,10 @@ async def get_guild(guild_id): @bp.route('/', methods=['UPDATE']) async def update_guild(guild_id): user_id = await token_check() - await guild_check(user_id, guild_id) - j = validate(await request.get_json(), GUILD_UPDATE) # TODO: check MANAGE_GUILD + await guild_check(user_id, guild_id) + j = validate(await request.get_json(), GUILD_UPDATE) if 'owner_id' in j: await guild_owner_check(user_id, guild_id) @@ -337,318 +233,6 @@ async def delete_guild(guild_id): return '', 204 -@bp.route('//channels', methods=['GET']) -async def get_guild_channels(guild_id): - user_id = await token_check() - await guild_check(user_id, guild_id) - - channels = await app.storage.get_channel_data(guild_id) - return jsonify(channels) - - -@bp.route('//channels', methods=['POST']) -async def create_channel(guild_id): - user_id = await token_check() - j = await request.get_json() - - # TODO: check permissions for MANAGE_CHANNELS - await guild_check(user_id, guild_id) - - channel_type = j.get('type', ChannelType.GUILD_TEXT) - channel_type = ChannelType(channel_type) - - if channel_type not in (ChannelType.GUILD_TEXT, - ChannelType.GUILD_VOICE): - raise BadRequest('Invalid channel type') - - new_channel_id = get_snowflake() - await create_guild_channel(guild_id, new_channel_id, channel_type,) - - chan = await app.storage.get_channel(new_channel_id) - await app.dispatcher.dispatch_guild( - guild_id, 'CHANNEL_CREATE', chan) - return jsonify(chan) - - -@bp.route('//channels', methods=['PATCH']) -async def modify_channel_pos(guild_id): - user_id = await token_check() - await guild_check(user_id, guild_id) - await request.get_json() - - # TODO: this route - - raise NotImplementedError - - -@bp.route('//roles', methods=['POST']) -async def create_guild_role(guild_id: int): - """Add a role to a guild""" - user_id = await token_check() - - # TODO: use check_guild and MANAGE_ROLES permission - await guild_owner_check(user_id, guild_id) - - # client can just send null - j = validate(await request.get_json() or {}, ROLE_CREATE) - - role_name = j['name'] - j.pop('name') - - role = await create_role(guild_id, role_name, **j) - - return jsonify(role) - - -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 app.dispatcher.dispatch_guild(guild_id, 'GUILD_ROLE_UPDATE', { - 'guild_id': str(guild_id), - 'role': role, - }) - - return role - - -async def _role_pairs_update(guild_id: int, pairs: list): - """Update the roles' positions. - - Dispatches GUILD_ROLE_UPDATE for all roles being updated. - """ - for pair in pairs: - pair_1, pair_2 = pair - - role_1, new_pos_1 = pair_1 - role_2, new_pos_2 = pair_2 - - conn = await app.db.acquire() - async with conn.transaction(): - # update happens in a transaction - # so we don't fuck it up - await conn.execute(""" - UPDATE roles - SET position = $1 - WHERE roles.id = $2 - """, new_pos_1, role_1) - - await conn.execute(""" - UPDATE roles - SET position = $1 - WHERE roles.id = $2 - """, new_pos_2, role_2) - - await app.db.release(conn) - - # the route fires multiple Guild Role Update. - await _role_update_dispatch(role_1, guild_id) - await _role_update_dispatch(role_2, guild_id) - - -@bp.route('//roles', methods=['PATCH']) -async def update_guild_role_positions(guild_id): - """Update the positions for a bunch of roles.""" - user_id = await token_check() - - # TODO: check MANAGE_ROLES - await guild_owner_check(user_id, guild_id) - - raw_j = await request.get_json() - - # we need to do this hackiness because thats - # cerberus for ya. - j = validate({'roles': raw_j}, ROLE_UPDATE_POSITION) - - # extract the list out - j = j['roles'] - print(j) - - all_roles = await app.storage.get_role_data(guild_id) - - # we'll have to calculate pairs of changing roles, - # then do the changes, etc. - roles_pos = {role['position']: int(role['id']) for role in all_roles} - new_positions = {role['id']: role['position'] for role in j} - - # always ignore people trying to change the @everyone role - # TODO: check if the user can even change the roles in the first place, - # preferrably when we have a proper perms system. - try: - new_positions.pop(guild_id) - except KeyError: - pass - - pairs = [] - - # we want to find pairs of (role_1, new_position_1) - # where new_position_1 is actually pointing to position_2 (for a role 2) - # AND we have (role_2, new_position_2) in the list of new_positions. - - # I hope the explanation went through. - - for change in j: - role_1, new_pos_1 = change['id'], change['position'] - - # check current pairs - # so we don't repeat a role - flag = False - - for pair in pairs: - if (role_1, new_pos_1) in pair: - flag = True - - # skip if found - if flag: - continue - - # find a role that is in that new position - role_2 = roles_pos.get(new_pos_1) - - # search role_2 in the new_positions list - new_pos_2 = new_positions.get(role_2) - - # if we found it, add it to the pairs array. - if new_pos_2: - pairs.append( - ((role_1, new_pos_1), (role_2, new_pos_2)) - ) - - await _role_pairs_update(guild_id, pairs) - - # return the list of all roles back - return jsonify(await app.storage.get_role_data(guild_id)) - - -@bp.route('//roles/', methods=['PATCH']) -async def update_guild_role(guild_id, role_id): - """Update a single role's information.""" - user_id = await token_check() - - # TODO: check MANAGE_ROLES - await guild_owner_check(user_id, guild_id) - - j = validate(await request.get_json(), ROLE_UPDATE) - - # we only update ints on the db, not Permissions - j['permissions'] = int(j['permissions']) - - for field in j: - await app.db.execute(f""" - UPDATE roles - SET {field} = $1 - WHERE roles.id = $2 AND roles.guild_id = $3 - """, j[field], role_id, guild_id) - - role = await _role_update_dispatch(role_id, guild_id) - return jsonify(role) - - -@bp.route('//members/', methods=['GET']) -async def get_guild_member(guild_id, member_id): - """Get a member's information in a guild.""" - user_id = await token_check() - await guild_check(user_id, guild_id) - member = await app.storage.get_single_member(guild_id, member_id) - return jsonify(member) - - -@bp.route('//members', methods=['GET']) -async def get_members(guild_id): - """Get members inside a guild.""" - user_id = await token_check() - await guild_check(user_id, guild_id) - - j = await request.get_json() - - limit, after = int(j.get('limit', 1)), j.get('after', 0) - - if limit < 1 or limit > 1000: - raise BadRequest('limit not in 1-1000 range') - - user_ids = await app.db.fetch(f""" - SELECT user_id - WHERE guild_id = $1, user_id > $2 - LIMIT {limit} - ORDER BY user_id ASC - """, guild_id, after) - - user_ids = [r[0] for r in user_ids] - members = await app.storage.get_member_multi(guild_id, user_ids) - return jsonify(members) - - -@bp.route('//members/', methods=['PATCH']) -async def modify_guild_member(guild_id, member_id): - """Modify a members' information in a guild.""" - j = await request.get_json() - - if 'nick' in j: - # TODO: check MANAGE_NICKNAMES - - await app.db.execute(""" - UPDATE members - SET nickname = $1 - WHERE user_id = $2 AND guild_id = $3 - """, j['nick'], member_id, guild_id) - - if 'mute' in j: - # TODO: check MUTE_MEMBERS - - await app.db.execute(""" - UPDATE members - SET muted = $1 - WHERE user_id = $2 AND guild_id = $3 - """, j['mute'], member_id, guild_id) - - if 'deaf' in j: - # TODO: check DEAFEN_MEMBERS - - await app.db.execute(""" - UPDATE members - SET deafened = $1 - WHERE user_id = $2 AND guild_id = $3 - """, j['deaf'], member_id, guild_id) - - if 'channel_id' in j: - # TODO: check MOVE_MEMBERS - # TODO: change the member's voice channel - pass - - member = await app.storage.get_member_data_one(guild_id, member_id) - member.pop('joined_at') - - await app.dispatcher.dispatch_guild(guild_id, 'GUILD_MEMBER_UPDATE', {**{ - 'guild_id': str(guild_id) - }, **member}) - - return '', 204 - - -@bp.route('//members/@me/nick', methods=['PATCH']) -async def update_nickname(guild_id): - """Update a member's nickname in a guild.""" - user_id = await token_check() - await guild_check(user_id, guild_id) - - j = await request.get_json() - - await app.db.execute(""" - UPDATE members - SET nickname = $1 - WHERE user_id = $2 AND guild_id = $3 - """, j['nick'], user_id, guild_id) - - member = await app.storage.get_member_data_one(guild_id, user_id) - member.pop('joined_at') - - await app.dispatcher.dispatch_guild(guild_id, 'GUILD_MEMBER_UPDATE', {**{ - 'guild_id': str(guild_id) - }, **member}) - - return j['nick'] - - 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.""" diff --git a/run.py b/run.py index 98f674d..78ebb09 100644 --- a/run.py +++ b/run.py @@ -10,8 +10,18 @@ from logbook import StreamHandler, Logger from logbook.compat import redirect_logging import config -from litecord.blueprints import gateway, auth, users, guilds, channels, \ - webhooks, science, voice, invites, relationships, dms +from litecord.blueprints import ( + gateway, auth, users, guilds, channels, webhooks, science, + voice, invites, relationships, dms +) + +# those blueprints are separated from the "main" ones +# for code readability if people want to dig through +# the codebase. +from litecord.blueprints.guild import ( + guild_roles, guild_members, guild_channels +) + from litecord.gateway import websocket_handler from litecord.errors import LitecordError from litecord.gateway.state_manager import StateManager @@ -50,7 +60,12 @@ bps = { auth: '/auth', users: '/users', relationships: '/users', + guilds: '/guilds', + guild_roles: '/guilds', + guild_members: '/guilds', + guild_channels: '/guilds', + channels: '/channels', webhooks: None, science: None,