Jump to content

Grooveshark


Zimmer

Recommended Posts

Hey what's up guys,

Great work so far Zimmer. I've been looking for an app like this (open source) or specifically for the palm pre so I can access Grooveshark on the go. (Grooveshark has been working on one for a while with no release date in sight...) Since the pre has an underlying Linux os I can implement this python program on it with a few modifications. I'm not that familiar with python but it isn't hard to pick up. With that said, I was wondering if you would be willing to help me with an error I'm having with getting your source to work. I've downloaded both the new and old source, and I receive the error in both versions. The function getToken's return gives me an error...

Traceback (most recent call last):

File "myGrooveshark.py", line 11, in <module>

g.sessionData()

File "/home/myUsrname/Downloads/Grooveshark - python/distrobution/Grooveshark.py", line 61, in sessionData

self.token = self.getToken()

File "/home/myUsrname/Downloads/Grooveshark - python/distrobution/Grooveshark.py", line 69, in getToken

return json.loads(reply)['result']

KeyError: 'result'

Have you or anyone else expirenced this error?

PS: I'm running Ubuntu 9.10 and installed python, wx, httplib2, & the JSON packages from my repositories.

Thanks!

Link to comment
Share on other sites

  • Replies 199
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Posted Images

That would be a dictionary

You see the json.loads creates a dictionary (yes like a dictionary)

basically a dictionary is well like a dictionary (so key - value or apple - a sweet fruit)

the syntax is {key: value, second_key: second_value} or {'apple': 'a fruit', 'orange': 'another fruit'}

so say you have x = {'apple': 'a fruit', 'orange': 'another fruit'}

then you do

print x['apple']

you get a fruit as a result

but if you do

print x['non existent value'] or x['peach'] in our example

you get the key error

Link to comment
Share on other sites

Thx, that clears it up.

I still can't get the source to work though and I realize why now. The reply I get back from getToken after calling sessionData says invalid client.

reply =

{"header":{"session":"c599864a8dd58b66c74464aa3c838f10"},"fault":{"code":1024,"message":" invalid client"}}

Have you found a workaround for this?

Thanks again for the help!

Link to comment
Share on other sites

After changing the client revision, the program still does not work anymore. The response when searching for a song is "Bad Token". After taking a look with Tamper Data it seems that the communication token is not used "as is" anymore when sending a request to Grooveshark, but is now longer and different for every request.

The new token probably hashes one or more of the following: time, the communication token, the song title, other, but it's pretty hard to figure out how it is formed exactly. Do you have any idea?

Link to comment
Share on other sites

I've been looking into this recently and the problem I have come across is that the token they are using is 46 charactors. This means it would have to be an 184-bit hash function which as far as I can tell doesn't exist. I agree that it has to be based on the current time since they are no constant characters. I'm trying to see if maybe it is the current time encrypted using the token as the key.

If anyone else has any insight on this it would be great.

EDIT:

I found the solution:

generate a 6 random leters and numbers such as: d298c5

Then find the name of the command you are going to run: logoutUser

get your communicationToken: 4b6706fb94216

generate the sha1 hash of param+":"+communicationToken+":theColorIsRed:"+theRandom:

"logoutUser:4b6706fb94216:theColorIsRed:d298c5"

gives you e36c21d4a8585d5144643a1477a7d7dd9fb95fdb

append the random onto the front giving: d298c5e36c21d4a8585d5144643a1477a7d7dd9fb95fdb

and there's your Token.

After changing the client revision, the program still does not work anymore. The response when searching for a song is "Bad Token". After taking a look with Tamper Data it seems that the communication token is not used "as is" anymore when sending a request to Grooveshark, but is now longer and different for every request.

The new token probably hashes one or more of the following: time, the communication token, the song title, other, but it's pretty hard to figure out how it is formed exactly. Do you have any idea?

Link to comment
Share on other sites

Someone has been looking at my codes, haven't they :P

I've been looking into this recently and the problem I have come across is that the token they are using is 46 charactors. This means it would have to be an 184-bit hash function which as far as I can tell doesn't exist. I agree that it has to be based on the current time since they are no constant characters. I'm trying to see if maybe it is the current time encrypted using the token as the key.

If anyone else has any insight on this it would be great.

EDIT:

I found the solution:

Link to comment
Share on other sites

  • 2 weeks later...
  • 2 weeks later...

Getting Session Data
Traceback (most recent call last):
  File "Command Line App Grooveshark.py", line 293, in &lt;module&gt;
    m = Menu(path)
  File "Command Line App Grooveshark.py", line 26, in __init__
    g.sessionData()
  File "/home/xqtftqx/Downloads/Grooveshark/Grooveshark.py", line 48, in sessionData
    self.token = self.getToken()
  File "/home/xqtftqx/Downloads/Grooveshark/Grooveshark.py", line 56, in getToken
    return json.loads(reply)['result']
KeyError: 'result'

All libraries installed, im a python n00b so any explanation would be sweet. I look forward to using this

Link to comment
Share on other sites

I came across this semi-accidentally and I think that it is a really cool project. I can't wait to see what else you have going! In the meantime, the script on the front page doesn't work, so here is a modified version that goes through the new grooveshark api calls that a few people were discussing above. I switched the functions around a bit so it won't work with the gui or command line and it won't actually play, but you can at least see how the api works from a python shell and get an mp3 file spewed out in hex. Just open a shell and type in the commands listed in Usage.

'''
Main class for handling of grooveshark

one grooveshark instance represents one session (so usually one per application instance)
'''
import time
import sys
import uuid
import hashlib
import urllib
import os
import random
import subprocess
from mutagen.mp3 import MP3
try:
    import json
except ImportError:
    import simplejson as json#If this also errors allows the gui/user/whatever to handle the failed import
#Third Pary Libaries
import httplib2#Need version 5 or higher for python 2.6 compatability  - No version info in httplib2 so I can not check this :(

#This is a default header that can be used if you just need a user agent change (this way not every function/method has to define this)
header = {}
header['user-agent'] = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)'

'''
Intialize Grooveshark
-- Get Session Data No Return
-- set ID3 tags No Return
-- Work methods
    - search, return json search results
    - songKeyfromID return songKey
    - Download - return song



Usage:
from Grooveshark import Grooveshark
g = Grooveshark()
g.sessionData()
-Searching
g.search('some search string')
-Get Song Key and Stream server
g.songKeyfromID(songID)
-Download
g.download(songKey, streamServer)
-Popular Songs
g.Popular()

'''
class Grooveshark(object):
    def __init__(self):
        self.search_url = 'http://cowbell.grooveshark.com/more.php?getSearchResults'
        self.count = 0
    def save(self, content, path):
        file_ = open(path, 'wb')
        file_.write(content)
        file_.close()
    def sessionData(self):
        self.session = self.getSessionID()
        self.uuid = self.getUID()
        self.token = self.getToken()
    def getToken(self):
        http = httplib2.Http()
        url = 'https://cowbell.grooveshark.com/service.php'
        self.secretKey = hashlib.md5(self.session).hexdigest()
        tokenPOSTdata = ('''{"header":{"session":"%s","uuid":"%s","client":"gslite","clientRevision":"20100211.13"},'''
        '''"parameters":{"secretKey":"%s"},"method":"getCommunicationToken"}''' % (self.session, self.uuid, self.secretKey))
        request, reply = http.request(url, 'POST', headers = header, body = tokenPOSTdata)
        return json.loads(reply)['result']
    def getUID(self):
        return uuid.uuid4()
    def getSessionID(self):
        http = httplib2.Http()
        url = 'http://listen.grooveshark.com'
        response, src = http.request(url, 'GET', headers = header)
        src = src.lower().replace(' ', '')
        start = src.find('session')
        end = src[start:].find("',")
        startSession =  src[start:end+start].find("'") +1
        return src[startSession+start:end+start]
    def getRequestToken(self, method):
        randomStr = ''.join([random.choice('0123456789abcdef') for x in xrange(6)])
        return randomStr + hashlib.sha1(method + ":" + self.token + ":theColorIsRed:" + randomStr).hexdigest()
#work Methods
    def search(self, search_string):
        http = httplib2.Http()
        data = ('''{"header":{"session":"%s","uuid":"%s","client":"gslite","clientRevision":"20100211.13","token":"%s"},'''
        '''"parameters":{"type":"Songs","query":"%s"},"method":"getSearchResults"}''' % (self.session, self.uuid, self.getRequestToken("getSearchResults"), search_string.lower()))
        self.response, self.result = http.request(self.search_url, 'POST', headers = header, body = data)
        self.result = self.result
        self.searchResults = json.loads(self.result)['result']
        return self.searchResults
    def songKeyfromID(self, id):
        http = httplib2.Http()
        self.songID = id
        songKeyURL = '  http://cowbell.grooveshark.com/more.php?ge...FromSongID'
        songKeyPOSTdata = ('''{"header":{"token":"%s","session":"%s","uuid":"%s","client":"gslite","clientRevision":"20100211.13"},'''
        '''"parameters":{"songID":%s,"prefetch":false},"method":"getStreamKeyFromSongID"}''') % (self.getRequestToken("getStreamKeyFromSongID"), self.session, self.uuid, self.songID)
        request, reply = http.request(songKeyURL, 'POST', headers = header, body = songKeyPOSTdata)
        self.reply = json.loads(reply)['result']
        self.songKey = self.reply['result']['streamKey']
        return (self.songKey, self.reply['result']['streamServer'])
    def download(self, songKey, streamServer):
        http = httplib2.Http()
        self.mp3URL = 'http://'+streamServer+'/stream.php'#use self. so that any outer program can access it (not local)
        data = {}
        data['streamKey'] = songKey
        songHeader = dict(header)
        songHeader['content-length'] = str(len(urllib.urlencode(data)))
        songHeader['content-type'] = 'application/x-www-form-urlencoded'
        self.response, self.song = http.request(self.mp3URL, 'POST', headers = songHeader, body = urllib.urlencode(data))
        if self.response['status'] == '302':
            self.response, self.song = http.request(self.response['location'], 'GET', headers = header)
        if self.response['status'] == '400':
            return '400 Error'
        return self.song

    def stream(self, streamKeys, songServers, file):
        '''A list of streamkeys and base file'''
        self.stream_process = subprocess.Popen('python stream.py %s %s %s' % (json.dumps(songServers), json.dumps(streamKeys), file))
    def popular(self):
        http = httplib2.Http()
        url = 'http://cowbell.grooveshark.com/more.php?popularGetSongs'
        popularPOSTdata = ('''{"header":{"token":"%s","session":"%s","uuid":"%s","client":"gslite","clientRevision":"20100211.13"},'''
        '''"parameters":{},"method":"popularGetSongs"}''' % (self.getRequestToken("popularGetSongs"), self.session, self.uuid))
        self.request, self.reply = http.request(url, 'POST', headers = header, body = popularPOSTdata)
        return json.loads(self.reply)['result']['Songs']

    # def favorites(self):
        # http = httplib2.Http()
        # url = 'http://cowbell.grooveshark.com/more.php?getFavorites'
        # songKeyPOSTdata = ('''{"header":{"token":"%s","session":"%s","uuid":"%s","client":"gslite","clientRevision":"20091027.09"},'''
        # '''"parameters":{"prefetch":false},"method":"getFavorites"}''' % (self.token, self.session, self.uuid))
        # request, reply = http.request(url, 'POST', headers = header, body = songKeyPOSTdata)
        # print request
    # def playlist(self):
        # http = httplib2.Http()
        # url = 'http://cowbell.grooveshark.com/more.php?playlistGetSongs'
        # songKeyPOSTdata = ('''{"header":{"token":"%s","session":"%s","uuid":"%s","client":"gslite","clientRevision":"20091027.09"},'''
        # '''"parameters":{"prefetch":false},"method":"getPaylist"}''' % (self.token, self.session, self.uuid))
        # request, reply = http.request(url, 'POST', headers = header, body = songKeyPOSTdata)
        # print request

    def setID3tags(self, file, title = None, artist = None, album = None, albumArt = None, genre = None, composer = None):
        '''Does NOT WORK DUE TO UNKOWN UNICODE PROBLEM when saving'''
        if not os.path.exists(file):
            raise IOError('MP3 File does not exist.')
        print file
        tag_lookup = {'album':'TALB', 'comment':"COMM::'eng'", 'description':'TIT3', 'artist':'TPE1', 'title':'TIT2', 'track':'TRCK', 'composer':'TCOM', 'genre':'TCON'}
        http = httplib2.Http()
        request, albumArt = http.request('http://beta.grooveshark.com/static/amazonart/'+albumArt)
        if request['status'] == '404':
            albumArt = u''
        tag_lookup['album_art'] = 'TART'
        tags = {}
        #tags['title'] = title
        #tags['artist'] = artist
        #tags['album'] = album
        #tags['album_art'] = albumArt
        tags['genre'] = genre
        #tags['composer'] = composer
        #if tags['composer'] == None:
        #    tags['composer'] = tags['artist']
        for i in tags:
            if tags[i] == None:
                tags[i] = ''
        audio = MP3(file)
        #Delete Existing Songs
        audio.delete()
        #Set the tags
        #Iterate through tags
        for i in tags:
            print i
            print type(tags[i])
            print tags[i]
            audio[tag_lookup[i]] = tags[i]
        print audio[tag_lookup[i]]
        audio.tags.save()


#This is for streaming support
class Stream(urllib.FancyURLopener):
    version = header['user-agent']

Great project!

Also, I have a semi-working obj-c/iPhone port if anyone is interested.

Link to comment
Share on other sites

Nice I have a version that is working pretty well but still rather unstable.

Also I probably should mark that it is broken on the front page :)

Oh by the way.

the new version will have an update checker

it will have a improved player

improved backend code base

no more lock ups, uses twisted now (so no more httplib2 :))

oh sorry no pandora support YET :( :( !!!!!

I learned a lot (wait what, you don't think that is a plus ;))

Edited by Zimmer
Link to comment
Share on other sites

with method = "getSearchResults", the result is [{u'SongClicks': 0, u'ArtistVerified': u'0', u'DSName': u'p', u'AlbumName': u'Track Of Time EP', u'ArtistPlays': 0, u'ArtistName': u'Anna Von Hausswolff', u'Score': 29204.446014486999, u'CoverArtFilename': None, u'QueryArtistClicks': u'0', u'QuerySongClicks': u'0', u'SongName': u'Pills', u'SphinxWeight': 1500689, u'DAName': u'', u'EstimateDuration': u'368', u'Popularity': u'0', u'Name': u'Anna Von Hausswolff', u'AlbumClicks': 0, u'Year': u'2010', u'AlbumVerified': u'0', u'AlbumID': u'4129726', u'AvgDuration': None, u'Flags': u'0', u'SongPlays': 0, u'QueryAlbumClicks': u'0', u'IsVerified': u'0', u'SongVerified': u'0', u'ArtistClicks': 0, u'ArtistID': u'1350648', u'TrackNum': u'2', u'AvgRating': None, u'IsLowBitrateAvailable': u'0', u'DALName': u'p'}]

the key songID is not in the result

How to get songID ?

Link to comment
Share on other sites

I found the explanation:

The research was done with "parameters":{"type":"Albums","query":"%s"} and not with "parameters":{"type":"Songs","query":"%s"}

With "type":"Albums", I think we should use after the method albumGetSongs for the songIDs of the Album

Link to comment
Share on other sites

Well I was about to have some sad news, but I right before I was about to post I had an epiphany :). My laptop's power was dead (I'm still not going to buy from Dell ever again) and it seemed that it was the motherboard. Well I still have no clue but I can boot (if I have a charged battery) and then run on AC power from the plug (it won't charge (dell has an id chip that if the tiny wire is broken the BIOS refuses to charge (it still receives power though)). Also I got my files to another computer. Anyways an indefinite suspension from this app moving forward was averted. and now I am back getting the app almost done :).

Note to self: I need a blog :P

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...