mirror of https://gitlab.com/litecord/litecord.git
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:
parent
8360638356
commit
712f1d9ff2
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue