Compare commits

..

3 Enmetoj

View File

@ -6,71 +6,7 @@
# Date: # Date:
#――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Return the ID of a post, based on its URL.
# url_id $url
url_id() {
local url="$1"
# Pleroma-style URLs: https://jam.xwx.moe/notice/Ac6PIZAP0ZzkMTYBBg
# Mastodon-style URLs: https://esperanto.masto.host/@minjo/111461250815264185
if echo "$url" | grep "/notice/" > /dev/null; then
echo "$url" \
| sed 's%.*/notice/%%'
else
echo "$url" \
| sed 's%.*/@[[:alnum:]]*/%%'
fi
}
# Return the server (including protocol) of a post, based on its URL.
# url_server $url
url_server() {
local url="$1"
local protocol="$(echo "$url" | grep --only-matching '[[:alnum:]]*://')"
printf "$protocol"
echo "$url" \
| sed 's%^'"$protocol"'%%' \
| sed 's%/.*%%'
}
# Make a request to the /api/v1/statuses/:id/$request API endpoint.
# statuses_api_request $post_url $request
statuses_api_request() {
local post_url="$1"
local api_request="$2"
if test -n "$api_request"; then
api_request="/$api_request"
fi
local id="$(url_id "$url")"
local server="$(url_server "$url")"
curl --location --header 'Accept: application/json,application/activity+json' \
"$server/api/v1/statuses/${id}${api_request}"
}
# Require the context-JSON of a post, by URL.
# fetch_post_context $url
fetch_post_context() {
local url="$1"
statuses_api_request "$url" "context"
}
# Given a post URL, request its JSON.
# fetch_post $url
fetch_post() {
local url="$1"
statuses_api_request "$url"
}
POST_TEMPLATE=' POST_TEMPLATE='
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="comments.css" type="text/css">
<article class="comment"> <article class="comment">
<a class="user" href="$ACCOUNT_URL"> <a class="user" href="$ACCOUNT_URL">
<img class="avatar" src="$ACCOUNT_AVATAR"> <img class="avatar" src="$ACCOUNT_AVATAR">
@ -86,9 +22,10 @@ POST_TEMPLATE='
<div class="attachments"> <div class="attachments">
$POST_ATTACHMENTS $POST_ATTACHMENTS
</div> </div>
<div class="responses">
$POST_RESPONSES
</div>
</article> </article>
</body>
</html>
' '
@ -120,6 +57,48 @@ ATTACH_IMAGE_TEMPLATE='
EMOJI_TEMPLATE='<img class="emoji" src="$EMOJI_URL" alt="$EMOJI_SHORTCODE" title="$EMOJI_SHORTCODE">' EMOJI_TEMPLATE='<img class="emoji" src="$EMOJI_URL" alt="$EMOJI_SHORTCODE" title="$EMOJI_SHORTCODE">'
# Given a notes JSON, render it as HTML.
# The most important part of the script!
# render_post $post_data $context_data $tree_level
render_post() {
local post_data="$1"
local responses_data="$2"
local POST_TREE_LEVEL="$3"
local ACCOUNT_URL="$(echo "$post_data" | jq -r .account.url)"
local ACCOUNT_ID="$(echo "$post_data" | jq -r .account.fqn)"
local ACCOUNT_NAME="$(echo "$post_data" | jq -r .account.display_name | replace_emojis "$(echo "$post_data" | jq -r '.account')")"
local ACCOUNT_AVATAR="$(echo "$post_data" | jq -r .account.avatar)"
local POST_URL="$(echo "$post_data" | jq -r .url)"
local POST_DATE="$(echo "$post_data" | jq -r .created_at)"
local POST_CONTENT="$(echo "$post_data" | jq -r .content | replace_emojis "$post_data")"
local POST_ATTACHMENTS="$(media_attachments "$post_data")"
local POST_RESPONSES="$(render_responses "$post_data" "$responses_data" "$POST_TREE_LEVEL")"
env_subst "$POST_TEMPLATE"
}
# Render a posts responses one-by-one and recursively.
# Each branch of the response tree will be rendered completely before proceeding
# to the next.
# render_responses $post_data $context_data $tree_level
render_responses() {
local post_data="$1"
local responses_data="$2"
local level="$3"
if test -z "$level"; then level=0; fi
local id="$(echo "$post_data" | jq -r '.id')"
local responses="$(echo "$responses_data" | grep "in_reply_to_id.*$id")"
local IFS="
"
for response in $responses; do
render_post "$response" "$responses_data" "$(expr "$level" + 1)"
done
}
# Accepts a string over stdin; it will replace all emoji shortcodes along stdin # Accepts a string over stdin; it will replace all emoji shortcodes along stdin
# input with appropriate <img> HTML, based on $EMOJI_TEMPLATE, and based on # input with appropriate <img> HTML, based on $EMOJI_TEMPLATE, and based on
# a posts JSON. # a posts JSON.
@ -166,6 +145,73 @@ media_attachments() {
} }
# Pass a posts context JSON along stdin; out comes the response_data in JSON.
# fetch_post_context $url | context_to_responses
context_to_responses() {
jq -cr '.descendants[] |= sort_by(.created_at)'
}
# Make a request to the /api/v1/statuses/:id/$request API endpoint.
# statuses_api_request $post_url $request
statuses_api_request() {
local post_url="$1"
local api_request="$2"
if test -n "$api_request"; then
api_request="/$api_request"
fi
local id="$(url_id "$url")"
local server="$(url_server "$url")"
curl --location --header 'Accept: application/json,application/activity+json' \
"$server/api/v1/statuses/${id}${api_request}"
}
# Require the context-JSON of a post, by URL.
# fetch_post_context $url
fetch_post_context() {
local url="$1"
statuses_api_request "$url" "context"
}
# Given a post URL, request its JSON.
# fetch_post $url
fetch_post() {
local url="$1"
statuses_api_request "$url"
}
# Return the ID of a post, based on its URL.
# url_id $url
url_id() {
local url="$1"
# Pleroma-style URLs: https://jam.xwx.moe/notice/Ac6PIZAP0ZzkMTYBBg
# Mastodon-style URLs: https://esperanto.masto.host/@minjo/111461250815264185
if echo "$url" | grep "/notice/" > /dev/null; then
echo "$url" \
| sed 's%.*/notice/%%'
else
echo "$url" \
| sed 's%.*/@[[:alnum:]]*/%%'
fi
}
# Return the server (including protocol) of a post, based on its URL.
# url_server $url
url_server() {
local url="$1"
local protocol="$(echo "$url" | grep --only-matching '[[:alnum:]]*://')"
printf "$protocol"
echo "$url" \
| sed 's%^'"$protocol"'%%' \
| sed 's%/.*%%'
}
# Sanitize a template-string. # Sanitize a template-string.
# AKA, escape quotation-marks. # AKA, escape quotation-marks.
# prep_template $template # prep_template $template
@ -189,18 +235,34 @@ env_subst() {
} }
# Given a notes JSON, render it as HTML. usage() {
# The most important part of the script! echo "usage: $(basename "$0") POST_URL"
# render_post $post_data echo
render_post() { echo "$(basename "$0") does exactly what it says on the tin: It formats"
local post_data="$1" echo "a fediverse post (and its replies) into simple-and-embeddable HTML."
local ACCOUNT_URL="$(echo "$post_data" | jq -r .account.url)" echo
local ACCOUNT_ID="$(echo "$post_data" | jq -r .account.fqn)" echo "It works with posts from any server that supports Mastodons API,"
local ACCOUNT_NAME="$(echo "$post_data" | jq -r .account.display_name | replace_emojis "$post_data")" echo "including Pleroma, Akkoma, Glitch, etc."
local ACCOUNT_AVATAR="$(echo "$post_data" | jq -r .account.avatar)" echo
local POST_URL="$(echo "$post_data" | jq -r .url)" echo "Notably, it supports post-atachments and custom-emoji. Keep in mind"
local POST_DATE="$(echo "$post_data" | jq -r .created_at)" echo "that images are all fetched from remote sources. It is recommended,"
local POST_CONTENT="$(echo "$post_data" | jq -r .content | replace_emojis "$post_data")" echo "if privacy or total archival, is a concern, to use wget(1)s --mirror"
local POST_ATTACHMENTS="$(media_attachments "$post_data")" echo "(or something like it) to fetch even these foreign files."
env_subst "$POST_TEMPLATE"
} }
URL="$1"
if test -z "$URL"; then
usage 1>&2
exit 2
elif test "$URL" = "-h" -o "$URL" = "--help"; then
usage
exit 0
fi
POST="$(fetch_post "$URL")"
RESPONSES="$(fetch_post_context "$URL" | context_to_responses)"
render_post "$POST" "$RESPONSES" 0