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 | |
| } | |
| } |