mirror of https://gitlab.com/litecord/litecord.git
webhooks: add draft for avatar_url on webhook execute
this is a bit messy due to us having to call mediaproxy for the given url, then store it on icons manager. - embed.messages: add is_media_url - embed.sanitizer: add fetch_raw_img - embed.schemas: add EmbedURL.to_md_path
This commit is contained in:
parent
1d9c9f7b85
commit
404783534c
|
|
@ -27,6 +27,7 @@ async def send_icon(scope, key, icon_hash, **kwargs):
|
||||||
"""Send an icon."""
|
"""Send an icon."""
|
||||||
icon = await app.icons.generic_get(
|
icon = await app.icons.generic_get(
|
||||||
scope, key, icon_hash, **kwargs)
|
scope, key, icon_hash, **kwargs)
|
||||||
|
|
||||||
if not icon:
|
if not icon:
|
||||||
return '', 404
|
return '', 404
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
|
import base64
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
from quart import Blueprint, jsonify, current_app as app, request
|
from quart import Blueprint, jsonify, current_app as app, request
|
||||||
|
|
@ -33,14 +34,16 @@ from litecord.schemas import (
|
||||||
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, ChannelNotFound
|
from litecord.errors import (
|
||||||
|
WebhookNotFound, Unauthorized, ChannelNotFound, BadRequest
|
||||||
|
)
|
||||||
|
|
||||||
from litecord.blueprints.channel.messages import (
|
from litecord.blueprints.channel.messages import (
|
||||||
msg_create_request, msg_create_check_content, msg_add_attachment,
|
msg_create_request, msg_create_check_content, msg_add_attachment,
|
||||||
msg_guild_text_mentions
|
msg_guild_text_mentions
|
||||||
)
|
)
|
||||||
from litecord.embed.sanitizer import fill_embed
|
from litecord.embed.sanitizer import fill_embed, fetch_raw_img
|
||||||
from litecord.embed.messages import process_url_embed
|
from litecord.embed.messages import process_url_embed, is_media_url
|
||||||
from litecord.utils import pg_set_json
|
from litecord.utils import pg_set_json
|
||||||
from litecord.enums import MessageType
|
from litecord.enums import MessageType
|
||||||
|
|
||||||
|
|
@ -335,6 +338,32 @@ async def create_message_webhook(guild_id, channel_id, webhook_id, data):
|
||||||
return message_id
|
return message_id
|
||||||
|
|
||||||
|
|
||||||
|
async def _create_avatar(webhook_id: int, avatar_url):
|
||||||
|
"""Create an avatar for a webhook out of an avatar URL,
|
||||||
|
given when executing the webhook.
|
||||||
|
|
||||||
|
Litecord will query that URL via mediaproxy and store the data
|
||||||
|
via IconManager.
|
||||||
|
"""
|
||||||
|
if avatar_url.scheme not in ('http', 'https'):
|
||||||
|
raise BadRequest('invalid avatar url scheme')
|
||||||
|
|
||||||
|
if not is_media_url(avatar_url):
|
||||||
|
raise BadRequest('url is not media url')
|
||||||
|
|
||||||
|
resp, raw = await fetch_raw_img(avatar_url)
|
||||||
|
raw_b64 = base64.b64encode(raw)
|
||||||
|
|
||||||
|
mime = resp.headers['content-type']
|
||||||
|
b64_data = f'data:{mime};base64,{raw_b64}'
|
||||||
|
|
||||||
|
icon = await app.icons.put(
|
||||||
|
'user', webhook_id, b64_data,
|
||||||
|
always_icon=True, size=(128, 128)
|
||||||
|
)
|
||||||
|
|
||||||
|
return icon.icon_hash
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>', methods=['POST'])
|
@bp.route('/webhooks/<int:webhook_id>/<webhook_token>', methods=['POST'])
|
||||||
async def execute_webhook(webhook_id: int, webhook_token):
|
async def execute_webhook(webhook_id: int, webhook_token):
|
||||||
|
|
@ -361,13 +390,25 @@ async def execute_webhook(webhook_id: int, webhook_token):
|
||||||
|
|
||||||
given_embeds = j.get('embeds', [])
|
given_embeds = j.get('embeds', [])
|
||||||
|
|
||||||
|
webhook = await get_webhook(webhook_id)
|
||||||
|
avatar = webhook['avatar']
|
||||||
|
|
||||||
|
if 'avatar_url' in j and j['avatar_url'] is not None:
|
||||||
|
avatar = await _create_avatar(webhook_id, j['avatar_url'])
|
||||||
|
|
||||||
message_id = await create_message_webhook(
|
message_id = await create_message_webhook(
|
||||||
guild_id, channel_id, webhook_id, {
|
guild_id, channel_id, webhook_id, {
|
||||||
'content': j.get('content', ''),
|
'content': j.get('content', ''),
|
||||||
'tts': j.get('tts', False),
|
'tts': j.get('tts', False),
|
||||||
|
|
||||||
'everyone_mention': mentions_everyone or mentions_here,
|
'everyone_mention': mentions_everyone or mentions_here,
|
||||||
'embeds': await async_map(fill_embed, given_embeds)
|
'embeds': await async_map(fill_embed, given_embeds),
|
||||||
|
|
||||||
|
'info': {
|
||||||
|
'id': webhook_id,
|
||||||
|
'name': j.get('name', webhook['name']),
|
||||||
|
'avatar': avatar
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,14 @@ async def _update_and_dispatch(payload, new_embeds, storage, dispatcher):
|
||||||
'channel', channel_id, 'MESSAGE_UPDATE', update_payload)
|
'channel', channel_id, 'MESSAGE_UPDATE', update_payload)
|
||||||
|
|
||||||
|
|
||||||
|
def is_media_url(url: str) -> bool:
|
||||||
|
parsed = urllib.parse.urlparse(url)
|
||||||
|
path = Path(parsed.path)
|
||||||
|
extension = path.suffix.lstrip('.')
|
||||||
|
|
||||||
|
return extension in MEDIA_EXTENSIONS:
|
||||||
|
|
||||||
|
|
||||||
async def insert_mp_embed(parsed, config, session):
|
async def insert_mp_embed(parsed, config, session):
|
||||||
"""Insert mediaproxy embed."""
|
"""Insert mediaproxy embed."""
|
||||||
embed = await fetch_embed(parsed, config=config, session=session)
|
embed = await fetch_embed(parsed, config=config, session=session)
|
||||||
|
|
@ -125,11 +133,7 @@ async def process_url_embed(config, storage, dispatcher,
|
||||||
new_embeds = []
|
new_embeds = []
|
||||||
|
|
||||||
for url in urls:
|
for url in urls:
|
||||||
parsed = urllib.parse.urlparse(url)
|
if is_media_url(url):
|
||||||
path = Path(parsed.path)
|
|
||||||
extension = path.suffix.lstrip('.')
|
|
||||||
|
|
||||||
if extension in MEDIA_EXTENSIONS:
|
|
||||||
embed = await insert_media_meta(url, config, session)
|
embed = await insert_media_meta(url, config, session)
|
||||||
else:
|
else:
|
||||||
embed = await insert_mp_embed(parsed, config, session)
|
embed = await insert_mp_embed(parsed, config, session)
|
||||||
|
|
|
||||||
|
|
@ -96,26 +96,32 @@ def proxify(url, *, config=None) -> str:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def fetch_metadata(url, *, config=None, session=None) -> Optional[Dict]:
|
def _mk_cfg_sess(config, session) -> tuple:
|
||||||
"""Fetch metadata for a url."""
|
if config is None:
|
||||||
|
config = app.config
|
||||||
|
|
||||||
if session is None:
|
if session is None:
|
||||||
session = app.session
|
session = app.session
|
||||||
|
|
||||||
if config is None:
|
return config, session
|
||||||
config = app.config
|
|
||||||
|
|
||||||
if isinstance(url, str):
|
|
||||||
url = EmbedURL(url)
|
|
||||||
|
|
||||||
parsed = url.parsed
|
|
||||||
|
|
||||||
md_path = f'{parsed.scheme}/{parsed.netloc}{parsed.path}'
|
|
||||||
|
|
||||||
|
def _md_base(config) -> tuple:
|
||||||
md_base_url = config['MEDIA_PROXY']
|
md_base_url = config['MEDIA_PROXY']
|
||||||
proto = 'https' if config['IS_SSL'] else 'http'
|
proto = 'https' if config['IS_SSL'] else 'http'
|
||||||
|
|
||||||
request_url = f'{proto}://{md_base_url}/meta/{md_path}'
|
return proto, md_base_url
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_metadata(url, *, config=None, session=None) -> Optional[Dict]:
|
||||||
|
"""Fetch metadata for a url."""
|
||||||
|
config, session = _mk_cfg_sess(config, session)
|
||||||
|
|
||||||
|
if not isinstance(url, EmbedURL):
|
||||||
|
url = EmbedURL(url)
|
||||||
|
|
||||||
|
proto, md_base_url = _md_base(config)
|
||||||
|
request_url = f'{proto}://{md_base_url}/meta/{url.to_md_path}'
|
||||||
|
|
||||||
async with session.get(request_url) as resp:
|
async with session.get(request_url) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
|
|
@ -128,6 +134,28 @@ async def fetch_metadata(url, *, config=None, session=None) -> Optional[Dict]:
|
||||||
return await resp.json()
|
return await resp.json()
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_raw_img(url, *, config=None, session=None) -> Optional[tuple]:
|
||||||
|
"""Fetch metadata for a url."""
|
||||||
|
config, session = _mk_cfg_sess(config, session)
|
||||||
|
|
||||||
|
if not isinstance(url, EmbedURL):
|
||||||
|
url = EmbedURL(url)
|
||||||
|
|
||||||
|
proto, md_base_url = _md_base(config)
|
||||||
|
# NOTE: the img, instead of /meta/.
|
||||||
|
request_url = f'{proto}://{md_base_url}/img/{url.to_md_path}'
|
||||||
|
|
||||||
|
async with session.get(request_url) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
body = await resp.text()
|
||||||
|
|
||||||
|
log.warning('failed to get img for {!r}: {} {!r}',
|
||||||
|
url, resp.status, body)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return resp, await resp.read()
|
||||||
|
|
||||||
|
|
||||||
async def fetch_embed(parsed, *, config=None, session=None) -> dict:
|
async def fetch_embed(parsed, *, config=None, session=None) -> dict:
|
||||||
"""Fetch an embed"""
|
"""Fetch an embed"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,12 @@ class EmbedURL:
|
||||||
"""'json' version of the url."""
|
"""'json' version of the url."""
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def to_md_path(self) -> str:
|
||||||
|
"""Convert the EmbedURL to a mediaproxy path."""
|
||||||
|
parsed = self.parsed
|
||||||
|
return f'{parsed.scheme}/{parsed.netloc}{parsed.path}'
|
||||||
|
|
||||||
|
|
||||||
EMBED_FOOTER = {
|
EMBED_FOOTER = {
|
||||||
'text': {
|
'text': {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue