Shuffle the Schau

2026 - Apr 6

Creating a small Mastodon bot

In his latest book, Die Känguru Rebellion, Marc-Uwe Kling tells us about an art project in which a website randomly selects titles from the German news platform Tagesschau and pairs them with a randomly selected teaser image. The resulting combinations may be irrelevant, funny, or even unsettling.

I liked the idea an thus I thought this might be a good point of a mastodon-bot.

The bot is hosted at mastodon.social and toots every hour. Due to the simple nature of the task I implemented the bot as shellscript with the help of curl, awk and sed. As source for the titles and images I use the RSS-feed. The script itself can then be started by cron.

#! bash

MYDIR="$(dirname "$(realpath "$0")")"
CRED_FILE="$MYDIR/credentials"
IMG="shuffleimg.jpg"

# Read credentials (strip CR/newline and leading/trailing space)
if [ ! -f "$CRED_FILE" ]; then
  printf 'Credential file not found: %s\n' "$CRED_FILE" >&2
  exit 1
fi

BASE_URL=$(sed -n '1p' "$CRED_FILE" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
ACCESS_TOKEN=$(sed -n '2p' "$CRED_FILE" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

echo $ACCESS_TOKEN

if [ -z "$BASE_URL" ] || [ -z "$ACCESS_TOKEN" ]; then
  printf 'credentials file must contain base URL on line 1 and token on line 2\n' >&2
  exit 1
fi


TOKEN=$(cat credentials)
RSS_URL='https://www.tagesschau.de/infoservices/alle-meldungen-100~rss2.xml'
title=`curl -s "$RSS_URL" | xmllint --xpath '//item/title/text()' - 2>/dev/null | awk 'BEGIN{srand()} {if (rand() <= 1/NR) line=$0} END{print line}'`
sleep 10
image=`curl -s "$RSS_URL"   | xmllint --xpath '//*[local-name()="encoded"]/text()' - 2>/dev/null   | xmllint --html --xpath '//img[1]/@src' - 2>/dev/null   | sed -E 's/src="([^"]+)"/\1/' | awk 'BEGIN{srand()} {if (rand() <= 1/NR) line=$0} END{print line}'`

curl $image --output shuffleimg.jpg
echo $title

resp=$(curl -s -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "file=@${IMG}" \
  "${BASE_URL}/api/v2/media")
echo $resp

# 2) extract media id (handles "id":123 or "id":"123")
# media_id=$(printf '%s' "$resp" | sed -n 's/.*"id"[[:space:]]*:[[:space:]]*"\?\([0-9][0-9]*\)"\?.*/\1/p' | head -n1)
media_id=$(echo $resp | sed -E 's/.*"id":"?([^,"]*)"?.*/\1/')
echo "Got media_id: ${media_id}"

# 3) post status (multipart form to safely include text)
curl -s -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "status=${title}" \
  -F "media_ids[]=${media_id}" \
  "${BASE_URL%/}/api/v1/statuses" >/dev/null || {
    printf 'Failed to create status\n' >&2
    exit 1
  }

The source can be found at codeberg