diff --git a/modules/grooveshark/__init__.py b/modules/grooveshark/__init__.py
new file mode 100644
index 00000000..f7ad83f5
--- /dev/null
+++ b/modules/grooveshark/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Bezleputh
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from .backend import GroovesharkBackend
+
+
+__all__ = ['GroovesharkBackend']
diff --git a/modules/grooveshark/backend.py b/modules/grooveshark/backend.py
new file mode 100644
index 00000000..687ad306
--- /dev/null
+++ b/modules/grooveshark/backend.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Bezleputh
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from weboob.tools.backend import BaseBackend
+from weboob.capabilities.radio import ICapRadio, Radio, Stream, Emission
+from weboob.capabilities.video import ICapVideo, BaseVideo
+from weboob.capabilities.collection import ICapCollection
+from .browser import GroovesharkBrowser
+
+__all__ = ['GroovesharkBackend']
+
+class GroovesharkBackend(BaseBackend, ICapVideo, ICapCollection):
+ NAME = 'grooveshark'
+ DESCRIPTION = u'grooveshark website'
+ MAINTAINER = u'Bezleputh'
+ EMAIL = 'carton_ben@yahoo.fr'
+ VERSION = '0.g'
+ LICENSE = 'AGPLv3+'
+
+ BROWSER = GroovesharkBrowser
+
+ def fill_video(self, video, fields):
+ if 'url' in fields:
+ with self.browser:
+ video.url = unicode(self.browser.get_stream_url_from_song_id(video.id))
+ if 'thumbnail' in fields and video.thumbnail:
+ with self.browser:
+ video.thumbnail.data = self.browser.readurl(video.thumbnail.url)
+
+ def search_videos(self, pattern, sortby=ICapVideo.SEARCH_RELEVANCE, nsfw=False, max_results=10):
+ with self.browser:
+ for video in self.browser.search_videos(pattern, max_results):
+ yield video
+
+ def get_video(self, video):
+ with self.browser:
+ return self.browser.fill_stream_list(video)
+
+ OBJECTS = {BaseVideo: fill_video}
diff --git a/modules/grooveshark/browser.py b/modules/grooveshark/browser.py
new file mode 100644
index 00000000..58cc64ab
--- /dev/null
+++ b/modules/grooveshark/browser.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Bezleputh
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+from weboob.tools.browser import BaseBrowser
+from weboob.tools.json import json as simplejson
+from weboob.capabilities.video import BaseVideo
+from weboob.tools.capabilities.thumbnail import Thumbnail
+import urllib2
+import hashlib
+import uuid
+import string
+import random
+import datetime
+from .pages import IndexPage
+
+__all__ = ['GroovesharkBrowser']
+
+class APIError(Exception):
+ pass
+
+class GroovesharkBrowser(BaseBrowser):
+
+ PROTOCOL = 'http'
+ DOMAIN = 'html5.grooveshark.com'
+ #SAVE_RESPONSE = True
+ #DEBUG_HTTP = True
+ #DEBUG_MECHANIZE = True
+ API_URL = 'https://html5.grooveshark.com/more.php'
+
+ #Setting the static header (country, session and uuid)
+ HEADER = {}
+ HEADER['country'] = {}
+ HEADER['country']['CC1'] = 0
+ HEADER['country']['CC2'] = 0
+ HEADER['country']['CC3'] = 0
+ HEADER['country']['CC4'] = 0
+ HEADER['country']['ID'] = 1
+ HEADER['country']['IPR'] = 1
+ HEADER['privacy'] = 0
+ HEADER['session'] = (''.join(random.choice(string.digits + string.letters[:6]) for x in range(32))).lower()
+ HEADER["uuid"] = str.upper(str(uuid.uuid4()))
+
+ #those values depends on a grooveshark version and may change
+ GROOVESHARK_CONSTANTS = ('mobileshark', '20120830', 'gooeyFlubber')
+ COMMUNICATION_TOKEN = None
+
+ PAGES = {
+ 'http://html5.grooveshark.com/.*': IndexPage,
+ }
+
+ def home(self):
+ self.location('/');
+ assert self.is_on_page(IndexPage)
+ self.get_communication_token()
+
+ def search_videos(self, pattern, max_results):
+ method = 'getResultsFromSearch'
+
+ parameters = {}
+ parameters['query'] = pattern.encode(self.ENCODING)
+ parameters['type'] = ['Songs']#['Songs','Playlists','Albums']
+ parameters['guts'] = 0
+ parameters['ppOverr'] = ''
+
+ response = self.API_post(method, parameters, self.create_token(method))
+
+ songs = self.create_video_from_songs_result(response['result']['result']['Songs'], max_results)
+ #playlists = self.create_video_from_playlist_result(response['result']['result']['Playlists'])
+ #albums = self.create_video_from_albums_result(response['result']['result']['Albums'])
+
+ return songs
+
+ def create_video_from_songs_result(self, songs, max_results):
+ videos = []
+
+ if max_results:
+ songs = songs[0:max_results]
+
+ for song in songs:
+ video = BaseVideo(song['SongID'])
+ video.title = u'Song - %s' % song['SongName'].encode('ascii', 'replace')
+ video.author = u'%s' % song['ArtistName'].encode('ascii', 'replace')
+ video.description = u'%s - %s - %s'%(video.author, song['AlbumName'].encode('ascii', 'replace'), song['Year'].encode('ascii', 'replace'))
+ video.thumbnail = Thumbnail(u'http://images.gs-cdn.net/static/albums/40_' + song['CoverArtFilename'])
+ video.duration = datetime.timedelta(seconds=int(float(song['EstimateDuration'])))
+ video.rating = float(song['AvgRating'])
+ video.date = datetime.date(year=int(song['Year']), month=1, day=1)
+ videos.append(video)
+ return videos
+
+ def create_video_from_playlist_result(self, playlists):
+ videos = []
+ for playlist in playlists:
+ video = BaseVideo(playlist['PlaylistID'])
+ video.title = u'Playlist - %s' %(playlist['Name'])
+ video.description = playlist['Artists']
+ videos.append(video)
+ return videos
+
+ def create_video_from_albums_result(self, albums):
+ videos = []
+ for album in albums:
+ video = BaseVideo(album['AlbumID'])
+ video.title = u'Album - %s' %(album['Name'])
+ video.description = album['Year']
+ videos.append(video)
+ return videos
+
+
+ def get_communication_token(self):
+ parameters = {'secretKey': hashlib.md5(self.HEADER["session"]).hexdigest()}
+ result = self.API_post('getCommunicationToken', parameters)
+ self.COMMUNICATION_TOKEN = result['result']
+
+ def create_token(self, method):
+
+ if self.COMMUNICATION_TOKEN is None:
+ self.get_communication_token()
+
+ rnd = (''.join(random.choice(string.hexdigits) for x in range(6)))
+ return rnd + hashlib.sha1('%s:%s:%s:%s' % (method, self.COMMUNICATION_TOKEN, self.GROOVESHARK_CONSTANTS[2], rnd)).hexdigest()
+
+ def fill_stream_list(self, video):
+ if isinstance(video, BaseVideo):
+ video.url = self.get_stream_url_from_song_id(video.id)
+
+ def get_stream_url_from_song_id(self, song_id):
+ method = 'getStreamKeyFromSongIDEx'
+
+ parameters = {}
+ parameters['prefetch'] = False
+ parameters['mobile'] = True
+ parameters['songID'] = int(song_id)
+ parameters['country'] = self.HEADER['country']
+
+ response = self.API_post(method, parameters, self.create_token(method))
+
+ self.mark_song_downloaded_ex(response['result'])
+
+ return u'http://%s/stream.php?streamKey=%s' %(response['result']['ip'], response['result']['streamKey'])
+
+ # in order to simulate a real browser
+ def mark_song_downloaded_ex(self, response):
+ method = 'markSongDownloadedEx'
+
+ parameters = {}
+ parameters['streamKey'] = response['streamKey']
+ parameters['streamServerID'] = response['streamServerID']
+ parameters['songID'] = response['SongID']
+
+ response = self.API_post(method, parameters, self.create_token(method))
+
+ def check_result(self, result):
+ if 'fault' in result:
+ raise APIError('%s' % result['fault']['message'])
+ if not result['result']:
+ raise APIError('%s' % "No response found")
+
+ def API_post(self, method, parameters, token=None):
+ """
+ Submit a POST request to the website
+ The JSON data is parsed and returned as a dictionary
+ """
+ data = self.create_json_data(method, parameters, token)
+ req = self.create_request(method)
+ response = urllib2.urlopen(req, data)
+ return self.parse_response(response)
+
+ def create_json_data(self, method, parameters, token):
+ data = {}
+ data['header'] = self.HEADER
+ data['header']['client'] = self.GROOVESHARK_CONSTANTS[0]
+ data['header']['clientRevision'] = self.GROOVESHARK_CONSTANTS[1]
+ if(token is not None):
+ data['header']['token'] = token
+ data['method'] = method
+ data['parameters'] = parameters
+ return simplejson.dumps(data)
+
+ def create_request(self, method):
+ req = urllib2.Request('%s?%s' %(self.API_URL, method))
+ req.add_header('Content-Type', 'application/json')
+ return req
+
+ def parse_response(self, response):
+ result = simplejson.loads(response.read(), self.ENCODING)
+ self.check_result(result)
+ return result
diff --git a/modules/grooveshark/pages.py b/modules/grooveshark/pages.py
new file mode 100644
index 00000000..d685c9e8
--- /dev/null
+++ b/modules/grooveshark/pages.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Bezleputh
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from weboob.tools.browser import BasePage
+
+__all__ = ['IndexPage']
+
+class IndexPage(BasePage):
+ pass
diff --git a/modules/grooveshark/test.py b/modules/grooveshark/test.py
new file mode 100644
index 00000000..d9eccfcf
--- /dev/null
+++ b/modules/grooveshark/test.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+# Copyright(C) 2013 Bezleputh
+#
+# This file is part of weboob.
+#
+# weboob is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# weboob is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with weboob. If not, see .
+
+
+from weboob.tools.test import BackendTest
+
+
+class GroovesharkTest(BackendTest):
+ BACKEND = 'grooveshark'
+
+ def test_grooveshark_video_search(self):
+ result = list(self.backend.search_videos("Loic Lantoine"))
+ self.assertTrue(len(result)>0)