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."""
|
||||
icon = await app.icons.generic_get(
|
||||
scope, key, icon_hash, **kwargs)
|
||||
|
||||
if not icon:
|
||||
return '', 404
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
"""
|
||||
|
||||
import secrets
|
||||
import base64
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
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.snowflake import get_snowflake
|
||||
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 (
|
||||
msg_create_request, msg_create_check_content, msg_add_attachment,
|
||||
msg_guild_text_mentions
|
||||
)
|
||||
from litecord.embed.sanitizer import fill_embed
|
||||
from litecord.embed.messages import process_url_embed
|
||||
from litecord.embed.sanitizer import fill_embed, fetch_raw_img
|
||||
from litecord.embed.messages import process_url_embed, is_media_url
|
||||
from litecord.utils import pg_set_json
|
||||
from litecord.enums import MessageType
|
||||
|
||||
|
|
@ -335,6 +338,32 @@ async def create_message_webhook(guild_id, channel_id, webhook_id, data):
|
|||
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'])
|
||||
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', [])
|
||||
|
||||
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(
|
||||
guild_id, channel_id, webhook_id, {
|
||||
'content': j.get('content', ''),
|
||||
'tts': j.get('tts', False),
|
||||
|
||||
'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)
|
||||
|
||||
|
||||
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):
|
||||
"""Insert mediaproxy embed."""
|
||||
embed = await fetch_embed(parsed, config=config, session=session)
|
||||
|
|
@ -125,11 +133,7 @@ async def process_url_embed(config, storage, dispatcher,
|
|||
new_embeds = []
|
||||
|
||||
for url in urls:
|
||||
parsed = urllib.parse.urlparse(url)
|
||||
path = Path(parsed.path)
|
||||
extension = path.suffix.lstrip('.')
|
||||
|
||||
if extension in MEDIA_EXTENSIONS:
|
||||
if is_media_url(url):
|
||||
embed = await insert_media_meta(url, config, session)
|
||||
else:
|
||||
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]:
|
||||
"""Fetch metadata for a url."""
|
||||
def _mk_cfg_sess(config, session) -> tuple:
|
||||
if config is None:
|
||||
config = app.config
|
||||
|
||||
if session is None:
|
||||
session = app.session
|
||||
|
||||
if config is None:
|
||||
config = app.config
|
||||
return config, session
|
||||
|
||||
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']
|
||||
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:
|
||||
if resp.status != 200:
|
||||
|
|
@ -128,6 +134,28 @@ async def fetch_metadata(url, *, config=None, session=None) -> Optional[Dict]:
|
|||
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:
|
||||
"""Fetch an embed"""
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ class EmbedURL:
|
|||
"""'json' version of the 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 = {
|
||||
'text': {
|
||||
|
|
|
|||
Loading…
Reference in New Issue