Closed MarshallOfSound closed 8 years ago
/cc @mmazzarolo
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).
@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:
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)
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.
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 =/
@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.
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?
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:
@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.
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?
@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
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...)
@twolfson You can't JSON.stringify(window)
because for some reason (I'm assuming Polymer magic) there is a circular dependency inside window.
That's why you would use json-stringify-safe
;)
@twolfson And that crashes Chrome :+1: :laughing:
Too much stuff for it too handle I think :D
Yea, prob needs to be done with some items omitted (e.g. _.omit
) and max depth parameters =/
After strategic _.omit
I can somewhat confirm that the queue isn't stored in the Window object.
Can you provide an example of the code you ran? (still haven't gotten around to this myself) =/
@twolfson Gimme a sec. I totally did it in dev tools so I didn't save it :laughing:
@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:
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
@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
Wow, nice work on the research :+1: imo the current playSong
implementation is limited as the song must already be queued up =/
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:
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.
@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?
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:
@twolfson I just pushed up some magical playlist code if you wanna take a look at how magical people do things :+1: :laughing:
Why wasn't there a PR? That's the ideal venue for code review =(
@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
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
@twolfson Yeah, I had to setup some funky branches. But it's up on GH now
As per #36 this PR adds in
queue
querying and controlling functionality.You can now:
change:queue
/cc @gmusic-utils/gmusic-js @twolfson