Jiwa v3, retour à la case départ
Publié le 17 février 2012

Je suis retourné sur Jiwa aujourd’hui. Je m’attendais à trouver ma plateforme de musique habituelle, mais je me suis rendu compte que cela faisait un bail que je n’y avais pas été. Le site a complètement changé de look et a été repris par Allomusic qui en a profité pour le PURifier à l’Hadopi. Vu que je m’étais bien intéressé à cette plateforme il y a de cela quelques années, je me suis demandé si les choses avaient changé. Et puis parce que cette satanée limitation de 30 secondes d’écoute m’a été insupportable.

Jiwa version 3

Comme en témoigne le code source de Jiwa, le site est passé en vesion 3 :

<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# jiwavtrois: http://ogp.me/ns/fb/jiwavtrois#">

Jiwa avait fait de nombreux efforts il y a quelques années pour empêcher le téléchargement des fichiers MP3 et leur sauvegarde. J’avais d’ailleurs à l’époque rédigé quelques articles et développé une extension Firefox dédiée. Il semble donc raisonnable de penser que les protections mises en place dans le code sont identiques ou meilleures que les dernières implémentées.

J’ai donc repris mes anciens Proof of Concept, et je me suis attelé à la tâche. J’ai tout simplement réessayé mes outils, sans succès. J’ai donc sorti l’artillerie lourde (paros + firefox) et commencé à regarder les requêtes et là, ce fut la surprise. Une grosse suprise. Non seulement quelques détails ont changé, certes très minimes, mais j’ai pu retrouver des requêtes similaires à celles employées quelques années plus tôt sur Jiwa ... Comme si on avait fait un bond en arrière de quelques années.

C’est reparti comme en 40 !

Autant dans la version précédente de Jiwa, on pouvait toujours écouter sans limite, autant avec la version actuelle Jiwa ne sert que d’appât : les pages redirigent pour l’écoute vers le site d’Allomusic, qui bride énormément les contenus et force un poil la main pour amener le visiteur à prendre un compte Premium.

Il faut dire qu’à l’époque où je m’étais interessé à Jiwa, l’écoute en ligne était toujours possible, ce qui par ailleurs me satisfaisait pleinement. Mais dans la configuration actuelle, je trouve ça un brin décevant de se servir de l’ancienne image de Jiwa pour essayer de remonter l’image d’un autre service.

On s’éloigne quand même du modèle original de Jiwa, et même de celui de Deezer : il ne s’agit plus de streaming gratuit en ligne reposant sur de la pub mais d’une version lite qui vise à convertir les utilisateurs en comptes Premium. Certainement une preuve que le modèle économique qui se veut reposer sur la pub n’est pas viable, comme a pu l’expérimenter Deezer. Sans parler des tarifs faramineux demandés par les majors comme Universal Music.

Alors bon, je ne suis pas un fervent adepte de ce système, bon nombre de lecteurs le savent, et j’ai adapté mes précédents scripts pour prendre en charge la version actuelle de Jiwa. Plusieurs restrictions ont été mises en œuvres pour éviter le téléchargement :

Le détail amusant c’est que même si l’écoute est limitée à 30 secondes, le fichier MP3 complet est téléchargé. Le bridage par user-agent est même complètement inefficace, je soupçonne même le script distant de bugger quasi aléatoirement ...

iJaw nouvelle génération

Pour ceux qui seraient intéressés, je diffuse ci-après le code source de mon Proof of Concept amélioré. Ce bout de code python est un poil sale, mais je ne compte pas le maintenir. C’est une preuve de concept, juste présente pour démontrer ce que j’affirme dans ce billet.

  1. #!/usr/bin/python
  2.  
  3. """
  4. Crapy Jiwa download tool
  5. """
  6.  
  7. import re
  8. import sys
  9. import json
  10. import time
  11. from hashlib import md5
  12. from random import choice
  13. import urllib,httplib,urllib2
  14.  
  15. def filter(s):
  16. return s.replace('\n',' ')
  17.  
  18. def randomUA():
  19. rstr = ''.join([choice('0123456789') for i in range(32)])
  20. return 'Mozilla Firefox (%s)'%rstr
  21.  
  22. def getSongDetails(sid):
  23. r = urllib2.Request('http://www.jiwa.fr/song/%d/play/'%sid)
  24. resp = urllib2.urlopen(r).read()
  25. return json.loads(resp)
  26.  
  27. def lookupSongs(keywords):
  28. try:
  29. r = urllib2.Request('http://www.jiwa.fr/search?q=%s'%urllib.quote(keywords))
  30. resp = urllib2.urlopen(r).read()
  31. matches = re.findall('/track/.*-([0-9]+)\.html',resp)
  32. res = []
  33. for match in list(set(matches))[:5]:
  34. res.append(getSongDetails(int(match)))
  35. return res
  36. except urllib2.Error,e:
  37. return None
  38.  
  39.  
  40. def dlTrack(sid,filename):
  41. errors = 0
  42. while errors<3:
  43. token = getToken(sid)
  44. _t = token.split('=')
  45. url = 'http://m.jiwa.fm/play.php'
  46. l1 = 'gwqd29ydg7sqys_qsh0'
  47. l3 = _t[0]
  48. l4 = _t[1]
  49. l5 = _t[2]
  50. l6 = _t[3]
  51. l7 = md5(l1).hexdigest()
  52. l8 = md5(l3+l7+str(sid)).hexdigest()
  53. url = "%s?r=%s&s=%s&t=%s&m=%s&from=1" % (url,l4,str(sid),l8,l6)
  54.  
  55. r = httplib.HTTPConnection('m.jiwa.fm:80')
  56. r.putrequest('GET',url)
  57. r.putheader('Host','m.jiwa.fm:80')
  58. r.putheader('User-Agent',randomUA())
  59. r.endheaders()
  60. token = r.getresponse().read()
  61. if token=='-1':
  62. errors += 1
  63. time.sleep(1)
  64. else:
  65. f = open(filename,'wb')
  66. f.write(token)
  67. f.close()
  68. return True
  69. return False
  70.  
  71. def getToken(sid):
  72. params = {
  73. 's':str(sid),
  74. }
  75. p = urllib.urlencode(params)
  76.  
  77. r = httplib.HTTPConnection('m.jiwa.fm:80')
  78. r.putrequest('POST','http://m.jiwa.fm/token.php?r=1')
  79. r.putheader('Host','m.jiwa.fm:80')
  80. r.putheader('User-Agent',randomUA())
  81. r.putheader('Content-Type','application/x-www-form-urlencoded')
  82. r.putheader('Content-Length',str(len(p)))
  83. r.endheaders()
  84. r.send(p)
  85.  
  86. token = r.getresponse().read()
  87. return token
  88.  
  89.  
  90. if __name__ == '__main__':
  91.  
  92. print '[>] iJaw - A Jiwa search & download tool'
  93. print '[>] author: virtualabs (http://www.virtualabs.fr)'
  94. print '[>]'
  95.  
  96. if len(sys.argv)>2:
  97. operator = sys.argv[1]
  98. if operator == 'search':
  99. kw = ' '.join(sys.argv[2:])
  100. print '[+] Looking for songs ...'
  101. tracks = lookupSongs(kw)
  102. if tracks:
  103. if len(tracks)>0:
  104. print ''
  105. print 'Song ID - Song Name [Artist, Album]'
  106. print ''
  107. for t in tracks:
  108. print '%s - %s [%s, %s]' % (t['songId'],filter(t['songName']),filter(t['artistName']),filter(t['albumName']))
  109. print ''
  110. print '%d songs found' % len(tracks)
  111. else:
  112. print '[!] No songs found.'
  113. else:
  114. print '[!] No songs found.'
  115. elif operator == 'get':
  116. tracks = [t for t in sys.argv[2:]]
  117. for sid in tracks:
  118. print '[+] getting track info ...'
  119. _track = getSongDetails(int(sid))
  120. filename = '%s_%s_%s.mp3' % (_track['artistName'].replace(' ','_'),_track['albumName'].replace(' ','_'),_track['songName'].replace(' ','_'))
  121. print '[+] downloading track ...'
  122. if dlTrack(sid,filter(filename)):
  123. print '[i] saved to %s.' % filter(filename)
  124. else:
  125. print '[!] Unable to download song.'
  126. elif operator == 'dl':
  127. kw = ' '.join(sys.argv[2:])
  128. tracks = lookupSongs(kw)
  129. if tracks:
  130. for t in tracks:
  131. _track = getSongDetails(t['songId'])
  132. filename = '%s_%s_%s.mp3' % (_track['artistName'].replace(' ','_'),_track['albumName'].replace(' ','_'),_track['songName'].replace(' ','_'))
  133. print '[+] downloading song "%s [%s]" ...' % (_track['songName'],_track['artistName'])
  134. if dlTrack(int(t['songId']),filter(filename)):
  135. print '[i] saved to %s.' % filter(filename)
  136. else:
  137. print '[!] Failed to download song: %s' % _track['songName']
  138. print '[+] Done.'
  139. else:
  140. print '[!] No songs found'
  141. else:
  142. print '[i] usage: %s [search|get|dl] [second arg]'
  143. print '[i]'
  144. print '[i] + you can look for songs:'
  145. print '[i]'
  146. print '[i] $ ijaw.py search Madonna like a virgin'
  147. print '[i] $ 12345 - Like a virgin [Madonna, ...]'
  148. print '[i]'
  149. print '[i] + and download a given song with its ID:'
  150. print '[i] $ ijaw.py get 12345'
  151. print '[i]'
  152. print '[i] + or download all tracks found:'
  153. print '[i]'
  154. print '[i] $ ijaw.py dl Madonna like a virgin'
  155. print '[i]'
  156. print '[i] + Have phun !'

Télécharger

Conclusion

Il est étonnant que le passage de Jiwa sous la houlette d’Allomusic n’ait pas introduit un nouveau système plus fiable de lecture de musique en ligne, et il est franchement dommage qu’une plateforme labellisée PUR force autant la main de l’utilisateur vers les comptes payants, en tentant de promouvoir l’écoute gratuite mais très limitée. OK, le modèle économique basé sur les pubs n’est pas forcément viable, mais on est pas QUE des vaches à lait. A bon entendeur ...