Implement twitter video embeds
WIP as they get cut off for tall videos Also do not serve them to Telegram because Telegram prefers twitter:card to og:video
This commit is contained in:
parent
dfe952ddaf
commit
05074527ed
1 changed files with 45 additions and 9 deletions
|
@ -31,13 +31,17 @@ def user_embed(username: str) -> Metadata:
|
||||||
user = requests.get(BACKEND_BASE_URL + "/api/user/" + username).json()
|
user = requests.get(BACKEND_BASE_URL + "/api/user/" + username).json()
|
||||||
yield "og:image:url", urllib.parse.join(PUBLIC_BASE_URL, user["avatarUrl"])
|
yield "og:image:url", urllib.parse.join(PUBLIC_BASE_URL, user["avatarUrl"])
|
||||||
|
|
||||||
def _image_embed(post: Dict[str, Any]) -> Metadata:
|
def _image_embed(post: Dict[str, Any], skip_twitter_player=False) -> Metadata:
|
||||||
url = PUBLIC_BASE_URL + post["contentUrl"]
|
url = PUBLIC_BASE_URL + post["contentUrl"]
|
||||||
|
|
||||||
if post["type"] == "video":
|
if post["type"] == "video":
|
||||||
prefix = "og:video"
|
prefix = "og:video"
|
||||||
yield "og:image", PUBLIC_BASE_URL + post["thumbnailUrl"]
|
yield "og:image", PUBLIC_BASE_URL + post["thumbnailUrl"]
|
||||||
yield "twitter:card", "player"
|
if not skip_twitter_player:
|
||||||
|
yield "twitter:card", "player"
|
||||||
|
yield "twitter:player", PUBLIC_BASE_URL + f"/player/{post['id']}"
|
||||||
|
yield "twitter:player:width", str(post["canvasWidth"])
|
||||||
|
yield "twitter:player:height", str(post["canvasHeight"])
|
||||||
else:
|
else:
|
||||||
prefix = "og:image"
|
prefix = "og:image"
|
||||||
yield "twitter:card", "summary_large_image"
|
yield "twitter:card", "summary_large_image"
|
||||||
|
@ -52,7 +56,7 @@ def _image_embed(post: Dict[str, Any]) -> Metadata:
|
||||||
def _author_embed(user: Dict[str, Any]) -> Metadata:
|
def _author_embed(user: Dict[str, Any]) -> Metadata:
|
||||||
yield "article:author", PUBLIC_BASE_URL + "/user/" + user["name"]
|
yield "article:author", PUBLIC_BASE_URL + "/user/" + user["name"]
|
||||||
|
|
||||||
def post_embed(post_id: int) -> Metadata:
|
def post_embed(post_id: int, *, skip_twitter_player=False) -> Metadata:
|
||||||
post = requests.get(BACKEND_BASE_URL + f"/api/post/{post_id}").json()
|
post = requests.get(BACKEND_BASE_URL + f"/api/post/{post_id}").json()
|
||||||
yield "og:type", "article"
|
yield "og:type", "article"
|
||||||
yield from _author_embed(post["user"])
|
yield from _author_embed(post["user"])
|
||||||
|
@ -61,14 +65,14 @@ def post_embed(post_id: int) -> Metadata:
|
||||||
value = "Tags: " + ", ".join(tag["names"][0] for tag in post["tags"])
|
value = "Tags: " + ", ".join(tag["names"][0] for tag in post["tags"])
|
||||||
yield "og:description", value
|
yield "og:description", value
|
||||||
yield "description", value
|
yield "description", value
|
||||||
yield from _image_embed(post)
|
yield from _image_embed(post, skip_twitter_player)
|
||||||
|
|
||||||
def homepage_embed(server_info: Dict[str, Any]) -> Metadata:
|
def homepage_embed(server_info: Dict[str, Any], *, skip_twitter_player=False) -> Metadata:
|
||||||
yield "og:title", server_info["config"]["name"]
|
yield "og:title", server_info["config"]["name"]
|
||||||
yield "og:type", "website"
|
yield "og:type", "website"
|
||||||
post = server_info["featuredPost"]
|
post = server_info["featuredPost"]
|
||||||
if post is not None:
|
if post is not None:
|
||||||
yield from _image_embed(post)
|
yield from _image_embed(post, skip_twitter_player)
|
||||||
|
|
||||||
def render_embed(metadata: Metadata) -> str:
|
def render_embed(metadata: Metadata) -> str:
|
||||||
out = []
|
out = []
|
||||||
|
@ -77,6 +81,28 @@ def render_embed(metadata: Metadata) -> str:
|
||||||
out.append(f'<meta property="{k}" content="{v}">')
|
out.append(f'<meta property="{k}" content="{v}">')
|
||||||
return ''.join(out)
|
return ''.join(out)
|
||||||
|
|
||||||
|
def serve_twitter_video_player(start_response, post_id: int):
|
||||||
|
r = requests.get(BACKEND_BASE_URL + f"/api/post/{post_id}")
|
||||||
|
data = r.json()
|
||||||
|
if r.status_code != HTTPStatus.OK:
|
||||||
|
start_response(r.status_code, [("Content-Type", "text/html; charset=utf-8")])
|
||||||
|
yield f"<h1>{html.escape(data['title'])}</h1><p>{html.escape(data['description'])}</p>".encode("utf-8"),
|
||||||
|
|
||||||
|
start_response(http_status.OK, [("Content-Type", "text/html; charset=utf-8")])
|
||||||
|
post = data
|
||||||
|
yield b"<!DOCTYPE html><html><head><title>​</title>"
|
||||||
|
yield b"<style type='text/css'>video { width: 100%; max-width: 600px; height: auto; }</style></head><body>"
|
||||||
|
yield b"<video autoplay controls"
|
||||||
|
flags = set(post["flags"])
|
||||||
|
if "loop" in flags:
|
||||||
|
yield b" loop"
|
||||||
|
|
||||||
|
if "sound" not in flags:
|
||||||
|
yield b" muted"
|
||||||
|
|
||||||
|
yield f"><source type='{post['mimeType']}' src='{post['contentUrl']}'>Your browser doesn't support HTML5 videos.".encode("utf-8")
|
||||||
|
yield b"</video></body></html>"
|
||||||
|
|
||||||
def application(env: Dict[str, Any], start_response: Callable[[str, Any], Any]) -> Tuple[bytes]:
|
def application(env: Dict[str, Any], start_response: Callable[[str, Any], Any]) -> Tuple[bytes]:
|
||||||
def serve_file(path):
|
def serve_file(path):
|
||||||
start_response(str(int(HTTPStatus.OK)), [("X-Accel-Redirect", path)])
|
start_response(str(int(HTTPStatus.OK)), [("X-Accel-Redirect", path)])
|
||||||
|
@ -99,12 +125,22 @@ def application(env: Dict[str, Any], start_response: Callable[[str, Any], Any])
|
||||||
path = "/" + path
|
path = "/" + path
|
||||||
path_components = path.split("/")
|
path_components = path.split("/")
|
||||||
|
|
||||||
if path_components[1] not in {"post", "user", ""}:
|
if path_components[1] not in {"post", "user", "", "player"}:
|
||||||
# serve index.htm like normal
|
# serve index.htm like normal
|
||||||
return serve_without_embed()
|
return serve_without_embed()
|
||||||
|
|
||||||
|
if path_components[1] == "player" and path_components[2]:
|
||||||
|
try:
|
||||||
|
post_id = int(path_components[2])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return serve_twitter_video_player(start_response, post_id)
|
||||||
|
|
||||||
server_info = requests.get(BACKEND_BASE_URL + "/api/info").json()
|
server_info = requests.get(BACKEND_BASE_URL + "/api/info").json()
|
||||||
privileges = server_info["config"]["privileges"]
|
privileges = server_info["config"]["privileges"]
|
||||||
|
# Telegram prefers twitter:card to og:video, so we need to skip the former in order for videos to play inline
|
||||||
|
skip_twitter_player = env["HTTP_USER_AGENT"].startswith("TelegramBot")
|
||||||
if path_components[1] == "user":
|
if path_components[1] == "user":
|
||||||
username = path_components[2]
|
username = path_components[2]
|
||||||
if privileges["users:view"] != "anonymous" or not username:
|
if privileges["users:view"] != "anonymous" or not username:
|
||||||
|
@ -119,10 +155,10 @@ def application(env: Dict[str, Any], start_response: Callable[[str, Any], Any])
|
||||||
|
|
||||||
if privileges["posts:view"] != "anonymous":
|
if privileges["posts:view"] != "anonymous":
|
||||||
return serve_without_embed()
|
return serve_without_embed()
|
||||||
metadata = post_embed(post_id)
|
metadata = post_embed(post_id, skip_twitter_player=skip_twitter_player)
|
||||||
|
|
||||||
elif path_components[1] == "":
|
elif path_components[1] == "":
|
||||||
metadata = homepage_embed(server_info)
|
metadata = homepage_embed(server_info, skip_twitter_player=skip_twitter_player)
|
||||||
|
|
||||||
metadata = itertools.chain(general_embed(server_info), metadata)
|
metadata = itertools.chain(general_embed(server_info), metadata)
|
||||||
body = INDEX_HTML.replace("<!-- Embed Placeholder -->", render_embed(metadata)).encode("utf-8")
|
body = INDEX_HTML.replace("<!-- Embed Placeholder -->", render_embed(metadata)).encode("utf-8")
|
||||||
|
|
Loading…
Reference in a new issue