Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active October 24, 2024 15:22
Show Gist options
  • Save marco79cgn/b09e26beaaf466cb04f9d74122866048 to your computer and use it in GitHub Desktop.
Save marco79cgn/b09e26beaaf466cb04f9d74122866048 to your computer and use it in GitHub Desktop.
Das Skript lädt entweder einen einzelnen Film, Staffeln einer Serie oder alle Tatort Folgen einer Stadt von ARD Plus
#!/bin/bash
FILE=ard-plus-token
# parse input parameter
ardPlusUrl=$1
username=$2
password=$3
skip=$4
movieId=''
token=''
showPath=$(echo $ardPlusUrl | rev | cut -d "/" -f1 | rev)
showId=$(echo $showPath | cut -d "-" -f1)
if [[ -z "$username" || -z "$password" ]]
then
echo "Credentials missing! Please start the script with 3 parameters: "
echo "./ard-plus-dl <ard-plus-url> <username> <password>"
exit 1
fi
if [[ -z "$skip" ]]
then
skip=1
fi
# login only if necessary
login() {
loginResult=$(curl -s -o /dev/null -w '%header{authorization}' 'https://auth.ardplus.de/auth/login?plainRedirect=true&redirectURL=https%3A%2F%2Fwww.ardplus.de%2Flogin%2Fcallback&errorRedirectURL=https%3A%2F%2Fwww.ardplus.de%2Fanmeldung%3Ferror%3Dtrue' \
-H 'authority: auth.ardplus.de' \
-H 'content-type: application/x-www-form-urlencoded' \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' \
--data-raw "username=$1&password=$2" \
--compressed)
token=$(echo $loginResult | awk -F'Bearer ' '{print $2}')
echo $token > $FILE
}
# cleanup after each episode and at the end
cleanup() {
deleteToken=$(curl -s 'https://token.ardplus.de/token/session/playback/delete' \
-H 'authority: token.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' \
--data-raw "{\"contentId\":\"$movieId\",\"contentType\":\"CmsMovie\"}" \
--compressed)
}
# get authorization for content
auth() {
auth=$(curl -s 'https://token.ardplus.de/token/session' \
-H 'authority: token.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' \
--data-raw "{\"contentId\":\"$movieId\",\"contentType\":\"CmsEpisode\",\"download\":false,\"appInfo\":{\"platform\":\"web\",\"appVersion\":\"1.0.0\",\"build\":\"web\",\"bundleIdentifier\":\"web\"},\"deviceInfo\":{\"isTouchDevice\":false,\"isTablet\":false,\"isFireOS\":false,\"appPlatform\":\"web\",\"isIOS\":false,\"isCastReceiver\":false,\"isSafari\":false,\"isFirefox\":false}}" \
--compressed)
urlParam=$(echo ${auth} | jq -r '.authorizationParams')
echo "$urlParam"
}
# intercept CTRL+C click to clean up before exit
term() {
echo "CTRL+C pressed. Cleanup and exit!"
cleanup
exit 0
}
trap term SIGINT
# perform login
if [ -f "$FILE" ]; then
# Using cached token
token=$(<$FILE)
else
# Log in once
login $username $password
fi
# check if token is valid
movieId="a0S010000007GcX"
urlParam=$( auth )
if [[ "$urlParam" == null ]]; then
login $username $password
token=$(<$FILE)
if [[ -z "$token" ]]; then
echo "Login not possible! Please check credentials and subscription for user $username."
exit 0
fi
fi
cleanup
# get requested content
contentUrl="https://data.ardplus.de/ard/graphql?extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%2240d7cbfb79e6675c80aae2d44da2a7f74e4a4ee913b5c31b37cf9522fa64d63b%22%7D%7D&variables=%7B%22movieId%22%3A%22$showId%22%2C%22externalId%22%3A%22%22%2C%22slug%22%3A%22%22%2C%22potentialMovieId%22%3A%22%22%7D"
seasonsStatus=$(curl -s -o content-result.txt -w "%{http_code}" "${contentUrl}" \
-H 'authority: data.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36')
if [[ $seasonsStatus != "200" ]]; then
#retry once
echo "Couldn't get season details. Trying again!"
sleep 2
seasonsStatus=$(curl -s -o content-result.txt -w "%{http_code}" "${contentUrl}" \
-H 'authority: data.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36')
contentResult=$(cat content-result.txt)
else
contentResult=$(cat content-result.txt)
fi
# check whether content is movie or series
movie=$(echo "$contentResult" | jq '.data.movie')
tvshow=$(echo "$contentResult" | jq '.data.series')
if [[ "$movie" != null ]]; then
movieId=$(echo "$movie" | jq -r '.id')
name=$(echo "$movie" | jq -r '.title')
videoUrl=$(echo "$movie" | jq -r '.videoSource.dashUrl')
year=$(echo "$movie" | jq -r '.productionYear')
filename="${name} (${year})"
urlParam=$( auth )
downloadUrl=${videoUrl}?${urlParam}
echo "Lade Film ${filename}..."
yt-dlp --quiet --progress --no-warnings ${downloadUrl} -o "$filename"
cleanup
elif [[ "$tvshow" != null ]]; then
requestedShow=$(echo "$contentResult" | jq '.data.series.title')
seasonIds=$(echo "$contentResult" | jq '[.data.series.seasons.nodes[] | { season: .seasonInSeries, seasonId: .id, title: .title }]')
seasonOutput=$(echo "$seasonIds" | jq '[.[] | { Option: .season, Titel: .title }]' | jq -r '(.[0]|keys_unsorted|(.,map(length*"-"))),.[]|map(.)|@tsv'|column -ts $'\t')
echo -e "\nGewünschte Serie: $requestedShow\n"
echo -e "$seasonOutput\n"
echo -n "Welche Staffel möchtest du runterladen? "
read -r selectedSeason
selectedSeasonId=$(echo "$seasonIds" | jq -r --argjson index 1 ".[$((selectedSeason - 1))].seasonId")
seasonData=$(curl -s "https://data.ardplus.de/ard/graphql?extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22134d75e1e68a9599d1cdccf790839d9d71d2e7d7dca57d96f95285fcfd02b2ae%22%7D%7D&variables=%7B%22seasonId%22%3A%22$selectedSeasonId%22%7D&operationName=EpisodesInSeasonData" \
-H 'authority: data.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36')
episodes=$(echo $seasonData | jq '[.data.episodes.nodes[] | { id: .id, episodeNo: .episodeInSeason, title: .title, videoUrl: .videoSource.dashUrl }]')
amount=$(echo $episodes | jq '. | length')
echo -e "\nStaffel $selectedSeason hat $amount Folgen."
selectedSeasonFormatted=$(printf '%02d\n' "$selectedSeason")
# loop over all episodes and download each
while read episode
do
movieId=$(echo "$episode" | jq -r '.id')
name=$(echo "$episode" | jq -r '.title')
videoUrl=$(echo "$episode" | jq -r '.videoUrl')
episode=$(echo "$episode" | jq -r '.episodeNo')
filename="S${selectedSeasonFormatted}E$(printf '%02d\n' $episode) - ${name}"
urlParam=$( auth )
downloadUrl=${videoUrl}?${urlParam}
echo "Lade ${filename}..."
yt-dlp --quiet --progress --no-warnings ${downloadUrl} -o "$filename"
cleanup
done < <(echo "$episodes" | jq -c '.[]')
elif [[ "$ardPlusUrl" == *"tatort"* ]]; then
tatortCity=$(echo $showPath | cut -d "-" -f2)
# get all episodes per city
tatortCityEpisodes=$(curl -s "https://data.ardplus.de/ard/graphql?operationName=CategoryDataBySlug&variables=%7B%22slug%22%3A%22tatort-$tatortCity%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%221bf6e600fa91aa72bba35ee95a53677cf21b994a4c2afbcd01127259c7e88612%22%7D%7D" \
--header 'authority: data.ardplus.de' \
--header 'content-type: application/json' \
--header "cookie: sid=$token" \
--header 'origin: https://www.ardplus.de' \
--header 'referer: https://www.ardplus.de/' \
--header 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36')
episodeIds=$(echo "$tatortCityEpisodes" | jq '[.data.category.content.nodes[] | { id: .contentMovie.id }]')
amount=$(echo $episodeIds | jq '. | length')
cityCapitalized=$(echo ${tatortCity} | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1')
echo "Der Tatort ${cityCapitalized} hat $amount Episoden."
echo -n "Wie viele Episoden möchtest du überspringen? (0=alle laden) "
read -r skip
echo "Überspringe $skip Episode(n)."
skip=$((skip + 1))
# loop over all episodes and download each
while read episode
do
movieId=$(echo "$episode" | jq -r '.id')
episodeUrl="https://data.ardplus.de/ard/graphql?operationName=MovieDetails&variables=%7B%22movieId%22%3A%22$movieId%22%2C%22externalId%22%3A%22%22%2C%22slug%22%3A%22%22%2C%22potentialMovieId%22%3A%22%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%226a791c24fd9716b154a3d68f9b5213eb0bf25828a5e633cdbbd2e35aa5b9a984%22%7D%7D"
episodeDetailsStatus=$(curl -s -o current-tatort-episode.txt -w "%{http_code}" "${episodeUrl}" \
-H 'authority: data.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36')
if [[ $episodeDetailsStatus != "200" ]]; then
#retry once
echo "Couldn't get episode details. Trying again!"
sleep 2
episodeDetailsStatus=$(curl -s -o current-tatort-episode.txt -w "%{http_code}" $episodeUrl \
-H 'authority: data.ardplus.de' \
-H 'content-type: application/json' \
-H "cookie: sid=$token" \
-H 'origin: https://www.ardplus.de' \
-H 'referer: https://www.ardplus.de/' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' \
--compressed)
episodeDetails=$(cat current-tatort-episode.txt)
else
episodeDetails=$(cat current-tatort-episode.txt)
fi
name=$(echo "$episodeDetails" | jq -r '.data.movie.title')
videoUrl=$(echo "$episodeDetails" | jq -r '.data.movie.videoSource.dashUrl')
year=$(echo "$episodeDetails" | jq -r '.data.movie.productionYear')
customData=$(echo "$episodeDetails" | jq -r '.data.movie.customData')
episode=$(echo "$customData" | jq -r '.episodeProductionNumber')
team=$(echo "$customData" | jq -r '.team')
city=$(echo "$customData" | jq -r '.location')
filename="Tatort ${city}"
if [[ -n "$team" ]];
then
filename="$filename (${team})"
fi
if [[ "$episode" != null ]];
then
filename="$filename - Folge ${episode}"
fi
filename="$filename - ${name} (${year})"
urlParam=$( auth )
downloadUrl=${videoUrl}?${urlParam}
echo "Lade ${filename}..."
yt-dlp --quiet --progress --no-warnings ${downloadUrl} -o "$filename"
cleanup
sleep 1
done < <(echo "$episodeIds" | jq -c '.[]' | tail -n +$skip )
else
echo "invalid content"
fi
cleanup
@marco79cgn
Copy link
Author

marco79cgn commented Nov 29, 2023

Bildschirmfoto 2023-12-29 um 17 25 27

Anforderungen

  • Shell/Bash (z.B. macOS Terminal)
  • jq
  • yt-dlp
  • curl, grep, awk, echo, cut (gnu tools)
  • ARD Plus Mitgliedschaft (14 Tage kostenlos)

Benutzung

Skript downloaden und ausführbar machen: chmod 755 ard-plus-series-dl.sh

Anschließend das Skript aufrufen und drei Parameter mitgeben:
./ard-plus-series-dl.sh <url> <username> <password>

Die URL ist die Übersichtsseite eines Films oder einer Serie bei ARD Plus, zum Beispiel
Praxis Bülowbogen (Serie):
https://www.ardplus.de/details/a0T01000000a1LU-praxis-buelowbogen
Lola rennt (Film):
https://www.ardplus.de/details/a0S01000000EWYi-lola-rennt

Das Skript erkennt automatisch, ob es sich um einen Film oder eine Serie handelt. Filme werden unmittelbar geladen. Im Falle einer Serie werden alle gefundenen Staffeln aufgelistet und zur Auswahl angeboten.

Es können zusätzlich zu Filmen und Serien auch ganze Tatort Ausgaben pro Stadt geladen werden, z.B. alle Folgen aus Bremen mit der URL:
https://www.ardplus.de/kategorie/tatort-bremen

Die Zieldateien werden sinnvoll benannt, z.B.:
S01E01 - Schönes Wochenende.mp4
oder
Lola rennt (1998).mp4

@gh01mojmnet
Copy link

gh01mojmnet commented Feb 29, 2024

Hallo!
Hast du eine Idee, ob sich in der ARDplus Mediathek etwas geändert hat?
Ich bekomme oft 'invalid content' für Filme und Serien werden auch nicht runtergeladen...

folgendes Beispiel - dl.sh:

~/git/ard-plus-dl/ard-plus-dl.sh https://www.ardplus.de/details/a0S01000000zF11-halleluja-amigo <user> <password>
~/git/ard-plus-dl/ard-plus-dl.sh https://www.ardplus.de/details/a0T01000000s7sX-don-matteo <user> <password>
~/git/ard-plus-dl/ard-plus-dl.sh https://www.ardplus.de/details/a0S010000015IyU-alfred-die-knallerbse <user> <password>
~/git/ard-plus-dl/ard-plus-dl.sh https://www.ardplus.de/details/a0S010000015IyY-der-zerstreute <user> <password>
~/git/ard-plus-dl/ard-plus-dl.sh https://www.ardplus.de/details/a0S010000015Iyf-der-sanfte-mit-den-schnellen-beinen <user> <password>
~/git/ard-plus-dl/ard-plus-dl.sh https://www.ardplus.de/details/a0S010000015RYQ-ein-tolpatsch-auf-abwegen <user> <password>

ergibt folgenden Output:

> ./dl.sh
invalid content

Gewünschte Serie: "Don Matteo"

Option  Titel
------  -----
1       Don Matteo - Staffel 1
2       Don Matteo - Staffel 2

Welche Staffel möchtest du runterladen? 2
jq: error (at <stdin>:1): Cannot iterate over null (null)

Staffel 2 hat  Folgen.
Lade Film Alfred, die Knallerbse (1972)...
invalid content
invalid content
Lade Film Ein Tolpatsch auf Abwegen (1978)...
Lade Film Eine Wolke zwischen den Zähnen (1974)...

Vielen Dank für das ansonsten tolle Script!

@marco79cgn
Copy link
Author

marco79cgn commented Oct 24, 2024

Update 24.10.2024: fixed API calls

@gh01mojmnet
Sorry, hatte dein Feedback komplett übersehen. Das Skript sollte jetzt wieder einwandfrei funktionieren, auch mit deinen Beispielen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment