Skip to content
This repository has been archived by the owner on Mar 24, 2019. It is now read-only.

Commit

Permalink
Merge pull request #2 from lucky-user/master
Browse files Browse the repository at this point in the history
* pithos library update
* code cleanup, silencing some PEP8 warnings (removed random trailing semicolons and slashes, etc.)

* thanks to @lucky-user
  • Loading branch information
rivy committed May 17, 2015
2 parents ff6aff1 + c832709 commit 2319d6a
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 49 deletions.
42 changes: 23 additions & 19 deletions default.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import xbmcgui
import xbmc
import xbmcaddon
import os, sys
import os
import sys

_settings = xbmcaddon.Addon()
_name = _settings.getAddonInfo('name')
Expand Down Expand Up @@ -57,7 +58,7 @@ def __init__( self ):

def set_proxy(self, proxy):
if proxy:
proxy_handler = urllib2.ProxyHandler({'http': proxy})
proxy_handler = urllib2.ProxyHandler({'http': proxy, 'https': proxy})
self.opener = urllib2.build_opener(proxy_handler)
## _or_ set_url_opener()
#self.set_url_opener( urllib2.build_opener(proxy_handler) )
Expand Down Expand Up @@ -105,12 +106,15 @@ def __init__( self ):
"user" : self.settings.getSetting( "proxy_user" ),
"pass" : self.settings.getSetting( "proxy_pass" )
}
self.pandora.set_proxy( "http://%(user)s:%(pass)s@%(host)s:%(port)s" % proxy_info )
if proxy_info.get("user"):
self.pandora.set_proxy("{user}:{pass}@{host}:{port}".format(**proxy_info))
else:
self.pandora.set_proxy("{host}:{port}".format(**proxy_info))

while not self.auth():
resp = xbmcgui.Dialog().yesno( _NAME, \
"Failed to authenticate listener.", \
"Check username/password and try again.", \
resp = xbmcgui.Dialog().yesno( _NAME,
"Failed to authenticate listener.",
"Check username/password and try again.",
"Show Settings?" )
if resp:
self.settings.openSettings()
Expand Down Expand Up @@ -144,14 +148,14 @@ def auth( self ):
dlg.update( 0 )
try:
self.pandora.connect(pithos.pandora.data.client_keys[client_id], user, pwd)
except PandoraError, e:
return 0;
except PandoraError:
return 0
dlg.close()
return 1

def playStation( self, stationId ):
self.curStation = stationId
station = self.pandora.get_station_by_id(self.curStation);
station = self.pandora.get_station_by_id(self.curStation)
dlg = xbmcgui.DialogProgress()
dlg.create( _NAME, "Opening Pandora station: " + station.name )
dlg.update( 0 )
Expand All @@ -172,7 +176,7 @@ def getMoreSongs( self ):
if self.curStation == "":
raise PandaException()
items = []
station = self.pandora.get_station_by_id(self.curStation);
station = self.pandora.get_station_by_id(self.curStation)
songs = station.get_playlist()
for song in songs:
log( "Adding song '%s'" % song.title )
Expand All @@ -183,22 +187,22 @@ def getMoreSongs( self ):
item.setIconImage( thumbnail )
item.setThumbnailImage( thumbnail )
item.setProperty( "Cover", thumbnail )
if song.rating_str != None:
if song.rating_str is not None:
item.setProperty( "Rating", song.rating_str )
else:
item.setProperty( "Rating", "" )
info = {
"title" : song.title, \
"artist" : song.artist, \
"album" : song.album, \
"title" : song.songName,
"artist" : song.artist,
"album" : song.album,
}
## HACK: set fictional duration to enable scrobbling
if self.settings.getSetting( "scrobble_hack" ) == "true":
duration = 60 * ( int(self.settings.getSetting( "scrobble_hack_time" )) + 1 )
info["duration"] = duration
log( "item info = %s" % info, xbmc.LOGDEBUG )
item.setInfo( "music", info )
items.append( ( song.audioUrl, item, song ) )
items.append((song.additionalAudioUrl, item, song))

self.playlist.extend( items )

Expand Down Expand Up @@ -245,12 +249,12 @@ def skipSong( self ):
def addFeedback( self, likeFlag ):
if not self.playing:
raise PandaException()
self.curSong[2].rate(likeFlag);
self.curSong[2].rate(likeFlag)

def addTiredSong( self ):
if not self.playing:
raise PandaException()
musicId = self.curSong[2].set_tired();
musicId = self.curSong[2].set_tired()

def main( self ):
if self.die:
Expand All @@ -277,14 +281,14 @@ def quit( self ):
if self.player and self.player.timer\
and self.player.timer.isAlive():
self.player.timer.stop()
if self.gui != None:
if self.gui is not None:
self.gui.close()
self.die = True

if __name__ == '__main__':
if _settings.getSetting( "username" ) == "" or \
_settings.getSetting( "password" ) == "":
xbmcgui.Dialog().ok( __name__, \
xbmcgui.Dialog().ok( __name__,
"Username and/or password not specified" )
_settings.setSetting( "firstrun", "true" )
else:
Expand Down
6 changes: 3 additions & 3 deletions resources/lib/pandagui.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,18 @@ def onInit(self):
def onAction(self, action):
buttonCode = action.getButtonCode()
actionID = action.getId()
if ( actionID in ( ACTION_PREVIOUS_MENU, ACTION_NAV_BACK, \
if ( actionID in ( ACTION_PREVIOUS_MENU, ACTION_NAV_BACK,
ACTION_PARENT_DIR ) ):
if xbmc.getCondVisibility( 'Skin.HasSetting(PandoraVis)' ):
xbmc.executebuiltin( 'Skin.Reset(PandoraVis)' )
#xbmc.executebuiltin( "SetProperty(HidePlayer,False)" )
else:
self.panda.quit()
elif (actionID == ACTION_NEXT_ITEM ):
elif actionID == ACTION_NEXT_ITEM:
self.panda.skipSong()

def onClick(self, controlID):
if (controlID == STATION_LIST_ID): # station list control
if controlID == STATION_LIST_ID: # station list control
selItem = self.list.getSelectedItem()
self.panda.playStation( selItem.getProperty("stationId") )
elif self.panda.playing:
Expand Down
23 changes: 13 additions & 10 deletions resources/lib/pithos/pandora/fake.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
### BEGIN LICENSE
# Copyright (C) 2010 Kevin Mehall <km@kevinmehall.net>
#This program is free software: you can redistribute it and/or modify it
#under the terms of the GNU General Public License version 3, as published
#This program is free software: you can redistribute it and/or modify it
#under the terms of the GNU General Public License version 3, as published
#by the Free Software Foundation.
#
#This program is distributed in the hope that it will be useful, but
#WITHOUT ANY WARRANTY; without even the implied warranties of
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#This program is distributed in the hope that it will be useful, but
#WITHOUT ANY WARRANTY; without even the implied warranties of
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
#PURPOSE. See the GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License along
#You should have received a copy of the GNU General Public License along
#with this program. If not, see <http://www.gnu.org/licenses/>.
### END LICENSE

from pithos.pandora.pandora import *
from gi.repository import Gtk
import logging

TEST_FILE = "http://pithos.github.io/testfile.aac"

class FakePandora(Pandora):
def __init__(self):
super(FakePandora, self).__init__()
Expand Down Expand Up @@ -105,18 +107,19 @@ def get_station_by_token(self, token):

def makeFakeSong(self, stationId):
c = self.count()
audio_url = TEST_FILE + '?val='+'0'*48
return {
'albumName':"AlbumName",
'artistName':"ArtistName",
'audioUrlMap': {
'highQuality': {
'audioUrl': 'http://pithos.github.io/testfile.aac?val='+'0'*48
'audioUrl': audio_url
},
'mediumQuality': {
'audioUrl': 'http://pithos.github.io/testfile.aac?val='+'0'*48
'audioUrl': audio_url
},
'lowQuality': {
'audioUrl': 'http://pithos.github.io/testfile.aac?val='+'0'*48
'audioUrl': audio_url
},
},
'trackGain':0,
Expand All @@ -126,7 +129,7 @@ def makeFakeSong(self, stationId):
'songName': 'Test song %i'%c,
'songDetailUrl': 'http://pithos.github.io/',
'albumDetailUrl':'http://pithos.github.io/',
'albumArtUrl':'http://pithos.github.io/img/logo.png',
'albumArtUrl':'http://pithos.github.io/img/pithos_logo.png',
'songExplorerUrl':'http://pithos.github.io/test-song.xml',
}

41 changes: 24 additions & 17 deletions resources/lib/pithos/pandora/pandora.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import time
import urllib
import urllib2
import codecs

# This is an implementation of the Pandora JSON API using Android partner
# credentials.
Expand All @@ -43,6 +44,7 @@
API_ERROR_INVALID_LOGIN = 1002
API_ERROR_LISTENER_NOT_AUTHORIZED = 1003
API_ERROR_PARTNER_NOT_AUTHORIZED = 1010
API_ERROR_PLAYLIST_EXCEEDED = 1039

PLAYLIST_VALIDITY_TIME = 60*60*3

Expand All @@ -60,18 +62,18 @@ class PandoraAPIVersionError(PandoraError): pass
class PandoraTimeout(PandoraNetError): pass

def pad(s, l):
return s + "\0" * (l - len(s))
return s + b'\0' * (l - len(s))

class Pandora(object):
def __init__(self):
self.opener = urllib2.build_opener()
pass

def pandora_encrypt(self, s):
return "".join([self.blowfish_encode.encrypt(pad(s[i:i+8], 8)).encode('hex') for i in xrange(0, len(s), 8)])
return b''.join([codecs.encode(self.blowfish_encode.encrypt(pad(s[i:i+8], 8)), 'hex_codec') for i in range(0, len(s), 8)])

def pandora_decrypt(self, s):
return "".join([self.blowfish_decode.decrypt(pad(s[i:i+16].decode('hex'), 8)) for i in xrange(0, len(s), 16)]).rstrip('\x08')
return b''.join([self.blowfish_decode.decrypt(pad(codecs.decode(s[i:i+16], 'hex_codec'), 8)) for i in range(0, len(s), 16)]).rstrip(b'\x08')

def json_call(self, method, args={}, https=False, blowfish=True):
url_arg_strings = []
Expand All @@ -94,7 +96,7 @@ def json_call(self, method, args={}, https=False, blowfish=True):
args['userAuthToken'] = self.userAuthToken
elif self.partnerAuthToken:
args['partnerAuthToken'] = self.partnerAuthToken
data = json.dumps(args)
data = json.dumps(args).encode('utf-8')

logging.debug(url)
logging.debug(data)
Expand All @@ -105,16 +107,16 @@ def json_call(self, method, args={}, https=False, blowfish=True):
try:
req = urllib2.Request(url, data, {'User-agent': USER_AGENT, 'Content-type': 'text/plain'})
response = self.opener.open(req, timeout=HTTP_TIMEOUT)
text = response.read()
text = response.read().decode('utf-8')
except urllib2.HTTPError as e:
logging.error("HTTP error: %s", e)
raise PandoraNetError(str(e))
except urllib2.URLError as e:
logging.error("Network error: %s", e)
if e.reason[0] == 'timed out':
if e.reason.strerror == 'timed out':
raise PandoraTimeout("Network error", submsg="Timeout")
else:
raise PandoraNetError("Network error", submsg=e.reason[1])
raise PandoraNetError("Network error", submsg=e.reason.strerror)

logging.debug(text)

Expand Down Expand Up @@ -146,8 +148,11 @@ def json_call(self, method, args={}, https=False, blowfish=True):
elif code == API_ERROR_PARTNER_NOT_AUTHORIZED:
raise PandoraError("Login Error", code,
submsg="Invalid Pandora partner keys. A Pithos update may be required.")
elif code == API_ERROR_PLAYLIST_EXCEEDED:
raise PandoraError("Playlist Error", code,
submsg="You have requested too many playlists. Try again later.")
else:
raise PandoraError("Pandora returned an error", code, "%s (code %d)"%(msg, code))
raise PandoraError("Pandora returned an error", code, "%s (code %d)" % (msg, code))

if 'result' in tree:
return tree['result']
Expand All @@ -163,8 +168,8 @@ def connect(self, client, user, password):
self.userAuthToken = self.time_offset = None

self.rpcUrl = client['rpcUrl']
self.blowfish_encode = Blowfish(client['encryptKey'])
self.blowfish_decode = Blowfish(client['decryptKey'])
self.blowfish_encode = Blowfish(client['encryptKey'].encode('utf-8'))
self.blowfish_decode = Blowfish(client['decryptKey'].encode('utf-8'))

partner = self.json_call('auth.partnerLogin', {
'deviceModel': client['deviceModel'],
Expand All @@ -176,7 +181,7 @@ def connect(self, client, user, password):
self.partnerId = partner['partnerId']
self.partnerAuthToken = partner['partnerAuthToken']

pandora_time = int(self.pandora_decrypt(partner['syncTime'])[4:14])
pandora_time = int(self.pandora_decrypt(partner['syncTime'].encode('utf-8'))[4:14])
self.time_offset = pandora_time - time.time()
logging.info("Time offset is %s", self.time_offset)

Expand Down Expand Up @@ -252,9 +257,9 @@ def transformIfShared(self):
self.pandora.json_call('station.transformSharedStation', {'stationToken': self.idToken})
self.isCreator = True

def get_playlist(self):
def get_playlist(self, audio_fmt="HTTP_128_MP3"):
logging.info("pandora: Get Playlist")
playlist = self.pandora.json_call('station.getPlaylist', {'stationToken': self.idToken}, https=True)
playlist = self.pandora.json_call('station.getPlaylist', {'stationToken': self.idToken, 'additionalAudioUrl': audio_fmt}, https=True)
songs = []
for i in playlist['items']:
if 'songName' in i: # check for ads
Expand Down Expand Up @@ -290,8 +295,10 @@ def __init__(self, pandora, d):
self.songDetailURL = d['songDetailUrl']
self.songExplorerUrl = d['songExplorerUrl']
self.artRadio = d['albumArtUrl']

self.tired=False
self.additionalAudioUrl = d['additionalAudioUrl']
self.bitrate = None
self.is_ad = None # None = we haven't checked, otherwise True/False
self.tired = False
self.message=''
self.start_time = None
self.finished = False
Expand All @@ -311,7 +318,7 @@ def title(self):
self._title = self.songName
else:
try:
xml_data = urllib.urlopen(self.songExplorerUrl)
xml_data = urllib2.urlopen(self.songExplorerUrl)
dom = minidom.parseString(xml_data.read())
attr_value = dom.getElementsByTagName('songExplorer')[0].attributes['songTitle'].value

Expand All @@ -331,7 +338,7 @@ def audioUrl(self):
except KeyError:
logging.warn("Unable to use audio format %s. Using %s",
quality, self.audioUrlMap.keys()[0])
return self.audioUrlMap.values()[0]['audioUrl']
return list(self.audioUrlMap.values())[0]['audioUrl']

@property
def station(self):
Expand Down

0 comments on commit 2319d6a

Please sign in to comment.