Created
June 17, 2026 17:25
-
-
Save cleanhands/3528f0f2e109de72588efe33d6ca0af6 to your computer and use it in GitHub Desktop.
Download videos from dailywire.com using yt-dlp versions which don't support dailywire
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| usage() { | |
| cat <<'EOF' | |
| Usage: dailywire-dl [options] <url> | |
| Download a Daily Wire episode by fetching signed playback data from the | |
| middleware API, then downloading the Mux stream with yt-dlp. | |
| Options: | |
| -c, --cookies <file> Cookies file (JSON or Netscape cookies.txt) | |
| -a, --access-token <token> Daily Wire accessToken (JWT) | |
| -r, --resolution <pixels> Pass -S "res:<pixels>" to yt-dlp (e.g. 720, 1080) | |
| One of --cookies or --access-token is required. | |
| Requires: curl, jq, yt-dlp | |
| Examples: | |
| ./dailywire-dl -c cookies.txt https://www.dailywire.com/episode/foo | |
| ./dailywire-dl --cookies cookies.json \ | |
| "https://www.dailywire.com/episode/foo?elementPosition=0" | |
| ./dailywire-dl -a "$ACCESS_TOKEN" -r 720 \ | |
| https://www.dailywire.com/episode/foo | |
| EOF | |
| } | |
| extract_slug() { | |
| local input_url="$1" | |
| printf '%s' "$input_url" | sed -E 's/\?.*//; s#.*/episode/##; s#/.*##' | |
| } | |
| is_json_cookies_file() { | |
| local file="$1" | |
| jq -e 'type == "array"' "$file" >/dev/null 2>&1 | |
| } | |
| extract_access_token_from_cookies_file() { | |
| local file="$1" | |
| if is_json_cookies_file "$file"; then | |
| jq -r '.[] | select(.name == "accessToken") | .value' "$file" | |
| else | |
| awk -F'\t' '$6 == "accessToken" { print $7; exit }' "$file" | |
| fi | |
| } | |
| middleware_origin="https://middleware-prod.dailywire.com" | |
| fetch_episode_from_middleware() { | |
| local slug="$1" | |
| local access_token="$2" | |
| curl -sS \ | |
| "${middleware_origin}/middleware/v4/getEpisodeWeb?slug=${slug}" \ | |
| -H "Authorization: Bearer ${access_token}" \ | |
| -H "Accept: application/json" \ | |
| -H "Origin: https://www.dailywire.com" \ | |
| -H "Referer: https://www.dailywire.com/episode/${slug}" | |
| } | |
| resolution="" | |
| cookies_file="" | |
| access_token="" | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| -c|--cookies) | |
| if [[ $# -lt 2 ]]; then | |
| echo "error: $1 requires a value" >&2 | |
| exit 1 | |
| fi | |
| cookies_file="$2" | |
| shift 2 | |
| ;; | |
| -a|--access-token) | |
| if [[ $# -lt 2 ]]; then | |
| echo "error: $1 requires a value" >&2 | |
| exit 1 | |
| fi | |
| access_token="$2" | |
| shift 2 | |
| ;; | |
| -r|--resolution) | |
| if [[ $# -lt 2 ]]; then | |
| echo "error: $1 requires a value" >&2 | |
| exit 1 | |
| fi | |
| resolution="$2" | |
| shift 2 | |
| ;; | |
| --) | |
| shift | |
| break | |
| ;; | |
| -*) | |
| echo "error: unknown option: $1" >&2 | |
| usage >&2 | |
| exit 1 | |
| ;; | |
| *) | |
| break | |
| ;; | |
| esac | |
| done | |
| if [[ $# -ne 1 ]]; then | |
| usage >&2 | |
| exit 1 | |
| fi | |
| if [[ -n "$cookies_file" && -n "$access_token" ]]; then | |
| echo "error: use either --cookies or --access-token, not both" >&2 | |
| exit 1 | |
| fi | |
| if [[ -z "$cookies_file" && -z "$access_token" ]]; then | |
| echo "error: either --cookies or --access-token is required" >&2 | |
| usage >&2 | |
| exit 1 | |
| fi | |
| if [[ -n "$resolution" && ! "$resolution" =~ ^[0-9]+$ ]]; then | |
| echo "error: resolution must be a positive integer (e.g. 720 or 1080)" >&2 | |
| exit 1 | |
| fi | |
| require_cmd() { | |
| if ! command -v "$1" >/dev/null 2>&1; then | |
| echo "error: $1 is required" >&2 | |
| exit 1 | |
| fi | |
| } | |
| require_cmd curl | |
| require_cmd jq | |
| require_cmd yt-dlp | |
| url="$1" | |
| slug="$(extract_slug "$url")" | |
| if [[ -z "$slug" ]]; then | |
| echo "error: could not extract episode slug from URL" >&2 | |
| exit 1 | |
| fi | |
| if [[ -n "$cookies_file" ]]; then | |
| if [[ ! -f "$cookies_file" ]]; then | |
| echo "error: cookies file not found: $cookies_file" >&2 | |
| exit 1 | |
| fi | |
| access_token="$(extract_access_token_from_cookies_file "$cookies_file")" | |
| if [[ -z "$access_token" || "$access_token" == "null" ]]; then | |
| echo "error: accessToken not found in $cookies_file" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| echo "Fetching episode metadata for ${slug}..." >&2 | |
| episode_json="$(fetch_episode_from_middleware "$slug" "$access_token")" | |
| if ! jq -e . >/dev/null 2>&1 <<<"$episode_json"; then | |
| echo "error: middleware API returned invalid JSON" >&2 | |
| printf '%s\n' "$episode_json" >&2 | |
| exit 1 | |
| fi | |
| if jq -e '.error | type == "string" and length > 0' >/dev/null 2>&1 <<<"$episode_json"; then | |
| echo "error: middleware API request failed" >&2 | |
| jq . >&2 <<<"$episode_json" | |
| exit 1 | |
| fi | |
| title="$(jq -r '.title // empty' <<<"$episode_json")" | |
| playback_id="$(jq -r '.muxPlaybackId // empty' <<<"$episode_json")" | |
| playback_token="$(jq -r '.muxPlaybackToken // empty' <<<"$episode_json")" | |
| if [[ -z "$title" || -z "$playback_id" || -z "$playback_token" ]]; then | |
| echo "error: could not find episode title or signed Mux stream URL" >&2 | |
| echo "note: ensure the access token is current" >&2 | |
| exit 1 | |
| fi | |
| content_url="https://stream.mux.com/${playback_id}.m3u8?token=${playback_token}" | |
| # Keep the page title; only strip chars illegal on Linux/macOS (/ and control chars). | |
| cleaned_title="$(printf '%s' "$title" | tr -d '\0/' | sed $'s/[\001-\037\177]//g')" | |
| if [[ -z "$cleaned_title" ]]; then | |
| cleaned_title="dailywire-episode" | |
| fi | |
| output_title="$cleaned_title" | |
| if [[ -n "$resolution" ]]; then | |
| output_title="${cleaned_title} ${resolution}p" | |
| fi | |
| echo "Title: $title" >&2 | |
| if [[ -n "$resolution" ]]; then | |
| echo "Resolution: ${resolution}p" >&2 | |
| fi | |
| echo "Downloading..." >&2 | |
| ytdlp_args=( | |
| --ignore-config | |
| --no-warnings | |
| --merge-output-format mp4 | |
| --no-mtime | |
| ) | |
| if [[ -n "$resolution" ]]; then | |
| ytdlp_args+=(-S "res:${resolution}") | |
| fi | |
| yt-dlp \ | |
| "${ytdlp_args[@]}" \ | |
| "$content_url" \ | |
| -o "${output_title}.%(ext)s" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment