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)
async def _msg_input() -> tuple:
async def msg_create_request() -> tuple:
"""Extract the json input and any file information
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()]
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."""
has_content = bool(payload.get('content', ''))
has_embed = 'embed' in payload
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
if not has_total_content:
raise BadRequest('No content has been provided.')
async def _add_attachment(message_id: int, channel_id: int,
attachment_file) -> int:
async def msg_add_attachment(message_id: int, channel_id: int,
attachment_file) -> int:
"""Add an attachment to a message.
Parameters
----------
message_id: int
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
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')
actual_guild_id = guild_id
payload_json, files = await _msg_input()
payload_json, files = await msg_create_request()
j = validate(payload_json, MESSAGE_CREATE)
_check_content(payload_json, files)
msg_create_check_content(payload_json, files)
# 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 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)

View File

@ -27,12 +27,21 @@ from litecord.blueprints.checks import (
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.snowflake import get_snowflake
from litecord.utils import async_map
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__)
@ -108,15 +117,17 @@ async def _webhook_many(where_clause, arg: int):
async def webhook_token_check(webhook_id: int, webhook_token: str):
"""token_check() equivalent for webhooks."""
webhook_id_fetch = await app.db.fetchrow("""
SELECT id
row = await app.db.fetchrow("""
SELECT guild_id, channel_id
FROM webhooks
WHERE id = $1 AND token = $2
""", webhook_id, webhook_token)
if webhook_id_fetch is None:
if row is None:
raise Unauthorized('webhook not found or unauthorized')
return row['guild_id'], row['channel_id']
@bp.route('/channels/<int:channel_id>/webhooks', methods=['POST'])
async def create_webhook(channel_id: int):
@ -259,17 +270,64 @@ async def del_webhook_tokened(webhook_id, webhook_token):
return '', 204
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>', methods=['POST'])
async def execute_webhook(webhook_id, webhook_token):
async def create_message_webhook(guild_id, channel_id, webhook_id, j):
# TODO: impl
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',
methods=['POST'])
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'])
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},
'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}
}
}