Tuesday, May 29, 2018

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

Untinkered with this script will
  • add 140 random songs from the source-playlist(s) to the destination-playlist
  • keep track of added files
  • keep artist-separation at 50 songs (some songs "featuring" other artists might slip through)
  • filter out titles with "bad" words, "live in", "live at", "remix", "shitty version" ... etc.
  • filter out artists
  • filter out songs by id

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