Skip to content

Instantly share code, notes, and snippets.

@anonymous1184
Last active January 3, 2025 18:24
Show Gist options
  • Save anonymous1184/64fe98f2d25c8faaeea11ebae7bf1014 to your computer and use it in GitHub Desktop.
Save anonymous1184/64fe98f2d25c8faaeea11ebae7bf1014 to your computer and use it in GitHub Desktop.
Spotify Keys (APP)
#Requires AutoHotkey v2.0
/*
If placing below any hotkeys that had a
previous thread change use the default:
*/
#MaxThreadsPerHotkey 1
/*
Change keys for something meaningful
This example is in alphabetical order
*/
1::Spotify.Next()
2::Spotify.Previous()
3::Spotify.Repeat()
4::Spotify.SeekBackward()
5::Spotify.SeekForward()
6::Spotify.Shuffle()
7::Spotify.TogglePlay()
8::Spotify.VolumeDown()
9::Spotify.VolumeUp()
; Show Spotify
0::Spotify.Restore()
; Dependency
#Include %A_LineFile%\..\Spotify.ahk
#Requires AutoHotkey v1.1
/*
If placing below any hotkeys that had a
previous thread change use the default:
*/
#MaxThreadsPerHotkey 1
/*
Change keys for something meaningful
This example is in alphabetical order
*/
1::Spotify.Next()
2::Spotify.Previous()
3::Spotify.Repeat()
4::Spotify.SeekBackward()
5::Spotify.SeekForward()
6::Spotify.Shuffle()
7::Spotify.TogglePlay()
8::Spotify.VolumeDown()
9::Spotify.VolumeUp()
; Show Spotify
0::Spotify.Restore()
; Dependency
#Include %A_LineFile%\..\Spotify1.ahk
#Requires AutoHotkey v2.0
; Version: 2023.06.18.1
; Usages and information: https://redd.it/orzend
class Spotify extends Spotify.Controls {
static _hWnd := 0, _OnExit := ""
static Restore() {
this._Win()
WinShow()
WinActivate()
Spotify._hWnd := 0
if (IsObject(Spotify._OnExit)) {
OnExit(Spotify._OnExit, 0)
Spotify._OnExit := ""
}
}
static _Win(Stash := false) {
static title := "-|Spotify ahk_exe i)Spotify\.exe"
DetectHiddenWindows(true)
SetTitleMatchMode("RegEx")
hWnd := WinExist(title)
if (hWnd = 0) {
MsgBox("Spotify is not running...", "Error", 0x40010)
Exit()
}
if (Spotify._hWnd = hWnd) {
return
}
Spotify._hWnd := hWnd
visible := DllCall("IsWindowVisible", "Ptr", hWnd, "Int")
RunWait("Spotify:")
WinWait(title)
if (Stash && !visible) {
WinWaitActive()
WinHide()
if (!IsObject(Spotify._OnExit)) {
Spotify._OnExit := ObjBindMethod(Spotify, "Restore")
OnExit(Spotify._OnExit, 1)
}
}
}
class Controls {
static _shortcuts := Map("Next", "^{Right}", "Previous", "^{Left}", "Repeat", "^r", "SeekBackward", "+{Left}", "SeekForward", "+{Right}", "Shuffle", "^s", "TogglePlay", "{Space}", "VolumeDown", "^{Down}", "VolumeUp", "^{Up}")
static __Call(Action, *) {
static WM_APPCOMMAND := 793
if (!this._shortcuts.Has(Action)) {
throw Error("Invalid action." Action, -1)
}
shortcut := this._shortcuts[Action]
hActive := WinExist("A")
this._Win(true)
ControlFocus("Chrome Legacy Window")
ControlSend(shortcut, "Chrome Legacy Window")
if (Action ~= "i)^(Next|Previous|TogglePlay)$") {
hWnd := DllCall("FindWindow", "Str", "NativeHWNDHost", "Ptr", 0)
try PostMessage(WM_APPCOMMAND, 0x000C, 0xA0000, , hWnd)
}
WinActivate(hActive)
}
}
}
#Requires AutoHotkey v1.1
; Version: 2023.06.18.1
; Usages and information: https://redd.it/orzend
class Spotify extends Spotify.Controls {
static _hWnd := 0, _OnExit := ""
Restore() {
this._Win()
WinShow
WinActivate
Spotify._hWnd := 0
if (IsObject(Spotify._OnExit)) {
OnExit(Spotify._OnExit, 0)
Spotify._OnExit := ""
}
}
_Win(Stash := false) {
static title := "-|Spotify ahk_exe i)Spotify\.exe"
DetectHiddenWindows On
SetTitleMatchMode RegEx
hWnd := WinExist(title)
if (hWnd = 0) {
MsgBox 0x40010, Error, Spotify is not running...
Exit
}
if (Spotify._hWnd = hWnd) {
return
}
Spotify._hWnd := hWnd
visible := DllCall("IsWindowVisible", "Ptr", hWnd, "Int")
RunWait Spotify:
WinWait % title
if (Stash && !visible) {
WinWaitActive
WinHide
if (!IsObject(Spotify._OnExit)) {
Spotify._OnExit := ObjBindMethod(Spotify, "Restore")
OnExit(Spotify._OnExit, 1)
}
}
}
class Controls {
static _shortcuts := { Next: "^{Right}", Previous: "^{Left}", Repeat: "^r", SeekBackward: "+{Left}", SeekForward: "+{Right}", Shuffle: "^s", TogglePlay: "{Space}", VolumeDown: "^{Down}", VolumeUp: "^{Up}" }
__Call(Action, _*) {
static WM_APPCOMMAND := 793
if (!this._shortcuts.HasKey(Action)) {
throw Exception("Invalid action." Action, -1)
}
shortcut := this._shortcuts[Action]
hActive := WinExist("A")
this._Win(true)
ControlFocus Chrome Legacy Window
ControlSend Chrome Legacy Window, % shortcut
if (Action ~= "i)^(Next|Previous|TogglePlay)$") {
hWnd := DllCall("FindWindow", "Str", "NativeHWNDHost", "Ptr", 0)
try PostMessage WM_APPCOMMAND, 0x000C, 0xA0000, , % "ahk_id" hWnd
}
WinActivate % "ahk_id" hActive
}
}
}
--- Spotify.ahk
+++ Spotify1.ahk
@@ -1 +1 @@
-#Requires AutoHotkey v2.0
+#Requires AutoHotkey v1.1
@@ -10 +10 @@
- static Restore() {
+ Restore() {
@@ -12,2 +12,2 @@
- WinShow()
- WinActivate()
+ WinShow
+ WinActivate
@@ -21 +21 @@
- static _Win(Stash := false) {
+ _Win(Stash := false) {
@@ -23,2 +23,2 @@
- DetectHiddenWindows(true)
- SetTitleMatchMode("RegEx")
+ DetectHiddenWindows On
+ SetTitleMatchMode RegEx
@@ -27,2 +27,2 @@
- MsgBox("Spotify is not running...", "Error", 0x40010)
- Exit()
+ MsgBox 0x40010, Error, Spotify is not running...
+ Exit
@@ -35,2 +35,2 @@
- RunWait("Spotify:")
- WinWait(title)
+ RunWait Spotify:
+ WinWait % title
@@ -38,2 +38,2 @@
- WinWaitActive()
- WinHide()
+ WinWaitActive
+ WinHide
@@ -49 +49 @@
- static _shortcuts := Map("Next", "^{Right}", "Previous", "^{Left}", "Repeat", "^r", "SeekBackward", "+{Left}", "SeekForward", "+{Right}", "Shuffle", "^s", "TogglePlay", "{Space}", "VolumeDown", "^{Down}", "VolumeUp", "^{Up}")
+ static _shortcuts := { Next: "^{Right}", Previous: "^{Left}", Repeat: "^r", SeekBackward: "+{Left}", SeekForward: "+{Right}", Shuffle: "^s", TogglePlay: "{Space}", VolumeDown: "^{Down}", VolumeUp: "^{Up}" }
@@ -51 +51 @@
- static __Call(Action, *) {
+ __Call(Action, _*) {
@@ -53,2 +53,2 @@
- if (!this._shortcuts.Has(Action)) {
- throw Error("Invalid action." Action, -1)
+ if (!this._shortcuts.HasKey(Action)) {
+ throw Exception("Invalid action." Action, -1)
@@ -59,2 +59,2 @@
- ControlFocus("Chrome Legacy Window")
- ControlSend(shortcut, "Chrome Legacy Window")
+ ControlFocus Chrome Legacy Window
+ ControlSend Chrome Legacy Window, % shortcut
@@ -63 +63 @@
- try PostMessage(WM_APPCOMMAND, 0x000C, 0xA0000, , hWnd)
+ try PostMessage WM_APPCOMMAND, 0x000C, 0xA0000, , % "ahk_id" hWnd
@@ -65 +65 @@
- WinActivate(hActive)
+ WinActivate % "ahk_id" hActive
@OmTatSat
Copy link

"I did some slight modifications to the Acc core functions to speedup DLL access"
Where i can download it?

@OmTatSat
Copy link

Can came thru authenticitication with this scripts https://github.com/CloakerSmoker/Spotify.ahk
But the only data i can get is thru MsgBox, % Spoofy.CurrentUser.SubscriptionLevel and it is retrieve word "open"
Default hot keys in example return error "PREMIUM_REQUIRED: Player command failed: Premium required"

@anonymous1184
Copy link
Author

Your AutoHotkey version is fine, any version 1.1 from the last couple of years should do the trick. As for Spotify, when using the API the client has nothing to do (you might as well don't have Spotify in the computer and control your phone App from AHK).

I can't see any error.
when i remove file options.ini or folder Lib, i can't see any error either

I tested that thoughtfully and some of the helpers in the Subreddit did too, it worked fine for them also.

Use the #Warn directive (with All argument) to help you spot any issue, still the best way to debug anything is with a proper tool; I use VSCode, here's how it looks debugging an object and here's a guide I wrote, if you don't like it other editors also have debugging capabilities.

You error (or lack thereof) seems a missing SpotifyStatus class (is in the same gist as SpotifyAPI). Make sure you have the following structure:

options.ini            ; https://redd.it/on9g8t - Configuration
spotify.ahk            ; https://redd.it/on9gb5 - Example OSD code
Lib\SpotifyAPI.ahk     ; https://git.io/JWFfe   - Automatic token handling
Lib\SpotifyStatus.ahk  ;                        - Actual API currently-playing method

; Dependencies

Lib\Socket.ahk         ; https://git.io/Jc9Sn - For TCP connections
Lib\WinHttpRequest.ahk ; https://git.io/JnMcX - Handle web requests

If you find that to be too much, you can put together everything, I do it like this as is the proper way of doing it but that's up to the end user.

You'll end up with something like this. The JSON coming from that call looks like this and the object should be something like this. And like I've said, there's no validations on that code and is just a dump on the currently playing information.


The small mods I made to the Acc core are in the getUrl() gist, basically all you have to do is pre-load the library and get the entry points for the functions in the DLL beforehand. You can do the same with the rest of the functions. But again, cache the objects once you find them so you only look for them once.


Never knew another project of Spotify handling its API exists. I just skimmed through the code, seems like they used their own app for everyone to use; I went with a personal approach on this (for privacy to avoid others to track me and my usage) and the way I did it was more generic so anyone can write what actually needs instead of disclosing all my information and requiring all the scopes.

However different, both projects have the same ideas behind them... I guess is just that my brain is hardwired to code as if I were in a Corporate environment, damn force of habit :P

Good luck!

@OmTatSat
Copy link

OmTatSat commented Aug 23, 2021

Yes, i don't had Lib\SpotifyStatus.ahk
also i add to example global spotify := SpotifyStatus("options.ini")
spotify.auth()

and have error
C:\staff\Spotify\web\Lib\SpotifyStatus.ahk (10) : ==> Unknown class. Specifically: SpotifyAPI
but i have Lib\SpotifyAPI.ahk
i don't know why it can't find it.
I tried move all libs to example script, then i have parse error i think in this

_toJSON(str)
    {
        return this._parser.Call(str)
    }

Maybe you can archive your folder with right files and structure? And i am just fill client_id and client_secret?

My example OSD is like this now, correct?
without

global spotify := SpotifyStatus("options.ini")
spotify.auth()

it just do nothing, like i said before(

global spotify := SpotifyStatus("options.ini")
spotify.auth()

global nowPlaying := ""
    , spotify := new SpotifyStatus("options.ini")


F2::nowPlaying()

nowPlaying()
{
    res := spotify.currentlyPlaying()
    res := spotify._toJSON(res) ; or JSON.Load(res)
    info := { ""
        . "album"       : res.item.album.name
        , "artist"      : res.item.artists.0.name
        , "cover"       : res.item.album.images.1.url
        , "track_number": res.item.track_number
        , "track"       : res.item.name }
    UrlDownloadToFile % info.cover, % A_Temp "\img"
    Gui 1:New, +AlwaysOnTop -Caption
    Gui Add, Picture, w300 h-1 x0 y0, % A_Temp "\img"
    Gui Add, Text, -Wrap w300 x5, % nowPlaying := info.artist " - " info.album " - " Format("{:02}", info.track_number) ". " info.track
    Gui Show, w300 h325
    SetTimer marquee, 300
}

marquee()
{
    static position := 0
    len := StrLen(nowPlaying)
    position += position = len ? -len : 1
    GuiControl Text, Static2, % SubStr(nowPlaying Format("{: 10}", ""), position) nowPlaying
}

GuiClose:
GuiEscape:
    SetTimer marquee, Delete
    Gui Destroy
return

@anonymous1184
Copy link
Author

@OmTatSat

Here but be sure to read about libraries. If the file is not included it might be auto-loaded if explicitly called not implicitly.

@OmTatSat
Copy link

Thank you, for now i have parse error.

Error in #include file "C:\staff\Spotify\test\Lib\SpotifyAPI.ahk":
     0x80020101 - 

Specifically: parse

	Line#
	133: RegExMatch(request, "\bcode=(?<Code>[^&]+)", match)  
	134: this._code := matchCode  
	135: this._access()  
	136: Send,^{F4}
	137: }
	138: }
	141: {
--->	142: Return,this._parser.Call(str)
	143: }
	146: {
	147: this._http := this._doc := this._parser := ""  
	148: }
	005: {
	007: Return,isObject(instance) ? instance : instance := new %A_ThisFunc%(params*)
	010: }

Continue running the script?

@OmTatSat
Copy link

I compared files, in Lib\SpotifyStatus.ahk i don't have #Include
and in example in

global nowPlaying := ""
    , spotify := new SpotifyStatus("options.ini")

i have "new". Without "new" i have parse error, with "new" have not any error, but "- - 00. - - 00." in GUI

What can cause parse problem, how to fix it?

@anonymous1184
Copy link
Author

Can you keep your replies in a single comment?

The idea of me sending a zip file is for you to use it regardless of what you have, it works, I tested it. Literally you only need to add your client ID/Secret. If for some reason it doesn't work try debugging it (use the VSCode guide I provided, or at very least print what's in the string). Whatever it is, is on your side.

@OmTatSat
Copy link

OmTatSat commented Aug 24, 2021

Of course, sorry.
I don't know why, but it started working)) i tried other version of ahk, and it is worked, then i tried my default version of ahk and it is worked too))
Thank you for your patience!)

I noticed that the script displays only the first artist, how can I display all the artists?

I tried like this

    info := { ""
        . "album"       : res.item.album.name
        , "artists"      : (res.item.artists.0.name ", " res.item.album.artists.1.name ", " res.item.album.artists.2.name ", " res.item.album.artists.3.name)

But when there is less then 4 artists, it is show an error at missed artists((

@anonymous1184
Copy link
Author

Glad you got it working @OmTatSat!

Regarding the artists, that's something you have to find out for yourself. I've been listening exclusively to metal for the last 30 years and having multiple artists is something not seen so I never looked into that.

For the sake of simplicity I used a JSON parser from a COM object, is very restrictive as it doesn't let you iterate (I explained that in the original posts). You might want to use cocobelgica's (or any other) that loads the JSON string into a native AHK object. The screenshot I previously shared of VSCode is precisely that.

However if you stick with the COM object use try statements:

artist0 := ""
try artist0 := res.item.artist.0.name
artist1 := ""
try artist1 := res.item.artist.1.name
artist2 := ""
try artist2 := res.item.artist.2.name
artist3 := ""
try artist3 := res.item.artist.3.name
MsgBox % "Artist 0: " artist0
    . "`n Artist 1: " artist1
    . "`n Artist 2: " artist2
    . "`n Artist 3: " artist3

I know it looks awful but you can't do it with a loop. If you have issues I recommend you to use r/AutoHotkey as there's lots of people that can help.

@OmTatSat
Copy link

@anonymous1184
Great solution! Thank you, it is very useful for me

@NeonLightning
Copy link

i feel like i'm missing something but surprisingly this is pretty much the only script i've found that'll handle spicefied version. but i've noticed when i first run a hotkey it focuses the window(well activates it in the background) but doesn't do anything until i then minimize and restore spotify.

@anonymous1184
Copy link
Author

@NeonLightning: given that I no longer use v1.1, I added the v2.0 version and updated a little the code, adding an extra step to make sure the focused window was not lost (no core functionality was changed).

Check the README.md for version information and the Reddit post for overall details.

@NeonLightning
Copy link

ah shame i use it with vmr.ahk to control voicemeeter and that's a 1.0 thanks anyways

@anonymous1184
Copy link
Author

@NeonLightning like I've said, check the README.md it contains both versions.

@NeonLightning
Copy link

yup finally figured it out. my apologies

@NeonLightning
Copy link

workin great btw.
the other one i used that used api calls stopped working for me but your um... unique method. is working still :)

@anonymous1184
Copy link
Author

@NeonLightning Thanks for letting me know, I'll check out the API calls and revisit the code as there are improved methods I'd like to implement for that one too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment