webhooks: "finish" execution impl

it can do messages. but many things are still missing.

 - channel.messages: export msg_guild_text_mentions
 - storage: revamp Storage._inject_author
 - schema.sql: remove messages.webhook_id, add message_webhook_info
    table
 - add 16_messages_webhooks.sql migration
This commit is contained in:
Luna 2019-03-18 22:47:59 -03:00
parent 3866b1f2bc
commit 02c6741d74
6 changed files with 103 additions and 27 deletions

View File

@ -194,8 +194,9 @@ async def create_message(channel_id: int, actual_guild_id: int,
return message_id
async def _guild_text_mentions(payload: dict, guild_id: int,
async def msg_guild_text_mentions(payload: dict, guild_id: int,
mentions_everyone: bool, mentions_here: bool):
"""Calculates mention data side-effects."""
channel_id = int(payload['channel_id'])
# calculate the user ids we'll bump the mention count for
@ -435,8 +436,8 @@ async def _create_message(channel_id):
""", message_id, channel_id, user_id)
if ctype == ChannelType.GUILD_TEXT:
await _guild_text_mentions(payload, guild_id,
mentions_everyone, mentions_here)
await msg_guild_text_mentions(
payload, guild_id, mentions_everyone, mentions_here)
return jsonify(payload)

View File

@ -37,10 +37,12 @@ from litecord.errors import WebhookNotFound, Unauthorized, ChannelNotFound
from litecord.blueprints.channel.messages import (
msg_create_request, msg_create_check_content, msg_add_attachment,
# create_message
msg_guild_text_mentions
)
from litecord.embed.sanitizer import fill_embed
from litecord.embed.messages import process_url_embed
from litecord.utils import pg_set_json
from litecord.enums import MessageType
bp = Blueprint('webhooks', __name__)
@ -304,9 +306,34 @@ async def del_webhook_tokened(webhook_id, webhook_token):
return '', 204
async def create_message_webhook(guild_id, channel_id, webhook_id, j):
# TODO: impl
pass
async def create_message_webhook(guild_id, channel_id, webhook_id, data):
"""Create a message, but for webhooks only."""
message_id = get_snowflake()
async with app.db.acquire() as conn:
await pg_set_json(conn)
await conn.execute(
"""
INSERT INTO messages (id, channel_id, guild_id, webhook_id,
content, tts, mention_everyone, message_type, embeds)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
""",
message_id,
channel_id,
guild_id,
webhook_id,
data['content'],
data['tts'],
data['everyone_mention'],
MessageType.DEFAULT.value,
data.get('embeds', [])
)
return message_id
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>', methods=['POST'])
@ -318,6 +345,12 @@ async def execute_webhook(webhook_id: int, webhook_token):
# TODO: ensure channel_id points to guild text channel
payload_json, files = await msg_create_request()
# NOTE: we really pop here instead of adding a kwarg
# to msg_create_request just because of webhooks.
# nonce isn't allowed on WEBHOOK_MESSAGE_CREATE
payload_json.pop('nonce')
j = validate(payload_json, WEBHOOK_MESSAGE_CREATE)
msg_create_check_content(j, files)
@ -326,13 +359,15 @@ async def execute_webhook(webhook_id: int, webhook_token):
mentions_everyone = '@everyone' in j['content']
mentions_here = '@here' in j['content']
given_embeds = j.get('embeds', [])
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']]
'embeds': await async_map(fill_embed, given_embeds)
}
)
@ -341,6 +376,9 @@ async def execute_webhook(webhook_id: int, webhook_token):
payload = await app.storage.get_message(message_id)
await app.dispatcher.dispatch('channel', channel_id,
'MESSAGE_CREATE', payload)
# spawn embedder in the background, even when we're on a webhook.
app.sched.spawn(
process_url_embed(
@ -349,6 +387,10 @@ async def execute_webhook(webhook_id: int, webhook_token):
)
)
# we can assume its a guild text channel, so just call it
await msg_guild_text_mentions(
payload, guild_id, mentions_everyone, mentions_here)
# TODO: is it really 204?
return '', 204

View File

@ -712,11 +712,12 @@ WEBHOOK_MESSAGE_CREATE = {
# TODO: url type, or something...
'avatar_url': {
'type': 'url', 'required': False
# 'type': 'url', 'required': False
'type': 'string', 'required': False
},
'embeds': {
'type': list,
'type': 'list',
'required': False,
'schema': {'type': 'dict', 'schema': EMBED_OBJECT}
}

View File

@ -796,18 +796,28 @@ class Storage:
return res
async def _inject_author(self, res: dict):
"""Inject a pseudo-user object when the message is made by a webhook."""
author_id, webhook_id = res['author_id'], res['webhook_id']
"""Inject a pseudo-user object when the message is
made by a webhook."""
author_id = res['author_id']
if author_id is not None:
# if author_id is None, we fetch webhook info
# from the message_webhook_info table.
if author_id is None:
wb_info = await self.db.fetchrow("""
SELECT webhook_id, name, avatar
FROM message_webhook_info
WHERE message_id = $1
""", int(res['id']))
res['author'] = {
'id': str(wb_info['id']),
'bot': True,
'username': wb_info['name'],
'avatar': wb_info['avatar']
}
else:
res['author'] = await self.get_user(res['author_id'])
res.pop('webhook_id')
elif webhook_id is not None:
res['author'] = {
'id': webhook_id,
'username': 'a',
'avatar': None
}
res.pop('author_id')
@ -815,7 +825,7 @@ class Storage:
user_id: Optional[int] = None) -> Optional[Dict]:
"""Get a single message's payload."""
row = await self.fetchrow_with_json("""
SELECT id::text, channel_id::text, author_id, webhook_id, content,
SELECT id::text, channel_id::text, author_id, content,
created_at AS timestamp, edited_at AS edited_timestamp,
tts, mention_everyone, nonce, message_type, embeds
FROM messages

View File

@ -0,0 +1,17 @@
-- this is a tricky one. blame discord
-- first, remove all messages made by webhooks
DELETE FROM messages WHERE author_id is null;
-- delete the row, removing the fkey. no connection anymore.
ALTER TABLE messages DROP COLUMN webhook_id;
-- add a message_webhook_info table. more on that in Storage._inject_author
CREATE TABLE IF NOT EXISTS message_webhook_info (
message_id bigint REFERENCES messages (id) PRIMARY KEY,
webhook_id bigint,
name text DEFAULT '<invalid>',
avatar text DEFAULT NULL
);

View File

@ -643,13 +643,9 @@ CREATE TABLE IF NOT EXISTS messages (
-- this is good for search.
guild_id bigint REFERENCES guilds (id) ON DELETE CASCADE,
-- those are mutually exclusive, only one of them
-- can NOT be NULL at a time.
-- if author is NULL -> message from webhook
-- if webhook is NULL -> message from author
-- if author is NULL -> message from webhook ->
-- fetch from message_webhook_info
author_id bigint REFERENCES users (id),
webhook_id bigint REFERENCES webhooks (id),
content text,
@ -666,6 +662,15 @@ CREATE TABLE IF NOT EXISTS messages (
message_type int NOT NULL
);
CREATE TABLE IF NOT EXISTS message_webhook_info (
message_id bigint REFERENCES messages (id) PRIMARY KEY,
webhook_id bigint,
name text DEFAULT '<invalid>',
avatar text DEFAULT NULL
);
CREATE TABLE IF NOT EXISTS message_reactions (
message_id bigint REFERENCES messages (id),
user_id bigint REFERENCES users (id),