jwdempsey / GoogleMusic.bundle

Plex Google Music Plugin
65 stars 18 forks source link

Update gmusicapi and changes webclient with mobileclient #13

Closed pablorusso closed 7 years ago

pablorusso commented 8 years ago

Web protocol was deprecated by google, so I have updated the gmusicapi and changes gmusic to use only mobileclient.

The new gmusicapi depends on pycrypto which was a PITA to build and use inside a windows plex, but its is working. I had tested it only in a windows server.

The trick is to compile it with plex python and vs2013 as plex uses a custom python 2.7 build (plexsripthost) that depednds on msvcrt120 instead of msvcrt90 (like regular python 2.7 does)

jwdempsey commented 8 years ago

This is fantastic! I'll give this a try locally and make sure it works in OSX as well.

gismo112 commented 8 years ago

It works nice. Thank you :)

dpeukert commented 8 years ago

I'm getting this error on PMS v0.9.12.11 and Ubuntu Server 15.04.

2015-08-31 13:20:33,367 (7f18e622f700) :  CRITICAL (core:613) - Exception starting plug-in (most recent call last):

  File "bundles-release/Framework.bundle-dist/Contents/Resources/Versions/2/Python/Framework/core.py", line 606, in start

  File "bundles-release/Framework.bundle-dist/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 256, in execute

  File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Code/__init__.py", line 4, in <module>

    from gmusic import GMusic, CallFailure, API

  File "bundles-release/Framework.bundle-dist/Contents/Resources/Versions/2/Python/Framework/code/sandbox.py", line 345, in __import__

ImportError: cannot import name _counter
pablorusso commented 8 years ago

The fix includes the binaries only for windows.

For all on Linux: You have to download Crypto from https://pypi.python.org/pypi/pycrypto, and then build it: python setup.py build.

Then copy the Crypto from the "build" directory to replace the Crypto directory.

The entire path to the folder you need to copy is 'build/lib.linux-x86_64-2.7/Crypto'. It needs to go into '/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared'.

Make sure you copy the 'Crypto' folder and not just the contents of it. Also make sure plex has proper access to it by doing 'sudo chown -R plex:plex Crypto' (or whatever user runs your Plex server).

Would be nice if someone on linux send a PR with this binaries to help others in linux.

r0n0c commented 8 years ago

Exact steps on Ubuntu

1) sudo apt-get install gcc python python-dev 2) wget https://pypi.python.org/packages/source/p/pycrypto/pycrypto-2.6.1.tar.gz 3) tar -xf pycrypto-2.6.1.tar.gz 4) cd pycrypto-2.6.1/ 5) python setup.py build 6) sudo cp -R build/lib.linux-x86_64-2.7/Crypto /var/lib/plexmediaserver/Library/Application\ Support/Plex\ Media\ Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/ 7) sudo chown -R plex:plex /var/lib/plexmediaserver/Library/Application\ Support/Plex\ Media\ Server/Plug-ins/

After doing this I can get the plugin to load when I don't have a username/password set. I get into it and I get a refresh Library button. After I set login credentials. (I use an a google App password since I have two-factor authentication enabled.) I get the same "This channel is not responding" error.

Log with no credentials set:

2015-09-16 12:33:10,203 (7fec53bde700) : DEBUG (runtime:717) - Handling request GET /music/googlemusic 2015-09-16 12:33:10,204 (7fec53bde700) : DEBUG (runtime:814) - Found route matching /music/googlemusic 2015-09-16 12:33:10,205 (7fec53bde700) : DEBUG (base:117) - Checking if com.plexapp.plugins.googlemusic is broken 2015-09-16 12:33:10,205 (7fec53bde700) : DEBUG (networking:166) - Requesting 'http://127.0.0.1:32400/:/plugins/com.plexapp.system/messaging/function/X1N0b3JlU2VydmljZTpJc0NoYW5uZWxCcm9rZW4_/Y2VyZWFsMQoxCmxpc3QKMApyMAo_/Y2VyZWFsMQoxCmRpY3QKMQpzMzEKY29tLnBsZXhhcHAucGx1Z2lucy5nb29nbGVtdXNpY3MxMAppZGVudGlmaWVycjAK' 2015-09-16 12:33:10,211 (7fec53bde700) : DEBUG (runtime:106) - Sending packed state data (112 bytes) 2015-09-16 12:33:10,211 (7fec53bde700) : DEBUG (runtime:918) - Response: [200] MediaContainer, 751 bytes

Setting credentials and trying to open plugin

2015-09-16 12:34:31,135 (7fec53bde700) : DEBUG (runtime:717) - Handling request GET /music/googlemusic/:/prefs 2015-09-16 12:34:31,136 (7fec53bde700) : DEBUG (runtime:814) - Found route matching /music/googlemusic/:/prefs 2015-09-16 12:34:31,138 (7fec53bde700) : DEBUG (runtime:106) - Sending packed state data (112 bytes) 2015-09-16 12:34:31,138 (7fec53bde700) : DEBUG (runtime:918) - Response: [200] MediaContainer, 363 bytes 2015-09-16 12:34:44,528 (7fec53bde700) : DEBUG (runtime:717) - Handling request GET /music/googlemusic/:/prefs/set?email=EMAILADDRESS&password=THISSHOULDNTBEPLAINTEXT 2015-09-16 12:34:44,529 (7fec53bde700) : DEBUG (runtime:814) - Found route matching /music/googlemusic/:/prefs/set 2015-09-16 12:34:44,530 (7fec53bde700) : DEBUG (preferences:198) - Saved the user preferences 2015-09-16 12:34:44,530 (7fec53bde700) : DEBUG (runtime:106) - Sending packed state data (112 bytes) 2015-09-16 12:34:44,530 (7fec53bde700) : DEBUG (runtime:918) - Response: [200] bool, 0 bytes 2015-09-16 12:34:48,294 (7fec53bde700) : DEBUG (runtime:717) - Handling request GET /music/googlemusic 2015-09-16 12:34:48,296 (7fec53bde700) : DEBUG (runtime:814) - Found route matching /music/googlemusic 2015-09-16 12:34:48,634 (7fec53bde700) : CRITICAL (runtime:883) - Exception (most recent call last): File "bundles-release/Framework.bundle-dist/Contents/Resources/Versions/2/Python/Framework/components/runtime.py", line 843, in handle_request File "bundles-release/Framework.bundle-dist/Contents/Resources/Versions/2/Python/Framework/handlers/base.py", line 111, in call File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Code/init.py", line 47, in MainMenu API.authenticate(Prefs['email'], Prefs['password']) File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusic.py", line 66, in authenticate self._set_all_access() File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusic.py", line 38, in _set_all_access settings = self._webclient._make_call(webclient.GetSettings, '') File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusicapi/clients/shared.py", line 80, in _make_call return protocol.perform(self.session, self.validate, _args, *_kwargs) File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusicapi/protocol/shared.py", line 208, in perform response = session.send(req_kwargs, cls.required_auth) File "/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusicapi/session.py", line 81, in send raise NotLoggedIn NotLoggedIn

2015-09-16 12:34:48,636 (7fec53bde700) : DEBUG (runtime:106) - Sending packed state data (112 bytes) 2015-09-16 12:34:48,636 (7fec53bde700) : DEBUG (runtime:918) - Response: [500] 1795 bytes

imthenachoman commented 8 years ago

@r0n0c Were you able to get it to work with those steps?

r0n0c commented 8 years ago

@imthenachoman nope. Kept getting errors.

Then I realized everything I play plex on is either a nexus player or chromecast enabled so I don't need it. And once All Access Family plan is up and running girlfriend won't need it either.

imthenachoman commented 8 years ago

@r0n0c Oh. Thanks!

benshemmeld commented 8 years ago

This fix works nicely! Now I'm just having an issue on my Roku 3. Any chance we could get this fix merged into master?

shoguevara commented 8 years ago

Hi all! Thanks Pablo for your efforts! I really appreciate it! Anyway, I had this plugin up and running till past week. I decided to enable 2way authentication with Google and In spite of generating and using app password the plugin stopped working and I got "Channel not responding" error. I disabled 2way authentication, but it didn't help. Plugin log is here http://pastebin.com/nk2qWyRk Running it on Win 10 //========================= UPD: Sorry, guys. My bad! It appeared to be a local problem. Works fine with fresh windows install - had to test it on VM.

samm-git commented 8 years ago

I can confirm, it does not work on OSX with Crypto installed:

2016-01-18 10:54:59,967 (7000024af000) :  DEBUG (runtime:717) - Handling request GET /music/googlemusic
2016-01-18 10:54:59,968 (7000024af000) :  DEBUG (runtime:814) - Found route matching /music/googlemusic
2016-01-18 10:54:59,971 (7000024af000) :  CRITICAL (runtime:889) - Exception (most recent call last):
  File "bundles-release/Framework.bundle-dist-ninja/Contents/Resources/Versions/2/Python/Framework/components/runtime.py", line 843, in handle_request
  File "bundles-release/Framework.bundle-dist-ninja/Contents/Resources/Versions/2/Python/Framework/handlers/base.py", line 111, in call
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Code/__init__.py", line 47, in MainMenu
    API.authenticate(Prefs['email'], Prefs['password'])
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusic.py", line 56, in authenticate
    mcauthenticated = self._mobileclient.login(email, password, Mobileclient.FROM_MAC_ADDRESS)
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusicapi/clients/mobileclient.py", line 63, in login
    if not self.session.login(email, password, android_id):
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gmusicapi/session.py", line 163, in login
    res = gpsoauth.perform_master_login(email, password, android_id)
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gpsoauth/__init__.py", line 56, in perform_master_login
    'EncryptedPasswd': google.signature(email, password, android_key_7_3_29),
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/gpsoauth/google.py", line 51, in signature
    encrypted_login = cipher.encrypt((email + u'\x00' + password).encode('utf-8'))
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/Crypto/Cipher/PKCS1_OAEP.py", line 164, in encrypt
    m = self._key.encrypt(em, 0)[0]
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/Crypto/PublicKey/RSA.py", line 150, in encrypt
    return pubkey.pubkey.encrypt(self, plaintext, K)
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/Crypto/PublicKey/pubkey.py", line 75, in encrypt
    ciphertext=self._encrypt(plaintext, K)
  File "/Users/asamorukov/Library/Application Support/Plex Media Server/Plug-ins/GoogleMusic.bundle/Contents/Libraries/Shared/Crypto/PublicKey/RSA.py", line 224, in _encrypt
    return (self.key._encrypt(c),)
ValueError: Plaintext too large

Update: it works when pycrypto compiled for the py-27

shoguevara commented 8 years ago

For some reasons I started getting the error I mentioned above - https://github.com/jwdempsey/GoogleMusic.bundle/pull/13#issuecomment-169540547 Only fix I could find is using my actual device Android ID in gmusic.py file. So, changing the line _mcauthenticated = self._mobileclient.login(email, password, Mobileclient.FROM_MACADDRESS) to _mcauthenticated = self._mobileclient.login(email, password, "myadroiddeviceid")_ did the trick. Could anyone please look into it?

samm-git commented 8 years ago

I finally been able to get it running, thank you! When testing i found one very easy to fix error - for the artists in non-ASCII it wont work correctly. I patches this issue and now it works fine. Patch provided below:

diff --git a/Contents/Libraries/Shared/gmusic.py b/Contents/Libraries/Shared/gmusic.py
index 94417ad..7aa3b5a 100644
--- a/Contents/Libraries/Shared/gmusic.py
+++ b/Contents/Libraries/Shared/gmusic.py
@@ -133,13 +133,13 @@ class GMusic(object):
     def get_tracks_for_type(self, type, name):
         type = type.lower()
         if type == 'artists':
-            return self.tracks_by_artist[name]
+            return self.tracks_by_artist[name.decode('utf_8')]
         elif type == 'albums':
-            return self.tracks_by_album[name]
+            return self.tracks_by_album[name.decode('utf_8')]
         elif type == 'genres':
-            return self.tracks_by_genre[name]
+            return self.tracks_by_genre[name.decode('utf_8')]
         elif type == 'songs by letter':
-            return self.tracks_by_letter[name]
+            return self.tracks_by_letter[name.decode('utf_8')]
         else:
             return {}
samm-git commented 8 years ago

And one more unicode related fix: display title2 for non-ASCII songs/artists:

diff --git a/Contents/Code/__init__.py b/Contents/Code/__init__.py
index 1a9b51b..a4f2998 100644
--- a/Contents/Code/__init__.py
+++ b/Contents/Code/__init__.py
@@ -288,7 +288,7 @@ def ShowSongs(title, shuffle=False, page=1):
 ################################################################################
 @route(PREFIX + '/getalbumsinlibrary')
 def GetAlbumsInLibrary(name):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))

     albums = API.get_albums_in_library(name)
     for album in sorted(albums['albums'], key = lambda x: x.get('year')):
@@ -307,7 +307,7 @@ def GetAlbumsInLibrary(name):
 ################################################################################
 @route(PREFIX + '/gettracklist')
 def GetTrackList(name, type):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))
     tracks = API.get_tracks_for_type(type, name)
     sort = 'title' if type == 'Songs By Letter' else 'trackType'
     for track in sorted(tracks, key = lambda x: x['track'].get(sort)):
@@ -318,7 +318,7 @@ def GetTrackList(name, type):
 ################################################################################
 @route(PREFIX + '/getplaylistcontents')
 def GetPlaylistContents(name, id):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))

     tracks = API.get_all_user_playlist_contents(id)
     for track in tracks:
@@ -334,7 +334,7 @@ def GetPlaylistContents(name, id):
 ################################################################################
 @route(PREFIX + '/getsharedplaylist')
 def GetSharedPlaylist(name, token):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))

     tracks = API.get_shared_playlist_contents(token)
     for track in tracks:
@@ -345,7 +345,7 @@ def GetSharedPlaylist(name, token):
 ################################################################################
 @route(PREFIX + '/getstationtracks')
 def GetStationTracks(name, id):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))

     tracks = API.get_station_tracks(id)
     for track in tracks:
@@ -363,7 +363,7 @@ def GetStationTracks(name, id):
 ################################################################################
 @route(PREFIX + '/genressubmenu', children=list)
 def GenresSubMenu(name, id, children=None):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))
     oc.add(DirectoryObject(key=Callback(CreateStation, id=id), title='Play ' + name))

     if children != None:
@@ -381,7 +381,7 @@ def CreateStation(id):
 ################################################################################
 @route(PREFIX + '/getartistinfo')
 def GetArtistInfo(name, id, inLibrary=False):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))

     artist = API.get_artist_info(id)
     for album in sorted(artist['albums'], key = lambda x: x.get('year')):
@@ -400,7 +400,7 @@ def GetArtistInfo(name, id, inLibrary=False):
 ################################################################################
 @route(PREFIX + '/getalbuminfo')
 def GetAlbumInfo(name, id):
-    oc = ObjectContainer(title2=name)
+    oc = ObjectContainer(title2=unicode(name))

     album = API.get_album_info(id)
     for track in album['tracks']:
pablorusso commented 7 years ago

new version here => https://github.com/jwdempsey/GoogleMusic.bundle/pull/20