About: https://redd.it/on9gb5
Example with dependencies: Spotify_Api.ahk.zip
About: https://redd.it/on9gb5
Example with dependencies: Spotify_Api.ahk.zip
[SPOTIFY] | |
port = 1234 | |
client_id = 0123456789abcdf0123456789abcdf | |
client_secret = 0123456789abcdf0123456789abcdf | |
scopes = user-read-currently-playing user-read-playback-state | |
access_token = | |
refresh_token = | |
expires_at = |
| |
; Version: 2022.11.11.1 | |
; About: https://redd.it/on9gb5 | |
#Include %A_LineFile%\..\Socket.ahk | |
#Include %A_LineFile%\..\WinHttpRequest.ahk | |
class Spotify_Api { | |
__New(Ini, Section := "SPOTIFY") { | |
IniRead buffer, % Ini, % Section | |
if (!buffer) | |
throw Exception("Invalid configuration file.", -1) | |
for _,line in StrSplit(buffer, "`n", "`n") { | |
parts := StrSplit(line, "=",, 2) | |
this["_" parts[1]] := parts[2] | |
} | |
if (!this._client_id || !this._client_secret) | |
throw Exception("Client ID/Secret error.", -1, Ini) | |
if (!this._port) | |
throw Exception("Port not defined.", -1, Ini) | |
else if (this._port < 1024 && !A_IsAdmin) | |
throw Exception("Invalid port for HTTP server.", -1, Ini) | |
this._ini := Ini | |
this._section := Section | |
this.http := new WinHttpRequest() | |
if (!this._access_token) | |
this.Auth() | |
while (!this._access_token) | |
Sleep 50 | |
} | |
Auth() { | |
server := new SocketTCP() | |
server.OnAccept := ObjBindMethod(this, "_Socket") | |
server.Bind(["0.0.0.0", this._port]) | |
server.Listen() | |
this._access_token := "" | |
redirect := this.http.encodeURIComponent("http://localhost:" this._port "/") | |
Run % "https://accounts.spotify.com/authorize" | |
. "?client_id=" this._client_id | |
. "&redirect_uri=" redirect | |
. "&response_type=code" | |
. "&scope=" RegExReplace(this._scopes, "\h+", "%20") | |
} | |
Rest(Method, Endpoint, Body := "", Headers := "") { | |
this._Refresh() | |
if (!IsObject(Headers)) | |
Headers := {} | |
Headers.Authorization := "Bearer " this._access_token | |
http := ObjBindMethod(this.http, Method) | |
try | |
response := http.Call(Endpoint, Body, Headers) | |
catch | |
return {"error":true, "error_description":"Couldn't complete request."} | |
try | |
return response.Json | |
catch | |
return {"error":true, "error_description":"JSON parse error."} | |
} | |
; Private | |
_Access(Code) { | |
ep := "https://accounts.spotify.com/api/token" | |
body := { "client_id": this._client_id | |
, "client_secret": this._client_secret | |
, "code": Code | |
, "grant_type": "authorization_code" | |
, "redirect_uri": "http://localhost:" this._port "/" } | |
try { | |
response := this.http.Post(ep, body).Json | |
if (!response.HasKey("access_token")) | |
throw | |
} catch e { | |
throw Exception("Couldn't get authorization code.", -1) | |
} | |
this._Persist("access_token", response.access_token) | |
this._Persist("expires_at", this._Epoch() + response.expires_in) | |
if (response.refresh_token) | |
this._Persist("refresh_token", response.refresh_token) | |
} | |
_Epoch() { | |
epoch := A_NowUTC | |
epoch -= 19700101, Seconds | |
return epoch | |
} | |
_Persist(Key, Value) { | |
this["_" Key] := Value | |
IniWrite % " " Value, % this._ini, % this._section, % Key | |
} | |
_Refresh() { | |
epoch := this._Epoch() | |
if (epoch < this._expires_at) | |
return | |
ep := "https://accounts.spotify.com/api/token" | |
body := { "client_id": this._client_id | |
, "client_secret": this._client_secret | |
, "grant_type": "refresh_token" | |
, "refresh_token": this._refresh_token } | |
try { | |
response := this.http.Post(ep, body).Json | |
if (!response.HasKey("access_token")) | |
throw | |
} catch e { | |
this.Auth() | |
return | |
} | |
this._Persist("access_token", response.access_token) | |
this._Persist("expires_at", epoch + response.expires_in) | |
if (response.refresh_token) | |
this._Persist("refresh_token", response.refresh_token) | |
} | |
_Socket(Instance) { | |
conn := Instance.Accept() | |
request := conn.RecvText() | |
request := StrSplit(request, A_Space,, 3)[2] | |
isError := InStr(request, "error") | |
RegExMatch(request, "code=\K[^&]+", code) | |
if (code || isError) { | |
txt := "HTTP/1.1 200 OK" "`r`n" | |
txt .= "Connection: Close" "`r`n" | |
txt .= "Content-Type: text/html" "`r`n" | |
txt .= "`r`n" | |
; Content in quirks mode | |
txt .= "<title>Spotify Authorization</title>" | |
txt .= "<h1>You can close this tab now.</h1>" | |
} else { | |
txt := "HTTP/1.1 204 No Content" "`r`n" | |
txt .= "Connection: Close" "`r`n" | |
txt .= "`r`n" | |
} | |
conn.SendText(txt) | |
conn.Disconnect() | |
if (code) | |
this._Access(code) | |
if (code || isError) | |
Instance.Disconnect() | |
if (isError) | |
throw Exception("Permission not granted, can't continue.", -1) | |
} | |
__Delete() { | |
ObjRelease(this.http) | |
this.http := "" | |
} | |
} |
| |
; Version: 2022.11.11.1 | |
; About: https://redd.it/on9gb5 | |
#Include %A_LineFile%\..\Spotify_Api.ahk | |
class Spotify_Status extends Spotify_Api { | |
CurrentlyPlaying() { | |
endpoint := "https://api.spotify.com/v1/me/player/currently-playing" | |
response := this.Rest("GET", endpoint) | |
if (response.HasKey("error")) { | |
MsgBox 0x40010, Error, CurrentlyPlaying(), % response.error_description | |
Exit | |
} | |
return response | |
} | |
} |