It's all fluff, all the way down ...

Create random Spotify playlist with bash, curl and the Spotify API

This post was automagically converted from Blogger. There may be dragons. Check any code you copy.

Untinkered with this script will

You need a access-token with enough permissions (scopes) to modify playlists. I created one with AAAALL the scopes for my personal use so I’m not entirely sure which ones are really needed.

On to the script …

#!/bin/bash

### figure out some stuff
dir="${BASH_SOURCE%/*}"
[[ ! -d "$dir" ]] && dir="$PWD"
me=$(basename -s .sh $0)

# SOURCE
source_playlists=(
    "spotify:user:[username]:playlist:[playlist_id_here]" #MyAwesomeGiantPlaylist
    "spotify:user:[username]:playlist:[another_id_here]"  #AnotherGiantPlaylist
)

# DESTINATION
pluri_dest="spotify:user:[username_here]:playlist:[playlist_id_here]"

dir="$dir/$me"
mkdir -p $dir

playlist_file="$dir/$me.playlist"
payload_file="$dir/$me.payload"

filter_file="$dir/$me.filter"   # avoid randomly adding files recently added
sfilter_file="$dir/$me.sfilter" # avoid adding explicitly "filtered" songs
wfilter_file="$dir/$me.wfilter" # avoid adding songs with "filtered" words
afilter_file="$dir/$me.afilter" # avoid adding songs with "filtered" artists

artist_sep=50
number_recent=0
addtorecent=true

# ------------------------------------------------------------------------------------------- ^_^ --

setAccessToken() {
    access_token=$(cat ~/.config/.spotify_access_token)
}

touchFiles() {
    touch $filter_file
    touch $sfilter_file
    touch $wfilter_file
    touch $afilter_file
    touch $payload_file
}

truncateFiles() {
    tail -n $number_recent $filter_file > $filter_file.tmp
    mv $filter_file.tmp $filter_file
}

updateListSpotify() {
    [ -f $playlist_file.tmp ] && rm $playlist_file.tmp

    for uri in "${source_playlists[@]}"; do
        IFS=":" read -r foo foo playlist_source_user foo playlist_source_id <<< "$uri"
        next="https://api.spotify.com/v1/users/$playlist_source_user/playlists/$playlist_source_id/tracks?offset=0&limit=100"

        # keep GETTING while $next is not "null"
        while [ "$next" != "null" ]; do
            next="$next&fields=next,items(track(uri,name,artists(name)))"
            echo "NEXT: $next"

            output=$(curl -s -X GET \\
            "$next" \\
            -H "Authorization: Bearer $access_token")

            jq -r '.items[].track | .uri + "|" + .name  + "|" + .artists[0].name' <<< $output >> $playlist_file.tmp
            
            next=$(jq -r .next <<< $output)
        done
    done

    mv $playlist_file.tmp $playlist_file

    number_recent=$(( $(wc -l < $playlist_file) - 1000 ))
}

addSong() {
    local b=true
    local len=$(wc -l < $playlist_file)
    
    while $b; do
        b=false
        local song=$(cat $playlist_file | sed -n $[RANDOM % $len]p)
        
        local id=$(echo $song | cut -d\\| -f1)
        local title=$(echo $song | cut -d\\| -f2)
        local artist=$(echo $song | cut -d\\| -f3)

        # if song id is in recent- or static filter
        # or song title has words we don't like
        # or artist had song added in last n songs
        # or artist is not supposed to be added
        # we go again ...
        $b || grep -qi "$id" $filter_file $sfilter_file && b=true
        $b || grep -qi -Ff $wfilter_file <<< "$title" && b=true
        $b || tail -n $artist_sep $filter_file | cut -d\\| -f3 | grep -qi "$artist" && b=true
        $b || grep -qi "$artist" $afilter_file && b=true
    done

    $addtorecent && echo $song >> $filter_file

    echo "\\"$id\\"" >> $payload_file.tmp
}

loopAdd() {
    local i=0
    local length=$1

    while (( $i < $length )); do
        addSong

        let i++
    done

    jq --slurp '{ "uris" : . }' < $payload_file.tmp > $payload_file
    rm $payload_file.tmp
}

playlistDo() {
    ## Replace : PUT
    ## Add     : POST
    curl -i -X $1 \\
    "https://api.spotify.com/v1/users/$playlist_dest_user/playlists/$playlist_dest_id/tracks" \\
    -H "Authorization: Bearer $access_token" \\
    -H "Content-Type:application/json" \\
    --data @$payload_file
}


## DO STUFF
setAccessToken
updateListSpotify

touchFiles

# ToDo: make this less shit.
loopAdd 100
playlistDo PUT      # 100 files max at once
loopAdd 40
playlistDo POST     # ... so we add 40 more here.

truncateFiles

exit 0