Compare commits

..

2 Enmetoj

Author SHA1 Message Date
Jaidyn Ann 8ca6350dda Make shellcheck (slightly) more happy
… though `dash` still doesn’t work. ;<
2024-10-31 14:46:26 -05:00
Jaidyn Ann 36c4c2d205 Replace `local` with ( functions )
That is, use functions defined with parens rather
than curly-braces.
2024-10-31 14:40:50 -05:00

194
fedi2html
View File

@ -70,151 +70,151 @@ fi
# Given a notes JSON, render it as HTML. # Given a notes JSON, render it as HTML.
# The most important part of the script! # The most important part of the script!
# render_post $post_data $context_data $tree_level # render_post $post_data $context_data $tree_level
render_post() { render_post() (
local post_data="$1" post_data="$1"
local responses_data="$2" responses_data="$2"
local POST_TREE_LEVEL="$3" POST_TREE_LEVEL="$3"
local reblog="" reblog=""
local acct_data="$(echo "$post_data" | jq -r .reblog.account)" acct_data="$(echo "$post_data" | jq -r .reblog.account)"
if test "$acct_data" = "null"; then if test "$acct_data" = "null"; then
acct_data="$(echo "$post_data" | jq -r .account)" acct_data="$(echo "$post_data" | jq -r .account)"
else else
reblog="1" reblog="1"
fi fi
local ACCOUNT_URL="$(echo "$acct_data" | jq -r .url)" ACCOUNT_URL="$(echo "$acct_data" | jq -r .url)"
local ACCOUNT_ID="$(echo "$acct_data" | jq -r .fqn)" ACCOUNT_ID="$(echo "$acct_data" | jq -r .fqn)"
local ACCOUNT_NAME="$(echo "$acct_data" | jq -r .display_name | replace_emojis "$acct_data")" ACCOUNT_NAME="$(echo "$acct_data" | jq -r .display_name | replace_emojis "$acct_data")"
local ACCOUNT_AVATAR="$(echo "$acct_data" | jq -r .avatar)" ACCOUNT_AVATAR="$(echo "$acct_data" | jq -r .avatar)"
local POST_URL="$(echo "$post_data" | jq -r .url)" POST_URL="$(echo "$post_data" | jq -r .url)"
local POST_DATE="$(echo "$post_data" | jq -r .created_at)" POST_DATE="$(echo "$post_data" | jq -r .created_at)"
local POST_CONTENT="$(echo "$post_data" | jq -r .content | replace_emojis "$post_data")" POST_CONTENT="$(echo "$post_data" | jq -r .content | replace_emojis "$post_data")"
local POST_ATTACHMENTS="$(media_attachments "$post_data")" POST_ATTACHMENTS="$(media_attachments "$post_data")"
if test -z "$NO_RESPONSES"; then if test -z "$NO_RESPONSES"; then
local POST_RESPONSES="$(render_responses "$post_data" "$responses_data" "$POST_TREE_LEVEL")" POST_RESPONSES="$(render_responses "$post_data" "$responses_data" "$POST_TREE_LEVEL")"
fi fi
env_subst "$POST_TEMPLATE" env_subst "$POST_TEMPLATE"
} )
# Render a posts responses one-by-one and recursively. # Render a posts responses one-by-one and recursively.
# Each branch of the response tree will be rendered completely before proceeding # Each branch of the response tree will be rendered completely before proceeding
# to the next. # to the next.
# render_responses $post_data $context_data $tree_level # render_responses $post_data $context_data $tree_level
render_responses() { render_responses() (
local post_data="$1" post_data="$1"
local responses_data="$2" responses_data="$2"
local level="$3" level="$3"
if test -z "$level"; then level=0; fi if test -z "$level"; then level=0; fi
local id="$(echo "$post_data" | jq -r '.id')" id="$(echo "$post_data" | jq -r '.id')"
local responses="$(echo "$responses_data" | grep "in_reply_to_id.*$id")" responses="$(echo "$responses_data" | grep "in_reply_to_id.*$id")"
local IFS=" IFS="
" "
for response in $responses; do for response in $responses; do
render_post "$response" "$responses_data" "$(expr "$level" + 1)" render_post "$response" "$responses_data" "$((level + 1))"
done 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.
# echo ":blobcat:" | replace_emojis $post_data # echo ":blobcat:" | replace_emojis $post_data
replace_emojis() { replace_emojis() (
local post_data="$1" post_data="$1"
local emojis="$(echo "$post_data" | jq -r '.emojis[]|(.url + "\t" + .shortcode)')" emojis="$(echo "$post_data" | jq -r '.emojis[]|(.url + "\t" + .shortcode)')"
local temp="$(mktemp)" temp="$(mktemp)"
local IFS=" IFS="
" "
cat > "$temp" cat > "$temp"
for line in $emojis; do for line in $emojis; do
local EMOJI_URL="$(echo "$line" | awk -F'\t' '{print $1}')" EMOJI_URL="$(echo "$line" | awk -F'\t' '{print $1}')"
local EMOJI_SHORTCODE="$(echo "$line" | awk -F'\t' '{print $2}')" EMOJI_SHORTCODE="$(echo "$line" | awk -F'\t' '{print $2}')"
local value="$(env_subst "$EMOJI_TEMPLATE")" value="$(env_subst "$EMOJI_TEMPLATE")"
sed -i "s%:${EMOJI_SHORTCODE}:%${value}%g" "$temp" sed -i "s%:${EMOJI_SHORTCODE}:%${value}%g" "$temp"
done done
cat "$temp" cat "$temp"
rm "$temp" rm "$temp"
} )
# Given a posts JSON data, return the appropriate HTML corresponding to its # 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, if any. Will return an empty string if none.
# media_attachments $post_data # media_attachments $post_data
media_attachments() { media_attachments() (
local post_data="$1" post_data="$1"
local attachments="$(echo "$post_data" | jq -r '.media_attachments[]|(.type + "\t" + .url + "\t" + .description + "\t" + .preview_url)')" attachments="$(echo "$post_data" | jq -r '.media_attachments[]|(.type + "\t" + .url + "\t" + .description + "\t" + .preview_url)')"
local IFS=" IFS="
" "
for line in $attachments; do for line in $attachments; do
local ATTACH_TYPE="$(echo "$line" | awk -F'\t' '{print $1}')" ATTACH_TYPE="$(echo "$line" | awk -F'\t' '{print $1}')"
local ATTACH_URL="$(echo "$line" | awk -F'\t' '{print $2}')" ATTACH_URL="$(echo "$line" | awk -F'\t' '{print $2}')"
local ATTACH_DESC="$(echo "$line" | awk -F'\t' '{print $3}')" ATTACH_DESC="$(echo "$line" | awk -F'\t' '{print $3}')"
local ATTACH_PREVIEW="$(echo "$line" | awk -F'\t' '{print $4}')" ATTACH_PREVIEW="$(echo "$line" | awk -F'\t' '{print $4}')"
local ATTACH_NAME="$(basename "$ATTACH_URL")" ATTACH_NAME="$(basename "$ATTACH_URL")"
if test "$ATTACH_TYPE" = "image"; then if test "$ATTACH_TYPE" = "image"; then
env_subst "$ATTACH_IMAGE_TEMPLATE" env_subst "$ATTACH_IMAGE_TEMPLATE"
else else
env_subst "$ATTACH_TEMPLATE" env_subst "$ATTACH_TEMPLATE"
fi fi
done done
} )
# Pass a posts context JSON along stdin; out comes the response_data in JSON. # Pass a posts context JSON along stdin; out comes the response_data in JSON.
# fetch_post_context $url | context_to_responses # fetch_post_context $url | context_to_responses
context_to_responses() { context_to_responses() (
jq '.descendants' 2> /dev/null \ jq '.descendants' 2> /dev/null \
| jq 'sort_by(.created_at)' \ | jq 'sort_by(.created_at)' \
| maybe_jq_reverse \ | maybe_jq_reverse \
| jq -cr '.[]' | jq -cr '.[]'
} )
# Make a request to the /api/v1/statuses/:id/$request API endpoint. # Make a request to the /api/v1/statuses/:id/$request API endpoint.
# statuses_api_request $post_url $request # statuses_api_request $post_url $request
statuses_api_request() { statuses_api_request() (
local post_url="$1" post_url="$1"
local api_request="$2" api_request="$2"
if test -n "$api_request"; then if test -n "$api_request"; then
api_request="/$api_request" api_request="/$api_request"
fi fi
local id="$(url_post_id "$post_url")" id="$(url_post_id "$post_url")"
local server="$(url_server "$post_url")" server="$(url_server "$post_url")"
curl --retry 3 --retry-delay 5 \ curl --retry 3 --retry-delay 5 \
--location --header 'Accept: application/json,application/activity+json' \ --location --header 'Accept: application/json,application/activity+json' \
"${server}/api/v1/statuses/${id}${api_request}" "${server}/api/v1/statuses/${id}${api_request}"
} )
# Require the context-JSON of a post, by URL. # Require the context-JSON of a post, by URL.
# fetch_post_context $url # fetch_post_context $url
fetch_post_context() { fetch_post_context() (
local url="$1" url="$1"
statuses_api_request "$url" "context" \ statuses_api_request "$url" "context" \
| context_to_responses | context_to_responses
} )
# Given a post URL, request its JSON. # Given a post URL, request its JSON.
# fetch_post $url # fetch_post $url
fetch_post() { fetch_post() (
local url="$1" url="$1"
statuses_api_request "$url" statuses_api_request "$url"
} )
# Given a user-account URL, request JSON of its posts. # Given a user-account URL, request JSON of its posts.
# fetch_post $url # fetch_post $url
fetch_user_posts() { fetch_user_posts() (
local url="$1" url="$1"
local server="$(url_server "$url")" server="$(url_server "$url")"
local status_url="$server/api/v1/accounts/$(url_user_id "$url")/statuses" status_url="$server/api/v1/accounts/$(url_user_id "$url")/statuses"
status_url="${status_url}?exclude_reblogs=${EXCLUDE_REBLOGS}&limit=$MAX_POSTS" status_url="${status_url}?exclude_reblogs=${EXCLUDE_REBLOGS}&limit=$MAX_POSTS"
status_url="${status_url}&exclude_replies=${EXCLUDE_REPLIES}$TAG_FILTER" status_url="${status_url}&exclude_replies=${EXCLUDE_REPLIES}$TAG_FILTER"
@ -222,13 +222,13 @@ fetch_user_posts() {
--location --header 'Accept: application/json,application/activity+json' \ --location --header 'Accept: application/json,application/activity+json' \
"$status_url" \ "$status_url" \
| jq -c '.[]' | jq -c '.[]'
} )
# Return the ID of a user, based on its URL. # Return the ID of a user, based on its URL.
# url_user_id $url # url_user_id $url
url_user_id() { url_user_id() (
local url="$1" url="$1"
# Pleroma-style URLs: https://jam.xwx.moe/users/Tirifto # Pleroma-style URLs: https://jam.xwx.moe/users/Tirifto
# Mastodon-style URLs: https://esperanto.masto.host/@jubiloEO # Mastodon-style URLs: https://esperanto.masto.host/@jubiloEO
if echo "$url" | grep "/users/" > /dev/null; then if echo "$url" | grep "/users/" > /dev/null; then
@ -241,13 +241,13 @@ url_user_id() {
else else
return 1 return 1
fi fi
} )
# Return the ID of a post, based on its URL. # Return the ID of a post, based on its URL.
# url_post_id $url # url_post_id $url
url_post_id() { url_post_id() (
local url="$1" url="$1"
# Pleroma-style URLs: https://jam.xwx.moe/notice/Ac6PIZAP0ZzkMTYBBg # Pleroma-style URLs: https://jam.xwx.moe/notice/Ac6PIZAP0ZzkMTYBBg
# Mastodon-style URLs: https://esperanto.masto.host/@minjo/111461250815264185 # Mastodon-style URLs: https://esperanto.masto.host/@minjo/111461250815264185
if echo "$url" | grep "/notice/" > /dev/null; then if echo "$url" | grep "/notice/" > /dev/null; then
@ -259,29 +259,29 @@ url_post_id() {
else else
return 1 return 1
fi fi
} )
# Return the server (including protocol) of a post, based on its URL. # Return the server (including protocol) of a post, based on its URL.
# url_server $url # url_server $url
url_server() { url_server() (
local url="$1" url="$1"
local protocol="$(echo "$url" | grep --only-matching '[[:alnum:]]*://')" protocol="$(echo "$url" | grep --only-matching '[[:alnum:]]*://')"
printf "$protocol" printf '%s' "$protocol"
echo "$url" \ echo "$url" \
| sed 's%^'"$protocol"'%%' \ | sed 's%^'"$protocol"'%%' \
| sed 's%/.*%%' | sed 's%/.*%%'
} )
# Sanitize a template-string. # Sanitize a template-string.
# AKA, escape quotation-marks. # AKA, escape quotation-marks.
# prep_template $template # prep_template $template
prep_template() { prep_template() (
local template="$1" template="$1"
echo "$template" \ echo "$template" \
| sed 's%\"%\\\"%g' | sed 's%\"%\\\"%g'
} )
# Rough replacement for gettexts envsubst. Safe! # Rough replacement for gettexts envsubst. Safe!
@ -291,37 +291,37 @@ prep_template() {
# variables contents (AKA, post contents) arent evaluated. # variables contents (AKA, post contents) arent evaluated.
# env_subst $template # env_subst $template
# env_subst "$SHELL" → "/bin/sh" # env_subst "$SHELL" → "/bin/sh"
env_subst() { env_subst() (
local template="$1" template="$1"
eval "echo \"$(prep_template "$template")\"" eval "echo \"$(prep_template "$template")\""
} )
# Based on the environment variable $REVERSE_ORDER (whether or not user provided # Based on the environment variable $REVERSE_ORDER (whether or not user provided
# the -I flag), reverse the JSON array over stdin. # the -I flag), reverse the JSON array over stdin.
# This is used to enable/disable reverse-chronological order of posts. # This is used to enable/disable reverse-chronological order of posts.
# fetch_context $url | jq '.descendants' | maybe_jq_reverse # fetch_context $url | jq '.descendants' | maybe_jq_reverse
maybe_jq_reverse() { maybe_jq_reverse() (
local input="$(cat)" input="$(cat)"
if test -n "$REVERSE_ORDER"; then if test -n "$REVERSE_ORDER"; then
echo "$input" \ echo "$input" \
| jq 'reverse' | jq 'reverse'
else else
echo "$input" echo "$input"
fi fi
} )
# Render a users posts, one-by-one, taking into account cli arguments. # Render a users posts, one-by-one, taking into account cli arguments.
# handle_user_url https://jam.xwx.moe/users/tirifto # handle_user_url https://jam.xwx.moe/users/tirifto
handle_user_url() { handle_user_url() (
local url="$1" url="$1"
local user_posts="$(fetch_user_posts "$url")" user_posts="$(fetch_user_posts "$url")"
local IFS=" IFS="
" "
echo "$user_posts" > jadedctrl.json echo "$user_posts" > jadedctrl.json
for post in $user_posts; do for post in $user_posts; do
local url="$(echo "$post" | jq -r '.url')" url="$(echo "$post" | jq -r '.url')"
if test -z "$NO_RESPONSES"; then if test -z "$NO_RESPONSES"; then
context="$(fetch_post_context "$url")" context="$(fetch_post_context "$url")"
if test -n "$NO_PARENT"; then if test -n "$NO_PARENT"; then
@ -333,24 +333,24 @@ handle_user_url() {
render_post "$post" "" 0 render_post "$post" "" 0
fi fi
done done
} )
# Render a post and/or its responses, taking into account cli arguments. # Render a post and/or its responses, taking into account cli arguments.
handle_post_url() { handle_post_url() (
local url="$1" url="$1"
local post="$(fetch_post "$url")" post="$(fetch_post "$url")"
local context="$(fetch_post_context "$url")" context="$(fetch_post_context "$url")"
if test -n "$NO_PARENT"; then if test -n "$NO_PARENT"; then
render_responses "$post" "$context" 0 render_responses "$post" "$context" 0
else else
render_post "$post" "$context" 0 render_post "$post" "$context" 0
fi fi
} )
usage() { usage() (
echo "usage: $(basename "$0") [-h] [-IRc] POST_URL" echo "usage: $(basename "$0") [-h] [-IRc] POST_URL"
echo " $(basename "$0") [-h] [-IRcbBt] [-m MAX] USER_URL" echo " $(basename "$0") [-h] [-IRcbBt] [-m MAX] USER_URL"
echo echo
@ -396,7 +396,7 @@ usage() {
echo 'See the first few lines of fedi2html for the default (example)' echo 'See the first few lines of fedi2html for the default (example)'
echo 'template values; see the README for a more detailed description' echo 'template values; see the README for a more detailed description'
echo 'of these variables meanings.' echo 'of these variables meanings.'
} )
TAG_FILTER="" TAG_FILTER=""
@ -432,6 +432,8 @@ while getopts 'hcIRt:bm:B' arg; do
B) B)
EXCLUDE_REPLIES="true" EXCLUDE_REPLIES="true"
;; ;;
*)
;;
esac esac
done done