mirror of https://gitlab.com/litecord/litecord.git
blueprints.guild: split blueprint into channels, members, roles
This commit is contained in:
parent
86705b0645
commit
43b0482581
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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('/<int:guild>/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('/<int:guild_id>/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('/<int:guild_id>/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
|
||||
|
|
@ -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('/<int:guild_id>/members/<int:member_id>', 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('/<int:guild_id>/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('/<int:guild_id>/members/<int:member_id>', 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('/<int:guild_id>/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']
|
||||
|
|
@ -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('/<int:guild_id>/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('/<int:guild_id>/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('/<int:guild_id>/roles/<int:role_id>', 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)
|
||||
|
|
@ -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('/<int:guild_id>', 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('/<int:guild>/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('/<int:guild_id>/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('/<int:guild_id>/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('/<int:guild_id>/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('/<int:guild_id>/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('/<int:guild_id>/roles/<int:role_id>', 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('/<int:guild_id>/members/<int:member_id>', 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('/<int:guild_id>/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('/<int:guild_id>/members/<int:member_id>', 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('/<int:guild_id>/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."""
|
||||
|
|
|
|||
19
run.py
19
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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue