fedi2html/fedi2html.sh

232 lines
6.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Name: fedi2html.sh
# Desc:
# Reqs:
# 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='
<article class="comment">
<a class="user" href="$ACCOUNT_URL">
<img class="avatar" src="$ACCOUNT_AVATAR">
<strong class="username">$ACCOUNT_NAME</strong>
<em class="useraddress">$ACCOUNT_ID</em>
</a>
<a class="address" href="$POST_URL">
<time title="$POST_DATE">$(date --date=$POST_DATE)</time>
</a>
<section>
$POST_CONTENT
</section>
<div class="attachments">
$POST_ATTACHMENTS
</div>
<b>$tree_level</b>¤
<div class="responses">
$POST_RESPONSES
</div>
</article>
'
ATTACH_TEMPLATE='
<a href="$ATTACH_URL">
<div class="attachment">
<strong>$ATTACH_NAME</strong>
$ATTACH_DESC
</div>
</a>'
ATTACH_IMAGE_TEMPLATE='
<figure>
<a href="$ATTACH_URL">
<img class="attachment" src="$ATTACH_URL" alt="$ATTACH_DESC" title="$ATTACH_DESC">
</a>
<figcaption>
<a href="$ATTACH_URL"><b>$ATTACH_NAME</b></a><br>
$(if test $(echo "$ATTACH_DESC" | wc -c) -gt 52; then
echo $ATTACH_DESC | head -c 53 | sed 's%[[:space:]]*$%%'
echo …
else
echo $ATTACH_DESC
fi)
</figcaption>
</figure>
'
EMOJI_TEMPLATE='<img class="emoji" src="$EMOJI_URL" alt="$EMOJI_SHORTCODE" title="$EMOJI_SHORTCODE">'
# 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
# a posts JSON.
# echo ":blobcat:" | replace_emojis $post_data
replace_emojis() {
local post_data="$1"
local emojis="$(echo "$post_data" | jq -r '.emojis[]|(.url + "\t" + .shortcode)')"
local temp="$(mktemp)"
local IFS="
"
cat > "$temp"
for line in $emojis; do
local EMOJI_URL="$(echo "$line" | awk -F'\t' '{print $1}')"
local EMOJI_SHORTCODE="$(echo "$line" | awk -F'\t' '{print $2}')"
local value="$(env_subst "$EMOJI_TEMPLATE")"
sed -i "s%:${EMOJI_SHORTCODE}:%${value}%g" "$temp"
done
cat "$temp"
rm "$temp"
}
# Given a posts JSON data, return the appropriate HTML corresponding to its
# media attachments, if any. Will return an empty string if none.
# media_attachments $post_data
media_attachments() {
local post_data="$1"
local attachments="$(echo "$post_data" | jq -r '.media_attachments[]|(.type + "\t" + .url + "\t" + .description + "\t" + .preview_url)')"
local IFS="
"
for line in $attachments; do
local ATTACH_TYPE="$(echo "$line" | awk -F'\t' '{print $1}')"
local ATTACH_URL="$(echo "$line" | awk -F'\t' '{print $2}')"
local ATTACH_DESC="$(echo "$line" | awk -F'\t' '{print $3}')"
local ATTACH_PREVIEW="$(echo "$line" | awk -F'\t' '{print $4}')"
local ATTACH_NAME="$(basename "$ATTACH_URL")"
if test "$ATTACH_TYPE" = "image"; then
env_subst "$ATTACH_IMAGE_TEMPLATE"
else
env_subst "$ATTACH_TEMPLATE"
fi
done
}
# Sanitize a template-string.
# AKA, escape quotation-marks.
# prep_template $template
prep_template() {
local template="$1"
echo "$template" \
| sed 's%\"%\\\"%g'
}
# Rough replacement for gettexts envsubst. Safe!
# This will evaluate a strings shell variables (somewhat) safely
# Probably not good for general use — but for our purposes, it only subsitutes
# along the “first level.” So environment variables are replaced, but those
# variables contents (AKA, post contents) arent evaluated.
# env_subst $template
# env_subst "$SHELL" → "/bin/sh"
env_subst() {
local template="$1"
eval "echo \"$(prep_template "$template")\""
}
# 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 context_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_context "$post_data" "$context_data" "$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_context $post_data $context_data $tree_level
render_context() {
local post_data="$1"
local context_data="$2"
local level="$3"
if test -z "$level"; then level=0; fi
local id="$(echo "$post_data" | jq -r '.id')"
local responses="$(echo "$context_data" | jq -cr '.descendants[]' | grep "in_reply_to_id.*$id")"
local IFS="
"
for response in $responses; do
render_post "$response" "$context_data" "$(expr "$level" + 1)"
done
}