server/api: add oEmbed and Open Graph
This commit is contained in:
parent
782f069031
commit
7a0a65bee4
4 changed files with 107 additions and 1 deletions
|
@ -169,6 +169,9 @@ privileges:
|
|||
'uploads:create': regular
|
||||
'uploads:use_downloader': power
|
||||
|
||||
homepage_url: https://www.example.com/
|
||||
site_url: https://www.example.com/booru
|
||||
|
||||
## ONLY SET THESE IF DEPLOYING OUTSIDE OF DOCKER
|
||||
#debug: 0 # generate server logs?
|
||||
#show_sql: 0 # show sql in server logs?
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import szurubooru.api.comment_api
|
||||
import szurubooru.api.info_api
|
||||
import szurubooru.api.oembed_api
|
||||
import szurubooru.api.password_reset_api
|
||||
import szurubooru.api.pool_api
|
||||
import szurubooru.api.pool_category_api
|
||||
|
|
97
server/szurubooru/api/oembed_api.py
Normal file
97
server/szurubooru/api/oembed_api.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
import re
|
||||
import html
|
||||
from urllib.parse import quote
|
||||
from typing import Dict, Optional
|
||||
|
||||
from szurubooru import config, model, rest
|
||||
from szurubooru.func import (
|
||||
auth,
|
||||
posts,
|
||||
serialization,
|
||||
)
|
||||
|
||||
with open(f"{config.config['data_dir']}/../index.htm") as index:
|
||||
index_html = index.read()
|
||||
|
||||
def _index_path(params: Dict[str, str]) -> int:
|
||||
try:
|
||||
return params["path"]
|
||||
except (TypeError, ValueError):
|
||||
raise posts.InvalidPostIdError(
|
||||
"Invalid post ID."
|
||||
)
|
||||
|
||||
|
||||
def _get_post(post_id: int) -> model.Post:
|
||||
return posts.get_post_by_id(post_id)
|
||||
|
||||
|
||||
def _get_post_id(match: re.Match) -> int:
|
||||
post_id = match.group("post_id")
|
||||
try:
|
||||
return int(post_id)
|
||||
except (TypeError, ValueError):
|
||||
raise posts.InvalidPostIdError(
|
||||
"Invalid post ID: %r." % post_id
|
||||
)
|
||||
|
||||
|
||||
def _serialize_post(
|
||||
ctx: rest.Context, post: Optional[model.Post]
|
||||
) -> rest.Response:
|
||||
return posts.serialize_post(
|
||||
post, ctx.user, options=serialization.get_serialization_options(ctx)
|
||||
)
|
||||
|
||||
|
||||
@rest.routes.get("/oembed/?")
|
||||
def get_post(
|
||||
ctx: rest.Context, _params: Dict[str, str] = {}, url: str = ""
|
||||
) -> rest.Response:
|
||||
auth.verify_privilege(ctx.user, "posts:view")
|
||||
|
||||
url = url or ctx.get_param_as_string("url")
|
||||
match = re.match(r".*?/post/(?P<post_id>\d+)", url)
|
||||
if not match:
|
||||
raise posts.InvalidPostIdError("Invalid post ID.")
|
||||
|
||||
post_id = _get_post_id(match)
|
||||
post = _get_post(post_id)
|
||||
serialized = _serialize_post(ctx, post)
|
||||
embed = {
|
||||
"version": "1.0",
|
||||
"type": "photo",
|
||||
"title": f"{config.config['name']} – Post #{post_id}",
|
||||
"author_name": serialized["user"]["name"] if serialized["user"] else None,
|
||||
"provider_name": config.config["name"],
|
||||
"provider_url": config.config["homepage_url"],
|
||||
"thumbnail_url": f"{config.config['site_url']}/{serialized['thumbnailUrl']}",
|
||||
"thumbnail_width": int(config.config["thumbnails"]["post_width"]),
|
||||
"thumbnail_height": int(config.config["thumbnails"]["post_height"]),
|
||||
"url": f"{config.config['site_url']}/{serialized['thumbnailUrl']}",
|
||||
"width": int(config.config["thumbnails"]["post_width"]),
|
||||
"height": int(config.config["thumbnails"]["post_height"])
|
||||
}
|
||||
return embed
|
||||
|
||||
|
||||
@rest.routes.get("/index(?P<path>/.+)")
|
||||
def post_index(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
|
||||
path = _index_path(params)
|
||||
oembed = get_post(ctx, {}, path)
|
||||
url = config.config["site_url"] + path
|
||||
new_html = index_html.replace("</head>", f'''
|
||||
<meta property="og:site_name" content="{config.config["name"]}">
|
||||
<meta property="og:url" content="{html.escape(url)}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:title" content="{html.escape(oembed['title'])}">
|
||||
<meta name="twitter:title" content="{html.escape(oembed['title'])}">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:image" content="{html.escape(oembed['url'])}">
|
||||
<meta property="og:image:url" content="{html.escape(oembed['url'])}">
|
||||
<meta property="og:image:width" content="{oembed['width']}">
|
||||
<meta property="og:image:height" content="{oembed['height']}">
|
||||
<meta property="article:author" content="{html.escape(oembed['author_name'] or '')}">
|
||||
<link rel="alternate" type="application/json+oembed" href="{config.config["site_url"]}/api/oembed?url={quote(html.escape(url))}" title="{html.escape(config.config["name"])}"></head>
|
||||
''').replace("<html>", '<html prefix="og: http://ogp.me/ns#">').replace("<title>Loading...</title>", f"<title>{html.escape(oembed['title'])}</title>")
|
||||
return {"return_type": "custom", "content": new_html}
|
|
@ -74,7 +74,8 @@ def application(
|
|||
) -> Tuple[bytes]:
|
||||
try:
|
||||
ctx = _create_context(env)
|
||||
if "application/json" not in ctx.get_header("Accept"):
|
||||
accept_header = ctx.get_header("Accept")
|
||||
if "*/*" not in accept_header and "application/json" not in accept_header:
|
||||
raise errors.HttpNotAcceptable(
|
||||
"ValidationError", "This API only supports JSON responses."
|
||||
)
|
||||
|
@ -111,6 +112,10 @@ def application(
|
|||
finally:
|
||||
db.session.remove()
|
||||
|
||||
if type(response) == dict and response.get("return_type") == "custom":
|
||||
start_response("200", [("content-type", "text/html")])
|
||||
return (response.get("content", "").encode("utf-8"),)
|
||||
|
||||
start_response("200", [("content-type", "application/json")])
|
||||
return (_dump_json(response).encode("utf-8"),)
|
||||
|
||||
|
|
Reference in a new issue