twolfson / google-music.js

Browser-side JS library for controlling Google Music
The Unlicense
47 stars 6 forks source link

Feature/queue #38

Closed MarshallOfSound closed 8 years ago

MarshallOfSound commented 8 years ago

As per #36 this PR adds in queue querying and controlling functionality.

You can now:

/cc @gmusic-utils/gmusic-js @twolfson

MarshallOfSound commented 8 years ago

/cc @mmazzarolo

twolfson commented 8 years ago

Sooo we have a fundamental unanswered question here... This is manipulating the UI in a manner that the user can see/disrupts any existing user interaction (i.e. opening the queue panel). I don't feel comfortable manipulating the UI as we stop becoming a low-level integration and start becoming a set of scripts.

I would prefer to find other ways (e.g. talking to Polymer application directly).

MarshallOfSound commented 8 years ago

@twolfson The problem with interfacing with Polymer is that Google have made a whole bunch of custom polymer components for GPM and there API's are all private and hidden and obfuscated. Every time Google update GPM the function names (that are obfuscated) change, so even if we could find the current obfuscated polymer method to call now it won't work the second Google update GPM.

IMO this is the least interfering UI experience you can make. It at "worst" interferes with the UI for 40ms, the animation to show the queue takes 400ms. So the worst thing that can happen is that you don't see 1/10th of the opening animation :+1:

twolfson commented 8 years ago

Do we have proof that the data is obfuscated? In my experience, global objects and their properties aren't obfuscated since they must allow access to scripts outside of their scopes (e.g. other <script> tags)

MarshallOfSound commented 8 years ago

I have witnessed the variables change over time as a while ago I was exploiting a method inside GPM but then the next update it function name changed. As far as I can tell their are only two scripts that the entire GPM application so everything is scoped to a single file so they don't have to worry about scripts outside of scope.

twolfson commented 8 years ago

That's still not evidence that the queue-related portion of the code will be dynamic; variables like USER_ID and USER_CONTEXT stay consistent =/

MarshallOfSound commented 8 years ago

@twolfson It's impossible to prove that something won't change. You can only assume that at some point it will, especially if it is unofficial and undocumented.

And FWIW the queue related parts of the code (that I can find by tracing) are nested inside function scopes and therefore aren't globally available.

twolfson commented 8 years ago

Similarly the UI can change on us and historically selectors have broken in frustrating ways (e.g. upgrading users in groups https://github.com/twolfson/google-music-electron/issues/22#issuecomment-150988801). In the end, global objects and UI are more/less equally stable in the long term.

Can you link me to the queue related parts of the code?

MarshallOfSound commented 8 years ago

Can you link me to the queue related parts of the code?

Not easily, it's all minified and I have the code at breakpoints in a chrome devtools extension that unminifies it for me. (Better than the built in tools). Give me a bit I'll see what I can do :+1:

MarshallOfSound commented 8 years ago

@twolfson I can't find a way to link to a section of minified code from dev tools so you'll have to find it yourself or believe me when I say we can't hook in to it :laughing:

Although we are technically using the UI (and changing it) if we do some math we can see that we are possibly interrupting the UI for <= 40ms.

This is somewhere between 2 - 3 frames of rendering (assuming a 60FPS render in chrome). And this won't even affect usage of GPM because if they attempt to open the queue during those 2 - 3 frames it will still open, just 2 - 3 frames slower.

twolfson commented 8 years ago

Maybe post the beautified source code to gist.github.com and link me to a line number? Or screenshot it? Or quote the code blocks that are good evidence?

MarshallOfSound commented 8 years ago

@twolfson Hows this

https://gist.github.com/MarshallOfSound/3a5dc38575f6f0d0a2b8a9164df5af76

I've removed about 99000 lines for you there :laughing: but basically all the GPM logical is wrapped up inside a massive self calling function. The queue logic (some of which I posted there) is also inside that scope

twolfson commented 8 years ago

Cool, thanks. I'm still not entirely sold on this though =/ Google Music could optimize its references by assigning variables via pass-by reference (might be good if names are out of GZIP range).

I have a silly idea that might get us insight but I don't have time to explore it at the moment:

Run JSON.stringify(window) with something like json-stringify-safe and search for the current song id in the JSON blob. If it's not there, then we know it's not accessible via the global scope (at least not unless we invoke a function...)

MarshallOfSound commented 8 years ago

@twolfson You can't JSON.stringify(window) because for some reason (I'm assuming Polymer magic) there is a circular dependency inside window.

twolfson commented 8 years ago

That's why you would use json-stringify-safe ;)

MarshallOfSound commented 8 years ago

@twolfson And that crashes Chrome :+1: :laughing:

Too much stuff for it too handle I think :D

twolfson commented 8 years ago

Yea, prob needs to be done with some items omitted (e.g. _.omit) and max depth parameters =/

MarshallOfSound commented 8 years ago

After strategic _.omit I can somewhat confirm that the queue isn't stored in the Window object.

twolfson commented 8 years ago

Can you provide an example of the code you ran? (still haven't gotten around to this myself) =/

MarshallOfSound commented 8 years ago

@twolfson Gimme a sec. I totally did it in dev tools so I didn't save it :laughing:

MarshallOfSound commented 8 years ago

@twolfson This is interesting, one of the things I was omitting (it was full of circular dependencies appears to contain some interesting data). I still think that path is unstable due to obfuscation.

But this path in GPM

window.APPCONTEXT.Go.q[0].hb.Eg

appears to be a list of all your playlists and their associated information :thought_balloon:

MarshallOfSound commented 8 years ago

OK, so I just found a REALLY obscure path that appears to give the current queue.

window.APPCONTEXT.Go.h[0].Hi.queue.Yc.items
MarshallOfSound commented 8 years ago

@twolfson You can fetch all the queue information through these methods

// First song in queue
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0]

// Proper song object
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].R()

// AlbumArt
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].Jc()

// Artist
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].Gb()

// Album
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].mc()

// AlbumArtist
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].cl()

// Lowercase Title
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].Qn()

// Lowercase artist
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].wj()

// Lowercase album
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].nm()

// Lowercase albumArtist
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].lm()

// Type / Genre of song
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].Re()

// Length of song in milliseconds
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].nf()

// UNKNOWN: Returns a number
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].ua()
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].Ima()

// Get play count
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].lv()

// Get rating (0=Neutral, 1=Down, 5=Up)
window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].cf()

// Get if playing
!!window.APPCONTEXT.Go.h[0].Hi.queue.qc()[0].Td[46]

The problem is. There is no play method. So we have read only access to the queue. I'll keep exploring but the UI is probably the way to go to control GPM

twolfson commented 8 years ago

Wow, nice work on the research :+1: imo the current playSong implementation is limited as the song must already be queued up =/

MarshallOfSound commented 8 years ago

OK,so I am like 99.999% sure that the method that plays songs is not accessible by us. It is a method on any object of type MO but the MO class is scoped (not accessible from the global object) and all instances of that class are not returned through any functions.

I stand by the UI implementation :+1:

twolfson commented 8 years ago

Alright, if we are going to go with a UI implementaton, then I'm on the fence about keeping it in gmusic.js. One alternative would be to build a gmusic-ui.js which extends window.GMusic.

Part of my reasoning behind this would be as a JS library, I don't expect my UI to be altered unless I request it (e.g. toggle visualization should start playing visualizations). I'm thinking beyond the current queue implementation, for example when we wanted methods to play any song, album, or artist which required navigating pages and whatnot.

MarshallOfSound commented 8 years ago

@twolfson Fair call, I had a feeling that's where this would end up. I already have a framework for extending the GMusic prototype (I've been doing it for a while with mini-player and playlists :+1:

Are we in agreement then that we should make gmusic-utils/gmusic-ui.js and put things like playlists and queue control in there?

twolfson commented 8 years ago

Yep, let's pull the trigger on making gmusic-ui.js. If we ever discover ways to avoid UI altering methods, then we can always add them back in here =)

I have set up a team for gmusic-ui.js and gmusic-ui.js-admins as well as a repo. I will let you take over from here :ship:

MarshallOfSound commented 8 years ago

onwards_my_trusty_steed_by_megrim96-d4uqp8f

MarshallOfSound commented 8 years ago

@twolfson I just pushed up some magical playlist code if you wanna take a look at how magical people do things :+1: :laughing:

https://github.com/gmusic-utils/gmusic-ui.js

twolfson commented 8 years ago

Why wasn't there a PR? That's the ideal venue for code review =(

MarshallOfSound commented 8 years ago

@twolfson Urgh, I keep forgetting with these new repos to PR into an empty master :cry:

Let me set something up for you to look at

twolfson commented 8 years ago

I usually do a placeholder set of files via a boilerplate and then do a PR onto that master. Otherwise, git can get mad screwy with default branches.

Example: https://github.com/underdogio/resolve-link/commit/668bc9846986781ac9ddd129bb5ad4731f7a87c6

MarshallOfSound commented 8 years ago

@twolfson Yeah, I had to setup some funky branches. But it's up on GH now

https://github.com/gmusic-utils/gmusic-ui.js/pull/1/files