manicito/manicito

254 lines
8.1 KiB
Plaintext
Raw Permalink Normal View History

2024-04-10 17:21:09 -05:00
#!/bin/sh
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
# Name: manicito (Manic-citation)
# Desc: A Mastodon/Pleroma bot which Quote-posts all posts of the given hashtag.
# Reqs: curl, shell, jq
# Date: 2024-04-10
2024-04-10 17:21:09 -05:00
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
if test -z "$MANICITO_TEMPLATE"; then
MANICITO_TEMPLATE='$USER (<a href="$USER_URL">$USERHOST</a>) <a href="$POST_URL">posted</a> about $HASHTAG: <blockquote>$(echo "$POST" | head -c 200)</blockquote>'
fi
# Prints a simple usage message.
usage() {
echo "usage: $(basename $0) [-h] [-H HISTORY_FILE] HASHTAG SERVER_URL"
echo 'Mastodon/Pleroma/etc script to quote-posts all posts of a given hashtag.'
echo
echo ' -h print this message and exit'
echo ' -H file used to store/check quoted posts; to avoid duplicates'
2024-04-10 22:10:48 -05:00
echo ' -Q disable actual quote-posting (which Mastodon doesnt support)'
2024-04-10 22:08:09 -05:00
echo ' -s scope for posts, one of: public/[unlisted]/private/direct'
echo
echo 'Posts tagged under HASHTAG will all be quote-posted to the'
echo 'Mastodon-compatible server at SERVER_URL.'
echo
echo 'In order to make these posts, you must set the environment variable $FEDI_AUTH'
echo 'to your authorization token. To find your authorization token, you can snoop '
echo 'through request headers in your web-browser. In Firefox, you can do:'
echo ' Developer Tools (F12) → Network → Headers'
echo 'Look for your $FEDI_AUTH value in the Authorization header like so:'
echo ' Authorization: Bearer $FEDI_AUTH'
echo
echo 'To avoid duplicate-posts, you should specify a “history” file with the -H'
echo 'parameter. With this, all quoted post IDs will be saved in this file, and'
echo 'on subsequent runs (so long as you dont change the history-file), no post'
echo 'will be quoted twice.'
echo
echo 'The environment variable $MANICITO_TEMPLATE is used create the body of your'
echo 'quote-posts. It substitutes the following shell-variables in templates:'
echo ' • $HASHTAG'
echo ' • $POST'
echo ' • $POST_URL'
echo ' • $USER'
echo ' • $USER_URL'
}
2024-04-10 17:21:09 -05:00
# Fetch a JSON array of posts of a hashtag. If a minimum post ID is provided,
# only posts older than that one will be returned.
# fetch_hashtag_posts $server $hashtag $minimum_id
fetch_hashtag_posts() {
local server="$1"
local hashtag="$2"
local minimum_id="$3"
if test -n "$minimum_id"; then
curl --fail "$server/api/v1/timelines/tag/$hashtag?min_id=$minimum_id"
else
curl --fail "$server/api/v1/timelines/tag/$hashtag"
fi
}
# Given a posts JSON, create and submit a quote-post for it.
# quote_post $tagged_post_json
quote_post() {
local tagged_post_json="$1"
curl --fail \
--request POST \
--header "Authorization: Bearer $FEDI_AUTH" \
--header 'Content-Type: application/json' \
--header "Idempotency-Key: $(echo "$tagged_post_json" | jq -r .id)" \
--data "$(post_json "$tagged_post_json")" "$FEDI_SERVER/api/v1/statuses"
2024-04-10 17:21:09 -05:00
}
# Given a JSON-array of posts over stdin, create and submit a quote-post for each one.
# $posts_json_array | quote_posts
quote_posts() {
local IFS="
"
while read -r tagged_post_line; do
quote_post "$tagged_post_line"
if test "$?" -ne 0; then
echo "Failed to post about post $(echo "$tagged_post_line" | jq .id)!" 1>&2
exit 1
fi
echo "$tagged_post_line"
done
}
# Given a posts JSON, return JSON of a quote-post quoting it.
# post_json $tagged_post-json
post_json() {
local tagged_post_json="$1"
2024-04-10 22:08:09 -05:00
printf '{ "content_type": "text/html", "visibility": "%s",' "$FEDI_SCOPE"
2024-04-10 22:10:48 -05:00
if test -z "$NO_QUOTE_POSTS"; then
printf '"quote_id": "%s",' "$(echo "$tagged_post_json" | jq -r .id)"
fi
printf '"expires_in": %s,' 864000
printf '"status": "%s" }\n' "$(post_body "$tagged_post_json")"
2024-04-10 17:21:09 -05:00
}
# Output the contents of our quote-posts body about the given tagged-posts JSON.
# This uses the global variable $MANICITO as our template, replacing the variables
# $POST, $POST_URL, $USER, $USER_URL, and $USERHOST.
# post_body $tagged_post_json
2024-04-10 17:21:09 -05:00
post_body() {
local tagged_post_json="$1"
POST="$(echo "$tagged_post_json" | jq -r .content | tr -d "\"'\n")"
2024-04-10 17:21:09 -05:00
POST_URL="$(echo "$tagged_post_json" | jq -r .uri)"
USER="$(echo "$tagged_post_json" | jq -r .account.display_name)"
USER_URL="$(echo "$tagged_post_json" | jq -r .account.url)"
USERHOST="$(echo "$tagged_post_json" | jq -r .account.acct)"
env_subst "$MANICITO_TEMPLATE"
2024-04-10 17:21:09 -05:00
}
# Receives posts in a JSON array over stdin. Given a newline-delimited list of
# post IDs ($ids_to_filter), all matching posts from the JSON array will be
# filted out.# sent over stdin that match one of these posts IDs will be filtered out.
# echo $post_array_json | filter_posts $ids_to_filter
2024-04-10 17:21:09 -05:00
filter_posts() {
local ids_to_filter="$1"
# If the list has more than give characters, use it to filter… (sanity check)
if test -n "$ids_to_filter"; then
2024-04-10 17:21:09 -05:00
jq -cr .[] \
| grep --invert-match $(echo "$ids_to_filter" | sed 's%^%-e %')
2024-04-10 17:21:09 -05:00
else
jq -cr .[]
fi
}
history_post_ids() {
local history_file="$1"
if test -f "$history_file"; then
awk -F '\t' '{print $2}' "$history_file" \
| grep -v '^[[:space:]]*$'
fi
}
# Pipe in fediverse posts in JSON format; these will all be echoed into the given
# “history file”, so that they can be filtered out and avoided on subsequent
# runs.
# echo $newline_delimited_post_jsons | update_filter $history_file
2024-04-10 17:21:09 -05:00
update_filter() {
local history_file="$1"
if test -n "$history_file"; then
2024-04-10 17:21:09 -05:00
jq -r '(.created_at + "\t" + .id)' \
| sort -n \
>> "$history_file"
2024-04-10 17:21:09 -05:00
fi
}
# 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")\""
}
2024-04-10 22:08:09 -05:00
FEDI_SCOPE="unlisted" # Default value.
2024-04-10 22:10:48 -05:00
while getopts 'hH:s:Q' arg; do
case $arg in
h)
usage
exit 0
;;
H)
HISTORY_FILE="$OPTARG"
;;
2024-04-10 22:08:09 -05:00
s)
OPTARG="$(echo "$OPTARG" | tr 'A-Z' 'a-z')"
if test "$OPTARG" = "unlisted" -o "$OPTARG" = "public" \
-o "$OPTARG" = "private" -o "$OPTARG" = "direct"; then
FEDI_SCOPE="$OPTARG"
else
1>&2 echo '$OPTARG is an invalid scope; must be one of:'
1>&2 echo ' • unlisted'
1>&2 echo ' • public'
1>&2 echo ' • private'
1>&2 echo ' • direct'
1>&2 echo
1>&2 echo 'Run with -h for more information.'
exit 6
fi
;;
2024-04-10 22:10:48 -05:00
Q)
NO_QUOTE_POSTS="1"
;;
esac
done
2024-04-10 22:08:09 -05:00
shift $((OPTIND-1))
HASHTAG="$1"
FEDI_SERVER="$2"
if test -z "$HASHTAG"; then
usage
exit 1
fi
if test -z "$FEDI_AUTH"; then
1>&2 echo 'You need to set the environment variable $FEDI_AUTH!'
1>&2 echo 'You can find your auth key by examining the "Authorization: Bearer" header'
1>&2 echo "used in requests by your server's web-client."
1>&2 echo 'In Firefox, F12→Network.'
1>&2 echo ""
exit 2
fi
if test -z "$FEDI_SERVER"; then
1>&2 echo 'No server specified!'
1>&2 echo 'Make sure to provide a hashtag (like #esperanto) and a'
1>&2 echo 'server URL (like https://fedi.server) as the last two arguments.'
1>&2 echo
1>&2 echo 'Run with -h for more information.'
exit 3
fi
POST_IDS_TO_IGNORE="$(history_post_ids "$HISTORY_FILE")"
MOST_RECENT_POST="$(echo "$POST_IDS_TO_IGNORE" | tail -1)"
fetch_hashtag_posts "$FEDI_SERVER" "$HASHTAG" "$MOST_RECENT_POST" \
| filter_posts "$POST_IDS_TO_IGNORE" \
2024-04-10 17:21:09 -05:00
| quote_posts \
| update_filter "$HISTORY_FILE"