2024-04-10 17:21:09 -05:00
|
|
|
|
#!/bin/sh
|
|
|
|
|
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
2024-04-10 17:45:29 -05:00
|
|
|
|
# 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
|
|
|
|
#―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
|
|
|
|
|
|
2024-04-10 18:14:36 -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'
|
|
|
|
|
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 don’t 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
|
|
|
|
|
|
|
|
|
|
2024-04-10 18:41:47 -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"
|
|
|
|
|
|
2024-04-10 21:56:08 -05:00
|
|
|
|
if test -n "$minimum_id"; then
|
2024-04-10 18:41:47 -05:00
|
|
|
|
curl --fail "$server/api/v1/timelines/tag/$hashtag?min_id=$minimum_id"
|
|
|
|
|
else
|
|
|
|
|
curl --fail "$server/api/v1/timelines/tag/$hashtag"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 17:45:29 -05:00
|
|
|
|
# Given a post’s 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' \
|
2024-04-10 21:56:08 -05:00
|
|
|
|
--header "Idempotency-Key: $(echo "$tagged_post_json" | jq -r .id)" \
|
2024-04-10 17:45:29 -05:00
|
|
|
|
--data "$(post_json "$tagged_post_json")" "$FEDI_SERVER/api/v1/statuses"
|
2024-04-10 17:21:09 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 17:45:29 -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 post’s JSON, return JSON of a quote-post quoting it.
|
|
|
|
|
# post_json $tagged_post-json
|
|
|
|
|
post_json() {
|
|
|
|
|
local tagged_post_json="$1"
|
|
|
|
|
printf '{ "content_type": "text/html", "visibility": "unlisted",'
|
|
|
|
|
printf '"quote_id": "%s",' "$(echo "$tagged_post_json" | jq -r .id)"
|
2024-04-10 18:41:47 -05:00
|
|
|
|
printf '"expires_in": %s,' 864000
|
2024-04-10 17:45:29 -05:00
|
|
|
|
printf '"status": "%s" }\n' "$(post_body "$tagged_post_json")"
|
2024-04-10 17:21:09 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 17:45:29 -05:00
|
|
|
|
# Output the contents of our quote-post’s body about the given tagged-post’s 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"
|
2024-04-10 17:45:29 -05:00
|
|
|
|
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)"
|
2024-04-10 17:45:29 -05:00
|
|
|
|
env_subst "$MANICITO_TEMPLATE"
|
2024-04-10 17:21:09 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 18:41:47 -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() {
|
2024-04-10 18:41:47 -05:00
|
|
|
|
local ids_to_filter="$1"
|
|
|
|
|
# If the list has more than give characters, use it to filter… (sanity check)
|
2024-04-10 21:56:08 -05:00
|
|
|
|
if test -n "$ids_to_filter"; then
|
2024-04-10 17:21:09 -05:00
|
|
|
|
jq -cr .[] \
|
2024-04-10 18:14:36 -05:00
|
|
|
|
| grep --invert-match $(echo "$ids_to_filter" | sed 's%^%-e %')
|
2024-04-10 17:21:09 -05:00
|
|
|
|
else
|
|
|
|
|
jq -cr .[]
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 18:14:36 -05:00
|
|
|
|
history_post_ids() {
|
|
|
|
|
local history_file="$1"
|
|
|
|
|
if test -f "$history_file"; then
|
2024-04-10 21:56:08 -05:00
|
|
|
|
awk -F '\t' '{print $2}' "$history_file" \
|
|
|
|
|
| grep -v '^[[:space:]]*$'
|
2024-04-10 18:14:36 -05:00
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 17:45:29 -05:00
|
|
|
|
# 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"
|
2024-04-10 18:41:47 -05:00
|
|
|
|
if test -n "$history_file"; then
|
2024-04-10 17:21:09 -05:00
|
|
|
|
jq -r '(.created_at + "\t" + .id)' \
|
|
|
|
|
| sort -n \
|
2024-04-10 18:14:36 -05:00
|
|
|
|
>> "$history_file"
|
2024-04-10 17:21:09 -05:00
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-10 17:45:29 -05:00
|
|
|
|
|
|
|
|
|
# Sanitize a template-string.
|
|
|
|
|
# AKA, escape quotation-marks.
|
|
|
|
|
# prep_template $template
|
|
|
|
|
prep_template() {
|
|
|
|
|
local template="$1"
|
|
|
|
|
echo "$template" \
|
|
|
|
|
| sed 's%\"%\\\"%g'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Rough replacement for gettext’s envsubst. Safe!
|
|
|
|
|
# This will evaluate a string’s 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) aren’t evaluated.
|
|
|
|
|
# env_subst $template
|
|
|
|
|
# env_subst "$SHELL" → "/bin/sh"
|
|
|
|
|
env_subst() {
|
|
|
|
|
local template="$1"
|
|
|
|
|
eval "echo \"$(prep_template "$template")\""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 18:14:36 -05:00
|
|
|
|
while getopts 'hH:' arg; do
|
|
|
|
|
case $arg in
|
|
|
|
|
h)
|
|
|
|
|
usage
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
H)
|
|
|
|
|
HISTORY_FILE="$OPTARG"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2024-04-10 18:41:47 -05:00
|
|
|
|
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 \
|
2024-04-10 18:14:36 -05:00
|
|
|
|
| update_filter "$HISTORY_FILE"
|