webhooks: add drafts for execution impl

- channel.messages: export msg_create_request,
    msg_create_check_payload, msg_add_attachment
 - schemas: add WEBHOOK_MESSAGE_CREATE
This commit is contained in:
Luna 2019-03-18 04:35:44 -03:00
parent 8360638356
commit 712f1d9ff2
3 changed files with 106 additions and 16 deletions

View File

@ -249,7 +249,7 @@ async def _guild_text_mentions(payload: dict, guild_id: int,
""", user_id, channel_id) """, user_id, channel_id)
async def _msg_input() -> tuple: async def msg_create_request() -> tuple:
"""Extract the json input and any file information """Extract the json input and any file information
the client gave to us in the request. the client gave to us in the request.
@ -277,26 +277,34 @@ async def _msg_input() -> tuple:
return json_from_form, [v for k, v in files.items()] return json_from_form, [v for k, v in files.items()]
def _check_content(payload: dict, files: list): def msg_create_check_content(payload: dict, files: list, *, use_embeds=False):
"""Check if there is actually any content being sent to us.""" """Check if there is actually any content being sent to us."""
has_content = bool(payload.get('content', '')) has_content = bool(payload.get('content', ''))
has_embed = 'embed' in payload
has_files = len(files) > 0 has_files = len(files) > 0
embed_field = 'embeds' if use_embeds else 'embed'
has_embed = embed_field in payload
has_total_content = has_content or has_embed or has_files has_total_content = has_content or has_embed or has_files
if not has_total_content: if not has_total_content:
raise BadRequest('No content has been provided.') raise BadRequest('No content has been provided.')
async def _add_attachment(message_id: int, channel_id: int, async def msg_add_attachment(message_id: int, channel_id: int,
attachment_file) -> int: attachment_file) -> int:
"""Add an attachment to a message. """Add an attachment to a message.
Parameters Parameters
---------- ----------
message_id: int message_id: int
The ID of the message getting the attachment. The ID of the message getting the attachment.
channel_id: int
The ID of the channel the message belongs to.
Exists because the attachment URL scheme contains
a channel id. The purpose is unknown, but we are
implementing Discord's behavior.
attachment_file: quart.FileStorage attachment_file: quart.FileStorage
quart FileStorage instance of the file. quart FileStorage instance of the file.
""" """
@ -362,10 +370,10 @@ async def _create_message(channel_id):
await channel_perm_check(user_id, channel_id, 'send_messages') await channel_perm_check(user_id, channel_id, 'send_messages')
actual_guild_id = guild_id actual_guild_id = guild_id
payload_json, files = await _msg_input() payload_json, files = await msg_create_request()
j = validate(payload_json, MESSAGE_CREATE) j = validate(payload_json, MESSAGE_CREATE)
_check_content(payload_json, files) msg_create_check_content(payload_json, files)
# TODO: check connection to the gateway # TODO: check connection to the gateway
@ -399,7 +407,7 @@ async def _create_message(channel_id):
# for each file given, we add it as an attachment # for each file given, we add it as an attachment
for pre_attachment in files: for pre_attachment in files:
await _add_attachment(message_id, channel_id, pre_attachment) await msg_add_attachment(message_id, channel_id, pre_attachment)
payload = await app.storage.get_message(message_id, user_id) payload = await app.storage.get_message(message_id, user_id)

View File

@ -27,12 +27,21 @@ from litecord.blueprints.checks import (
channel_check, channel_perm_check, guild_check, guild_perm_check channel_check, channel_perm_check, guild_check, guild_perm_check
) )
from litecord.schemas import validate, WEBHOOK_CREATE, WEBHOOK_UPDATE from litecord.schemas import (
validate, WEBHOOK_CREATE, WEBHOOK_UPDATE, WEBHOOK_MESSAGE_CREATE
)
from litecord.enums import ChannelType from litecord.enums import ChannelType
from litecord.snowflake import get_snowflake from litecord.snowflake import get_snowflake
from litecord.utils import async_map from litecord.utils import async_map
from litecord.errors import WebhookNotFound, Unauthorized from litecord.errors import WebhookNotFound, Unauthorized
from litecord.blueprints.channel.messages import (
msg_create_request, msg_create_check_content, msg_add_attachment,
# create_message
)
from litecord.embed.sanitizer import fill_embed
from litecord.embed.messages import process_url_embed
bp = Blueprint('webhooks', __name__) bp = Blueprint('webhooks', __name__)
@ -108,15 +117,17 @@ async def _webhook_many(where_clause, arg: int):
async def webhook_token_check(webhook_id: int, webhook_token: str): async def webhook_token_check(webhook_id: int, webhook_token: str):
"""token_check() equivalent for webhooks.""" """token_check() equivalent for webhooks."""
webhook_id_fetch = await app.db.fetchrow(""" row = await app.db.fetchrow("""
SELECT id SELECT guild_id, channel_id
FROM webhooks FROM webhooks
WHERE id = $1 AND token = $2 WHERE id = $1 AND token = $2
""", webhook_id, webhook_token) """, webhook_id, webhook_token)
if webhook_id_fetch is None: if row is None:
raise Unauthorized('webhook not found or unauthorized') raise Unauthorized('webhook not found or unauthorized')
return row['guild_id'], row['channel_id']
@bp.route('/channels/<int:channel_id>/webhooks', methods=['POST']) @bp.route('/channels/<int:channel_id>/webhooks', methods=['POST'])
async def create_webhook(channel_id: int): async def create_webhook(channel_id: int):
@ -259,17 +270,64 @@ async def del_webhook_tokened(webhook_id, webhook_token):
return '', 204 return '', 204
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>', methods=['POST']) async def create_message_webhook(guild_id, channel_id, webhook_id, j):
async def execute_webhook(webhook_id, webhook_token): # TODO: impl
pass pass
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>', methods=['POST'])
async def execute_webhook(webhook_id: int, webhook_token):
"""Execute a webhook. Sends a message to the channel the webhook
is tied to."""
guild_id, channel_id = await webhook_token_check(webhook_id, webhook_token)
# TODO: ensure channel_id points to guild text channel
payload_json, files = await msg_create_request()
j = validate(payload_json, WEBHOOK_MESSAGE_CREATE)
msg_create_check_content(j, files)
# webhooks don't need permissions.
mentions_everyone = '@everyone' in j['content']
mentions_here = '@here' in j['content']
message_id = await create_message_webhook(
guild_id, channel_id, webhook_id, {
'content': j.get('content', ''),
'tts': j.get('tts', False),
'everyone_mention': mentions_everyone or mentions_here,
'embeds': [await fill_embed(e) for e in j['embeds']]
}
)
for pre_attachment in files:
await msg_add_attachment(message_id, channel_id, pre_attachment)
payload = await app.storage.get_message(message_id)
# spawn embedder in the background, even when we're on a webhook.
app.sched.spawn(
process_url_embed(
app.config, app.storage, app.dispatcher, app.session,
payload
)
)
# TODO: is it really 204?
return '', 204
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>/slack', @bp.route('/webhooks/<int:webhook_id>/<webhook_token>/slack',
methods=['POST']) methods=['POST'])
async def execute_slack_webhook(webhook_id, webhook_token): async def execute_slack_webhook(webhook_id, webhook_token):
pass """Execute a webhook but expecting Slack data."""
# TODO: know slack webhooks
await webhook_token_check(webhook_id, webhook_token)
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>/github', methods=['POST']) @bp.route('/webhooks/<int:webhook_id>/<webhook_token>/github', methods=['POST'])
async def execute_github_webhook(webhook_id, webhook_token): async def execute_github_webhook(webhook_id, webhook_token):
pass """Execute a webhook but expecting GitHub data."""
# TODO: know github webhooks
await webhook_token_check(webhook_id, webhook_token)

View File

@ -697,3 +697,27 @@ WEBHOOK_UPDATE = {
'avatar': {'type': 'b64_icon', 'required': False, 'nullable': False}, 'avatar': {'type': 'b64_icon', 'required': False, 'nullable': False},
'channel_id': {'coerce': int, 'required': False, 'nullable': False} 'channel_id': {'coerce': int, 'required': False, 'nullable': False}
} }
WEBHOOK_MESSAGE_CREATE = {
'content': {
'type': 'string',
'minlength': 0, 'maxlength': 2000, 'required': False
},
'tts': {'type': 'boolean', 'required': False},
'username': {
'type': 'string',
'minlength': 2, 'maxlength': 32, 'required': False
},
# TODO: url type, or something...
'avatar_url': {
'type': 'url', 'required': False
},
'embeds': {
'type': list,
'required': False,
'schema': {'type': 'dict', 'schema': EMBED_OBJECT}
}
}