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 quart import current_app as app
|
||||||
|
|
||||||
from ..enums import ChannelType, GUILD_CHANS
|
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):
|
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')
|
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):
|
async def channel_check(user_id, channel_id):
|
||||||
"""Check if the current user is authorized
|
"""Check if the current user is authorized
|
||||||
to read the channel's information."""
|
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 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 ..auth import token_check
|
||||||
from ..snowflake import get_snowflake
|
from ..snowflake import get_snowflake
|
||||||
from ..enums import ChannelType
|
from ..enums import ChannelType
|
||||||
from ..errors import Forbidden, GuildNotFound, BadRequest
|
|
||||||
from ..schemas import (
|
from ..schemas import (
|
||||||
validate, GUILD_CREATE, GUILD_UPDATE, ROLE_CREATE, ROLE_UPDATE,
|
validate, GUILD_CREATE, GUILD_UPDATE
|
||||||
ROLE_UPDATE_POSITION
|
|
||||||
)
|
)
|
||||||
from ..utils import dict_get
|
from ..utils import dict_get
|
||||||
from .channels import channel_ack
|
from .channels import channel_ack
|
||||||
from .checks import guild_check
|
from .checks import guild_check, guild_owner_check
|
||||||
|
|
||||||
|
|
||||||
bp = Blueprint('guilds', __name__)
|
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):
|
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)
|
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):
|
async def guild_create_roles_prep(guild_id: int, roles: list):
|
||||||
"""Create roles in preparation in guild create."""
|
"""Create roles in preparation in guild create."""
|
||||||
# by reaching this point in the code that means
|
# 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):
|
async def guild_create_channels_prep(guild_id: int, channels: list):
|
||||||
"""Create channels pre-guild create"""
|
"""Create channels pre-guild create"""
|
||||||
for channel_raw in channels:
|
for channel_raw in channels:
|
||||||
|
|
@ -255,10 +151,10 @@ async def get_guild(guild_id):
|
||||||
@bp.route('/<int:guild_id>', methods=['UPDATE'])
|
@bp.route('/<int:guild_id>', methods=['UPDATE'])
|
||||||
async def update_guild(guild_id):
|
async def update_guild(guild_id):
|
||||||
user_id = await token_check()
|
user_id = await token_check()
|
||||||
await guild_check(user_id, guild_id)
|
|
||||||
j = validate(await request.get_json(), GUILD_UPDATE)
|
|
||||||
|
|
||||||
# TODO: check MANAGE_GUILD
|
# TODO: check MANAGE_GUILD
|
||||||
|
await guild_check(user_id, guild_id)
|
||||||
|
j = validate(await request.get_json(), GUILD_UPDATE)
|
||||||
|
|
||||||
if 'owner_id' in j:
|
if 'owner_id' in j:
|
||||||
await guild_owner_check(user_id, guild_id)
|
await guild_owner_check(user_id, guild_id)
|
||||||
|
|
@ -337,318 +233,6 @@ async def delete_guild(guild_id):
|
||||||
return '', 204
|
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):
|
async def remove_member(guild_id: int, member_id: int):
|
||||||
"""Do common tasks related to deleting a member from the guild,
|
"""Do common tasks related to deleting a member from the guild,
|
||||||
such as dispatching GUILD_DELETE and GUILD_MEMBER_REMOVE."""
|
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
|
from logbook.compat import redirect_logging
|
||||||
|
|
||||||
import config
|
import config
|
||||||
from litecord.blueprints import gateway, auth, users, guilds, channels, \
|
from litecord.blueprints import (
|
||||||
webhooks, science, voice, invites, relationships, dms
|
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.gateway import websocket_handler
|
||||||
from litecord.errors import LitecordError
|
from litecord.errors import LitecordError
|
||||||
from litecord.gateway.state_manager import StateManager
|
from litecord.gateway.state_manager import StateManager
|
||||||
|
|
@ -50,7 +60,12 @@ bps = {
|
||||||
auth: '/auth',
|
auth: '/auth',
|
||||||
users: '/users',
|
users: '/users',
|
||||||
relationships: '/users',
|
relationships: '/users',
|
||||||
|
|
||||||
guilds: '/guilds',
|
guilds: '/guilds',
|
||||||
|
guild_roles: '/guilds',
|
||||||
|
guild_members: '/guilds',
|
||||||
|
guild_channels: '/guilds',
|
||||||
|
|
||||||
channels: '/channels',
|
channels: '/channels',
|
||||||
webhooks: None,
|
webhooks: None,
|
||||||
science: None,
|
science: None,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue