3 # PSN Trophy Card for Maemo 5
5 # The code is ugly because I hacked it together quick.
6 # -- thp, circa 2010-08-26
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
13 # 1. Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
16 # 2. Redistributions in binary form must reproduce the above copyright
17 # notice, this list of conditions and the following disclaimer in the
18 # documentation and/or other materials provided with the distribution.
20 # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 # PSN Public Trophy List Parser
36 # 2010-08-24 Thomas Perl <thp@thpinfo.com>
37 # see also: http://talk.maemo.org/showthread.php?t=59732
42 from BeautifulSoup
import BeautifulSoup
54 ScrolledWindow
= hildon
.PannableArea
55 Window
= hildon
.StackableWindow
57 b
= hildon
.Button(gtk
.HILDON_SIZE_AUTO_WIDTH | \
58 gtk
.HILDON_SIZE_FINGER_HEIGHT
, \
59 hildon
.BUTTON_ARRANGEMENT_VERTICAL
)
62 Entry
= lambda: hildon
.Entry(0)
64 ScrolledWindow
= gtk
.ScrolledWindow
69 LEVEL_ICON
= 'http://us.playstation.com/playstation/img/profile_level_icon.png'
72 'http://us.playstation.com/playstation/img/trophy-01-bronze.png',
73 'http://us.playstation.com/playstation/img/trophy-02-silver.png',
74 'http://us.playstation.com/playstation/img/trophy-03-gold.png',
75 'http://us.playstation.com/playstation/img/trophy-04-platinum.png',
78 def create_connection(address
):
79 """Stolen from Python 2.6"""
80 msg
= "getaddrinfo returns an empty list"
82 for res
in socket
.getaddrinfo(host
, port
, 0, socket
.SOCK_STREAM
):
83 af
, socktype
, proto
, canonname
, sa
= res
86 sock
= socket
.socket(af
, socktype
, proto
)
96 def Progress(window
, progress
):
98 hildon
.hildon_gtk_window_set_progress_indicator(window
, progress
)
107 def GetContents(url
):
108 """downloader with caching"""
109 if url
.startswith('/playstation/PSNImageServlet?avtar='):
110 url
= url
[len('/playstation/PSNImageServlet?avtar='):]
112 filename
= os
.path
.join(os
.path
.expanduser('~/.cache/ps3trophy/'), basename
)
114 if not os
.path
.exists(os
.path
.dirname(filename
)):
115 os
.makedirs(os
.path
.dirname(filename
))
117 if os
.path
.exists(filename
):
118 return open(filename
, 'rb').read()
120 data
= urllib2
.urlopen(url
).read()
121 fp
= open(filename
, 'wb')
127 return gtk
.Label(text
)
130 return gtk
.Label(text
)
132 def BigLabel(text
, cb
=None):
134 l
.set_markup('<b><big>%s</big></b>' % (text
,))
139 def ProgressIndicator(progress
):
140 pb
= gtk
.ProgressBar()
141 pb
.set_text('%d%%' % (progress
,))
142 pb
.set_fraction(float(progress
)/100.)
146 def __init__(self
, image
, name
, progress
, trophies
, \
150 self
.progress
= progress
151 self
.trophies
= trophies
152 self
.trophycount
= trophycount
154 def __unicode__(self
):
155 name
= self
.name
.replace('\n', ' ').replace('\r', ' ')
157 name
= name
[:37] + '...'
158 return u
'%-40s %3d%% - %2d trophies (%r)' % (name
, self
.progress
, self
.trophies
, self
.trophycount
)
160 class PsnTrophyParser(object):
161 def __init__(self
, username
):
162 self
.username
= username
163 self
.trophy_data
= self
.get_trophy_data()
164 self
.profile_data
= self
.get_profile_data()
165 self
.games
= list(self
.get_games())
168 def get_session_id(self
):
169 c
= create_connection(('us.playstation.com', 80))
170 c
.send("""HEAD /playstation/psn/profiles/%s HTTP/1.1\r
171 TE: deflate,gzip;q=0.3\r
172 Connection: TE, close\r
173 Host: us.playstation.com\r
174 User-Agent: lwp-request/5.834 libwww-perl/5.834\r
177 response
= c
.recv(1024)
179 for line
in response
.splitlines():
180 m
= re
.match('Set-Cookie: ([^=]*)=([^;]*)', line
)
182 name
, value
= m
.groups()
183 if name
== 'JSESSIONID':
186 def get_profile_data(self
):
187 c
= create_connection(('us.playstation.com', 80))
188 c
.send("""GET /playstation/psn/profiles/%s HTTP/1.0\r
189 Referer: http://us.playstation.com/publictrophy/index.htm?onlinename=%s\r
190 User-Agent: Wget/1.12 (linux-gnu)\r
192 Host: us.playstation.com\r
193 Connection: Keep-Alive\r
194 Cookie: JSESSIONID=%s\r
196 """ % (self
.username
, self
.username
, self
.get_session_id()))
203 return ''.join(response
)
206 def get_trophy_data(self
):
207 c
= create_connection(('us.playstation.com', 80))
208 c
.send("""GET /playstation/psn/profile/%s/get_ordered_trophies_data HTTP/1.0\r
209 Referer: http://us.playstation.com/playstation/psn/profiles/%s\r
210 User-Agent: Wget/1.12 (linux-gnu)\r
212 Host: us.playstation.com\r
213 Connection: Keep-Alive\r
214 Cookie: JSESSIONID=%s\r
216 """ % (self
.username
, self
.username
, self
.get_session_id()))
223 return ''.join(response
)
225 def parse_profile(self
):
226 s
= BeautifulSoup(self
.profile_data
)
227 self
.level
= int(s
.find('div', {'id':'leveltext'}).string
.strip())
228 self
.progress
= int(s
.find('div', {'class':'progresstext'}).string
.strip()[:-1]) # strip the trailing "%" sign
229 self
.totaltrophies
= int(s
.find('div', {'id': 'totaltrophies'}).find('div', {'id': 'text'}).string
.strip())
230 self
.avatar
= s
.find('div', {'id': 'id-avatar'}).find('img')['src']
233 s
= BeautifulSoup(self
.trophy_data
)
235 for div
in s
.findAll('div', {'class': 'slot'}):
236 image
= dict(div
.find('img').attrs
)['src']
237 name
= div
.find('span', {'class': 'gameTitleSortField'}).string
238 progress
= int(div
.find('span', {'class': \
239 'gameProgressSortField'}).string
.strip())
240 trophies
= int(div
.find('span', {'class': \
241 'gameTrophyCountSortField'}).string
.strip())
242 values
= [int(d
.string
.strip()) for d
in div
.find('div', {'class': 'trophyholder trophycountholder'}).findAll('div', {'class': 'trophycontent'})]
243 yield Game(image
, name
, progress
, trophies
, *values
)
245 class TrophyCard(gtk
.Table
):
246 def __init__(self
, parser
, parent
):
247 gtk
.Table
.__init
__(self
, 4+len(parser
.games
), 8)
249 self
._parent
= parent
251 self
.attach(self
.Image(self
.parser
.avatar
), 0, 2, 0, 1)
252 self
.attach(BigLabel(self
.parser
.username
), 0, 2, 1, 2)
253 self
.attach(self
.LevelIndicator(self
.parser
.level
), 2, 4, 0, 1)
254 self
.attach(ProgressIndicator(self
.parser
.progress
), 2, 7, 1, 2)
255 self
.attach(BigLabel('%d Trophies' % (parser
.totaltrophies
,)), 4, 7, 0, 1)
256 self
.attach(gtk
.HSeparator(), 0, 7, 2, 3)
257 self
.attach(Header('Title'), 0, 1, 3, 4)
258 self
.attach(Header('Progress'), 1, 2, 3, 4)
259 self
.attach(Header('Trophies'), 2, 3, 3, 4)
261 self
.attach(self
.Image(TROPHY_ICONS
[i
]), 3+i
, 4+i
, 3, 4)
263 for i
, game
in enumerate(self
.parser
.games
):
264 self
.attach(self
.Title(game
.name
, game
.image
), 0, 1, 4+i
, 5+i
)
265 self
.attach(ProgressIndicator(game
.progress
), 1, 2, 4+i
, 5+i
)
266 self
.attach(Label(str(game
.trophies
)), 2, 3, 4+i
, 5+i
)
268 self
.attach(Label(str(game
.trophycount
[x
])), 3+x
, 4+x
, 4+i
, 5+i
)
270 gobject
.idle_add(self
._run
_later
_once
)
272 def _run_later_once(self
):
274 task
= self
._run
_later
.pop(0)
276 while gtk
.events_pending():
277 gtk
.main_iteration(False)
278 if not self
._run
_later
:
279 Progress(self
._parent
, False)
284 def Image(self
, url
, cb
=None):
287 pbl
= gtk
.gdk
.PixbufLoader()
288 pbl
.write(GetContents(url
))
290 i
.set_from_pixbuf(pbl
.get_pixbuf())
293 self
._run
_later
.append(x
)
296 def ScaledImage(self
, url
, factor
, cb
=None):
299 width
, height
= pb
.get_width(), pb
.get_height()
300 i
.set_from_pixbuf(pb
.scale_simple(\
301 int(float(width
)*factor
), \
302 int(float(height
)*factor
), \
303 gtk
.gdk
.INTERP_NEAREST
))
304 im
= self
.Image(url
, x
)
307 def LevelIndicator(self
, level
):
309 h
.add(self
.Image(LEVEL_ICON
, lambda i
: i
.set_alignment(1., .5)))
310 h
.add(BigLabel(str(level
), lambda l
: l
.set_alignment(0., .5)))
313 def Title(self
, text
, icon
):
315 hb
.add(self
.ScaledImage(icon
, .4))
317 lb
.set_markup('<small><b>%s</b></small>' % (text
,))
318 lb
.set_ellipsize(pango
.ELLIPSIZE_END
)
323 if __name__
== '__main__':
324 def show_trophies(username
):
325 print 'show_trophies', username
327 w
.set_title('Trophies: ' + username
)
330 while gtk
.events_pending():
331 gtk
.main_iteration(False)
333 parser
= PsnTrophyParser(username
)
335 sw
= ScrolledWindow()
336 sw
.add_with_viewport(TrophyCard(parser
, w
))
341 w1
.set_title('PS3 Trophies')
343 hb
.pack_start(gtk
.Label('PSN Nickname:'), False, False)
345 hb
.pack_start(eUsername
)
346 btn
= Button('Show trophy card')
347 btn
.connect('clicked', lambda b
: show_trophies(eUsername
.get_text()))
348 hb
.pack_start(btn
, False, False)
350 vb
.pack_start(gtk
.Label(''), True, True)
351 vb
.pack_start(hb
, False, False)
353 footer
.set_markup('<small>(c) 2010 Thomas Perl <thpinfo.com> / Icon by mr_xzibit</small>')
354 footer
.set_alignment(1., 1.)
355 vb
.pack_start(footer
, True, True)
357 w1
.connect('destroy', gtk
.main_quit
)