├── .gitignore
├── README.rst
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
└── yamusic_app_tests.py
└── yamusic
├── __init__.py
└── app.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pot
3 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Python YaMusic Readme
2 | =====================
3 |
4 | This library for using yandex music in python with orm and cursor.
5 |
6 | Using ORM
7 | ---------
8 |
9 | Import models:
10 | >>> from yamusic.app import Artist, Album, Track
11 |
12 | Use filter for iterate result:
13 | >>> Track.objects.filter(title='this must be', artist__title='royksopp', album__title='junior')
14 | >>> Album.objects.filter(artist__title='royksopp', title='junior')
15 | >>> Artist.objects.filter(title='royksopp')
16 |
17 | For managing iterated filter result you can use:
18 | >>> Track.objects.filter(artist__title='unkle').all()
19 | >>> Album.objects.filter(artist__title='a place')[1:5:2]
20 | >>> Artist.objects.filter(title='the')[5]
21 |
22 | If you want to get single item use *get* instead *filter*:
23 | >>> Track.objects.get(title='this must be', artist__title='royksopp', album__title='junior')
24 | >>> Album.objects.get(artist__title='royksopp', title='junior')
25 | >>> Artist.objects.get(title='royksopp')
26 |
27 | You can get *Album* and *Artist* by *id*:
28 | >>> Artist.objects.get(id=49522)
29 | >>> Album.objects.get(id=34596)
30 |
31 | For getting track you need *id* and on of *album*, *album__id* or *album__title*
32 | >>> Track.objects.get(id=id, album__id=album__id)
33 |
34 | Or if you only need play use *id* and *storage_dir*:
35 | >>> Track.objects.get(id=id, storage_dir=storage_dir)
36 |
37 | Using cursor [deprecated]
38 | -------------------------
39 |
40 | Import search app:
41 | >>> from yamusic.app import Search, cursor
42 |
43 | Cursor can search artists:
44 | >>> cursor.search(Search.TYPE_ARTISTS, 'query')
45 |
46 | Albums:
47 | >>> cursor.search(Search.TYPE_ALBUMS, 'query')
48 |
49 | And tracks:
50 | >>> cursor.search(Search.TYPE_TRACKS, 'query')
51 |
52 | If single=True, search return one item:
53 | >>> cursor.search(Search.TYPE_TRACKS, 'query', single=True)
54 |
55 | Else - return iterator.
56 |
57 | Work with search result
58 | -----------------------
59 |
60 | For getting data from albums and artists use:
61 | >>> artist.get_albums()
62 | >>> artist.get_tracks()
63 | >>> album.get_tracks()
64 |
65 | For opening track like file use:
66 | >>> track.open()
67 |
68 | For fast getting data objects have:
69 | >>> track.artist
70 | >>> track.album
71 | >>> track.title
72 | >>> album.artist
73 | >>> album.title
74 | >>> artist.title
75 |
76 | Other you can find in source.
77 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [egg_info]
2 | tag_build = dev
3 | tag_svn_revision = true
4 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import sys, os
3 |
4 | version = '63'
5 |
6 |
7 | setup(name='python-yamusic',
8 | version=version,
9 | description="Library for acessing yandex music from python",
10 | long_description="""\
11 | """,
12 | classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
13 | keywords='yandex music',
14 | author='Vladimir Yakovlev',
15 | author_email='nvbn.rm@gmail.com',
16 | url='http://github.com/nvbn/python-yamusic',
17 | license='LGPL',
18 | packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
19 | include_package_data=True,
20 | zip_safe=True,
21 | install_requires=requireds,
22 | entry_points="""
23 | # -*- Entry points: -*-
24 | """,
25 | )
26 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvbn/python-yamusic/88a1de6ebc5e7fd216f256fcde7343eb32985e85/tests/__init__.py
--------------------------------------------------------------------------------
/tests/yamusic_app_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This program is free software: you can redistribute it and/or modify
3 | # it under the terms of the GNU Lesser General Public License
4 | # as published by the Free Software Foundation.
5 | #
6 | # This program is distributed in the hope that it will be useful,
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 | # GNU General Public License for more details.
10 | #
11 | # You should have received a copy of the GNU General Public License
12 | # along with this program. If not, see .
13 |
14 | import unittest
15 | import sys
16 | import os
17 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
18 | from yamusic.app import cursor, Search, Artist, Album, Track
19 | from itertools import islice
20 |
21 |
22 | class CursorTestCase(unittest.TestCase):
23 | def setUp(self):
24 | self.artists = (
25 | 'notfoundableartist',
26 | 'royksopp', 'liars', 'portishead'
27 | )
28 | self.not_fount_artist = self.artists[0]
29 | self.albums = (
30 | 'notofoundableshitalbum',
31 | 'junior', 'jade motel',
32 | )
33 | self.not_found_album = self.albums[0]
34 | self.tracks = (
35 | 'song 2', 'paranoid android', 'castle'
36 | )
37 |
38 | def test_artist(self):
39 | for artist in self.artists:
40 | artist_list = list(
41 | islice(cursor.search(Search.TYPE_ARTISTS, artist), 1)
42 | )
43 | if artist == self.not_fount_artist:
44 | self.assertEqual(
45 | len(artist_list), 0,
46 | 'Not found not work!'
47 | )
48 | else:
49 | self.assertEqual(
50 | len(artist_list), 1,
51 | 'Search artist not work!'
52 | )
53 | artist_obj = artist_list[0]
54 | albums = list(artist_obj.get_albums())
55 | self.assertEqual(
56 | len(albums) > 0, True,
57 | 'get_album not work!'
58 | )
59 | for album in albums:
60 | tracks = list(album.get_tracks())
61 | self.assertEqual(
62 | len(tracks) > 0, True,
63 | 'get_tracks from album not work!'
64 | )
65 |
66 | def test_albums(self):
67 | for album in self.albums:
68 | album_list = list(
69 | islice(cursor.search(Search.TYPE_ALBUMS, album), 1)
70 | )
71 | if album == self.not_found_album:
72 | self.assertEqual(
73 | len(album_list), 0,
74 | 'Not found not work!'
75 | )
76 | else:
77 | self.assertEqual(
78 | len(album_list), 1,
79 | 'Search album not work!'
80 | )
81 | album_obj = album_list[0]
82 | tracks = list(album_obj.get_tracks())
83 | self.assertEqual(
84 | len(tracks) > 0, True,
85 | 'get_tracks from album not work!'
86 | )
87 |
88 | def test_tracks(self):
89 | for track in self.tracks:
90 | track = cursor.search(Search.TYPE_TRACKS, track, single=True)
91 | self.assertEqual(
92 | len(track.title) > 0, True,
93 | 'Track search not work!'
94 | )
95 | self.assertEqual(
96 | len(track.open().read(200)), 200,
97 | 'Track downloading not work!'
98 | )
99 |
100 |
101 | class ORMTestCase(unittest.TestCase):
102 | def setUp(self):
103 | self.artists = (
104 | 'notfoundableartist',
105 | 'royksopp', 'liars', 'portishead'
106 | )
107 | self.not_fount_artist = self.artists[0]
108 | self.albums = (
109 | 'notofoundableshitalbum',
110 | 'junior', 'jade motel',
111 | )
112 | self.not_found_album = self.albums[0]
113 | self.tracks = (
114 | 'song 2', 'paranoid android', 'castle'
115 | )
116 |
117 | def test_artist(self):
118 | for artist in self.artists:
119 | artist_list = Artist.objects.filter(title=artist).all()[0:1]
120 | if artist == self.not_fount_artist:
121 | self.assertEqual(
122 | len(artist_list), 0,
123 | 'Not found not work!'
124 | )
125 | else:
126 | self.assertEqual(
127 | len(artist_list), 1,
128 | 'Search artist not work!'
129 | )
130 | artist_obj = artist_list[0]
131 | albums = list(artist_obj.get_albums())
132 | self.assertEqual(
133 | len(albums) > 0, True,
134 | 'get_album not work!'
135 | )
136 | for album in albums:
137 | tracks = list(album.get_tracks())
138 | self.assertEqual(
139 | len(tracks) > 0, True,
140 | 'get_tracks from album not work!'
141 | )
142 | self.assertEqual(Artist.objects.get(id=24463).title, 'Archive')
143 |
144 | def test_albums(self):
145 | for album in self.albums:
146 | album_list = Album.objects.filter(title=album).all()[0:1]
147 | if album == self.not_found_album:
148 | self.assertEqual(
149 | len(album_list), 0,
150 | 'Not found not work!'
151 | )
152 | else:
153 | self.assertEqual(
154 | len(album_list), 1,
155 | 'Search album not work!'
156 | )
157 | album_obj = album_list[0]
158 | tracks = list(album_obj.get_tracks())
159 | self.assertEqual(
160 | len(tracks) > 0, True,
161 | 'get_tracks from album not work!'
162 | )
163 | self.assertEqual(Album.objects.get(id=298323).title, 'The Less You Know, The Better')
164 |
165 | def test_tracks(self):
166 | for track in self.tracks:
167 | track = Track.objects.get(title=track)
168 | self.assertEqual(
169 | len(track.title) > 0, True,
170 | 'Track search not work!'
171 | )
172 | self.assertEqual(
173 | len(track.open().read(200)), 200,
174 | 'Track downloading not work!'
175 | )
176 | self.assertEqual(Track.objects.get(id=1675302, album__id=166649).title, 'Karen')
177 |
178 | if __name__ == '__main__':
179 | unittest.main()
180 |
--------------------------------------------------------------------------------
/yamusic/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nvbn/python-yamusic/88a1de6ebc5e7fd216f256fcde7343eb32985e85/yamusic/__init__.py
--------------------------------------------------------------------------------
/yamusic/app.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This program is free software: you can redistribute it and/or modify
3 | # it under the terms of the GNU Lesser General Public License
4 | # as published by the Free Software Foundation.
5 | #
6 | # This program is distributed in the hope that it will be useful,
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 | # GNU General Public License for more details.
10 | #
11 | # You should have received a copy of the GNU General Public License
12 | # along with this program. If not, see .
13 |
14 | from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup
15 | import urllib2
16 | import cookielib
17 | from itertools import imap, islice
18 | from hashlib import md5
19 | import json
20 | import re
21 |
22 | TYPE_TRACKS = 0
23 | TYPE_ALBUMS = 1
24 | TYPE_ARTISTS = 2
25 |
26 |
27 | def fix_json_single_quotes(text):
28 | def replace_quotes(match):
29 | if match.group(1)[0] == "'":
30 | return '"%s"'%(match.group(2).replace(r"\'","'").replace('"',r'\"'))
31 | else:
32 | return '"%s"'%(match.group(2))
33 |
34 | return re.sub(r"""(["'])((?:\\?.)*?)\1""", replace_quotes, text);
35 |
36 | class Cached(object):
37 | """Simple cache for avoiding duplicates"""
38 | CACHE = ''
39 |
40 | @classmethod
41 | def get(cls, **kwargs):
42 | id = kwargs.get('id', -1)
43 | if id in getattr(Search, cls.CACHE):
44 | return getattr(Search, cls.CACHE)[id]
45 | result = cls(**kwargs)
46 | if result.id:
47 | getattr(Search, cls.CACHE)[id] = result
48 | return result
49 |
50 | def __unicode__(self):
51 | return ''
52 |
53 | def __repr__(self):
54 | return '<%s: %s>' % (
55 | self.__class__.__name__,
56 | self.__unicode__()
57 | )
58 |
59 |
60 | class Manager(object):
61 |
62 | def __init__(self, search_cls, _type, filter_fnc=None):
63 | self.search_cls = search_cls
64 | self.type = _type
65 | self._filter_fnc = filter_fnc
66 |
67 | @property
68 | def filter_result(self):
69 | if not self._filter_fnc:
70 | raise ValueError
71 | if not hasattr(self, '_filter_result'):
72 | self._filter_result = self._filter_fnc()
73 | return self._filter_result
74 |
75 | def _get_titles(self, *args, **kwargs):
76 | """Get title for search
77 |
78 | Keyword Arguments:
79 | *args -- classes for search
80 | **kwargs -- search fields
81 |
82 | Returns: str
83 | """
84 | titles = []
85 | for cls, cls_name in map(
86 | lambda arg: (arg, arg.__name__.lower()), args
87 | ):
88 | cls_dict = dict(filter(
89 | lambda (name, val):
90 | val and cls_name in name, kwargs.items()
91 | ))
92 | title = cls_dict.get(cls_name + '__title', '')
93 | id = cls_dict.get(cls_name + '__id', None)
94 | obj = cls_dict.get(cls_name)
95 | if not title:
96 | if id and not obj:
97 | obj = cls.objects.get(id=id)
98 | if obj:
99 | title = obj.title
100 | titles.append(title)
101 | return ' '.join(titles)
102 |
103 | def _search(self, single, title='', **kwargs):
104 | titles = self._get_titles(*self.search_cls, **kwargs)
105 | if title:
106 | titles = ' '.join([titles, title])
107 | return cursor.search(self.type, titles, single=single)
108 |
109 | def all(self):
110 | return list(self.filter_result)
111 |
112 | def filter(self, title='', **kwargs):
113 | return Manager(
114 | self.search_cls,
115 | self.type,
116 | lambda: self._search(False, title, **kwargs)
117 | )
118 |
119 | def get(self, title='', **kwargs):
120 | if 'id' in kwargs:
121 | raise ValueError
122 | return self._search(True, title, **kwargs)
123 |
124 | def __getitem__(self, item):
125 | if type(item) is slice:
126 | return list(islice(
127 | self.filter_result, item.start, item.stop, item.step
128 | ))
129 | else:
130 | return list(islice(self.filter_result, item, item + 1))[0]
131 |
132 | def __iter__(self):
133 | return self.filter_result
134 |
135 | def __len__(self):
136 | return len(self.all())
137 |
138 |
139 | class ArtistManager(Manager):
140 |
141 | def __init__(self):
142 | super(ArtistManager, self).__init__([], TYPE_ARTISTS)
143 |
144 | def get(self, id=None, **kwargs):
145 | if id:
146 | artist = Artist(id=id)
147 | artist.get_data()
148 | return artist
149 | else:
150 | return super(ArtistManager, self).get(**kwargs)
151 |
152 |
153 | class Artist(Cached):
154 | """Artist item"""
155 | CACHE = 'ARTISTS_CACHE'
156 | objects = ArtistManager()
157 |
158 | def __init__(self, id=None, title=None):
159 | self.id = id
160 | self.title = title
161 |
162 | def __unicode__(self):
163 | return self.title
164 |
165 | def get_albums(self):
166 | """Lazy get artist albums"""
167 | if not hasattr(self, '_albums'):
168 | self.get_data()
169 | return self._albums
170 |
171 | def get_data(self):
172 | self._albums = []
173 | data = cursor.open(
174 | 'http://music.yandex.ru/fragment/artist/%d/tracks' %
175 | int(self.id)
176 | ).read()
177 | soup = BeautifulSoup(data)
178 | self.title = cursor._remove_html(
179 | soup.find(
180 | 'h1', cursor._class_filter('b-title__title')
181 | ).__unicode__()
182 | )
183 | for album in soup.findAll(
184 | 'div', cursor._class_filter('b-album-control')
185 | ):
186 | try:
187 | album_data = json.loads(fix_json_single_quotes(album['onclick'][7:]))
188 | album = Album.get(
189 | id=album_data.get('id'),
190 | title=album_data.get('title'),
191 | artist=self,
192 | cover=album_data.get('cover')
193 | )
194 | self._albums.append(album)
195 | album.set_tracks(album_data.get('tracks'))
196 | except ValueError:
197 | pass
198 |
199 | def get_tracks(self):
200 | """Lazy get artist tracks"""
201 | if hasattr(self, '_tracks'):
202 | return self._tracks
203 | tracks = []
204 | for album in self.get_albums():
205 | tracks += album.get_tracks()
206 | self._tracks = tracks
207 | return self._tracks
208 |
209 |
210 | class AlbumManager(Manager):
211 |
212 | def __init__(self):
213 | super(AlbumManager, self).__init__([Artist], TYPE_ALBUMS)
214 |
215 | def get(self, id=None, **kwargs):
216 | if id:
217 | album = Album(id=id)
218 | album.get_data()
219 | return album
220 | else:
221 | return super(AlbumManager, self).get(**kwargs)
222 |
223 |
224 | class Album(Cached):
225 | """Album item"""
226 | CACHE = 'ALBUMS_CACHE'
227 | objects = AlbumManager()
228 |
229 | def __init__(self, id=None, title=None, cover=None,
230 | artist__id=None, artist__title=None,
231 | artist=None):
232 | self.id = id
233 | self.title = title
234 | self.cover = cover
235 | if artist__id or artist__title:
236 | self.artist = Artist.get(
237 | id=artist__id,
238 | title=artist__title,
239 | )
240 | if artist:
241 | self.artist = artist
242 |
243 | def set_tracks(self, tracks):
244 | """Set tracks to album"""
245 | self._tracks = []
246 | for track in tracks:
247 | self._tracks.append(
248 | Track.get(
249 | id=track['id'],
250 | title=track['title'],
251 | artist=self.artist,
252 | album=self,
253 | duration=track['duration'],
254 | storage_dir=track['storage_dir'],
255 | )
256 | )
257 |
258 | def get_tracks(self):
259 | """Lazy get album tracks"""
260 | if not hasattr(self, '_tracks'):
261 | self.get_data()
262 | return self._tracks
263 |
264 | def get_data(self):
265 | data = cursor.open(
266 | 'http://music.yandex.ru/fragment/album/%d' %
267 | int(self.id)
268 | ).read()
269 | soup = BeautifulSoup(data)
270 | self._tracks = []
271 | artist_soup = soup.find(
272 | 'div', cursor._class_filter('b-title__artist')
273 | ).find('a')
274 | self.artist__id = artist_soup['href'].split('/')[-1]
275 | self.artist__title = cursor._remove_html(artist_soup.__unicode__())
276 | self.artist = Artist.get(
277 | id=self.artist__id,
278 | title=self.artist__title,
279 | )
280 | for track in soup.findAll('div', cursor._class_filter('b-track')):
281 | track_data = json.loads(track['onclick'][7:])
282 | self._tracks.append(Track.get(
283 | id=track_data['id'],
284 | title=track_data['title'],
285 | artist=self.artist,
286 | album=self,
287 | storage_dir=track_data['storage_dir'],
288 | duration=track_data['duration']
289 | ))
290 | self.title = cursor._remove_html(
291 | soup.find(
292 | 'h1', cursor._class_filter('b-title__title')
293 | ).__unicode__()
294 | )
295 |
296 | def __unicode__(self):
297 | return u'%s - %s' % (self.artist, self.title)
298 |
299 |
300 | class TrackManager(Manager):
301 |
302 | def __init__(self):
303 | super(TrackManager, self).__init__((Artist, Album), TYPE_TRACKS)
304 |
305 | def get(self, id=None, storage_dir=None, **kwargs):
306 | album = kwargs.get('album', None)
307 | album__id = kwargs.get('album__id', None)
308 | album__title = kwargs.get('album__title', None)
309 | if not album:
310 | if album__id:
311 | album = Album.objects.get(id=album__id)
312 | elif album__title:
313 | album = Album.objects.get(title=album__title)
314 | if id and storage_dir:
315 | return Track(id, storage_dir, **kwargs)
316 | elif id and album:
317 | track = Track(id=id, album=album)
318 | track.get_data()
319 | return track
320 | else:
321 | return super(TrackManager, self).get(**kwargs)
322 |
323 |
324 | class Track(Cached):
325 | """Track item"""
326 | CACHE = "TRACKS_CACHE"
327 | objects = TrackManager()
328 |
329 | def __init__(self, id=None, title=None, artist__id=None,
330 | artist__title=None, album__id=None, album__title=None,
331 | album__cover=None, duration=None, storage_dir=None,
332 | artist=None, album=None):
333 | self.id = int(id)
334 | self.title = title
335 | if artist__id or artist__title:
336 | self.artist = Artist.get(
337 | id=artist__id,
338 | title=artist__title,
339 | )
340 | if album__id or album__title or album__cover:
341 | self.album = Album.get(
342 | id=album__id,
343 | title=album__title,
344 | cover=album__cover,
345 | )
346 | self.duration = duration
347 | self.storage_dir = storage_dir
348 | if artist:
349 | self.artist = artist
350 | if album:
351 | self.album = album
352 |
353 | def __unicode__(self):
354 | return u'%s - %s' % (self.artist, self.title)
355 |
356 | @property
357 | def url(self):
358 | """Calculate track url"""
359 | if not self.storage_dir:
360 | raise AttributeError('Storage dir required!')
361 | info_path_data = cursor.open(
362 | 'http://storage.music.yandex.ru/get/%s/2.xml' % self.storage_dir
363 | ).read()
364 | info_path_soup = BeautifulStoneSoup(info_path_data)
365 | file_path_data = cursor.open(
366 | 'http://storage.music.yandex.ru/download-info/%s/%s' % (
367 | self.storage_dir,
368 | info_path_soup.find('track')['filename'],
369 | )
370 | ).read()
371 | file_path_soup = BeautifulStoneSoup(file_path_data).find('download-info')
372 | path = file_path_soup.find('path').text
373 | return 'http://%s/get-mp3/%s/%s%s?track-id=%d®ion=225&from=service-search' % (
374 | file_path_soup.find('host').text,
375 | cursor.get_key(path[1:] + file_path_soup.find('s').text),
376 | file_path_soup.find('ts').text,
377 | path,
378 | int(self.id),
379 | )
380 |
381 | def get_data(self):
382 | self.artist = self.album.artist
383 | soup = BeautifulSoup(
384 | cursor.open(
385 | 'http://music.yandex.ru/fragment/track/%d/album/%d' % (
386 | self.id,
387 | self.album.id,
388 | )
389 | ).read()
390 | )
391 | data = soup.find('div', cursor._class_filter('b-track b-track_type_track js-track'))
392 | for attr, val in cursor._parse_track(data).items():
393 | setattr(self, attr, val)
394 |
395 | def open(self):
396 | """Open track like urlopen"""
397 | return cursor.open(self.url)
398 |
399 |
400 | class Search(object):
401 | """Main search class"""
402 | TYPE_TRACKS = TYPE_TRACKS
403 | TYPE_ALBUMS = TYPE_ALBUMS
404 | TYPE_ARTISTS = TYPE_ARTISTS
405 | TYPES = {
406 | TYPE_TRACKS: 'tracks',
407 | TYPE_ALBUMS: 'albums',
408 | TYPE_ARTISTS: 'artists',
409 | }
410 | URL = 'http://music.yandex.ru/fragment/search?text=%(text)s&type=%(type)s&page=%(page)d'
411 | TRACKS_CACHE = {}
412 | ALBUMS_CACHE = {}
413 | ARTISTS_CACHE = {}
414 |
415 | def __init__(self):
416 | self._opener = self._cookie_jar = None
417 | self.authenticated = False
418 |
419 | @property
420 | def cookie_jar(self):
421 | if not self._cookie_jar:
422 | self._cookie_jar = cookielib.CookieJar()
423 | return self._cookie_jar
424 |
425 | @property
426 | def opener(self):
427 | if not self._opener:
428 | self._opener = urllib2.build_opener(
429 | urllib2.HTTPCookieProcessor(self.cookie_jar)
430 | )
431 | return self._opener
432 |
433 | def open(self, url):
434 | """Open with cookies"""
435 | return self.opener.open(url)
436 |
437 | def get_key(self, key):
438 | """Get secret key for track loading"""
439 | return md5('XGRlBW9FXlekgbPrRHuSiA' + key.replace('\r\n', '\n')).hexdigest()
440 |
441 | def _class_filter(self, cls_name):
442 | """Create BeautifulSoup class filter"""
443 | return {'class': re.compile(r'\b%s\b' % cls_name)}
444 |
445 | def _remove_html(self, data):
446 | p = re.compile(r'<.*?>')
447 | try:
448 | return p.sub('', data)
449 | except TypeError:
450 | return data
451 |
452 | def _parse_track(self, data):
453 | track = json.loads(data['onclick'][7:])
454 | return {
455 | 'id': track['id'],
456 | 'title': track['title'],
457 | 'artist__id': track['artist_id'],
458 | 'artist__title': track['artist'],
459 | 'album__id': track['album_id'],
460 | 'album__title': track['album'],
461 | 'album__cover': track['cover'],
462 | 'storage_dir': track['storage_dir']
463 | }
464 |
465 | def _get_tracks(self, soup):
466 | for track in soup.findAll('div', self._class_filter('b-track')):
467 | yield Track.get(**self._parse_track(track))
468 |
469 | def _get_albums(self, soup):
470 | for album in soup.findAll('div', self._class_filter('b-albums')):
471 | cover_a = album.find('div', self._class_filter('b-albums__cover')).find('a')
472 | artist_a = album.find('a',
473 | self._class_filter('b-link_class_albums-title-link')
474 | )
475 | yield Album.get(
476 | id=cover_a['href'].split('/')[-1],
477 | title=self._remove_html(album.find('a',
478 | self._class_filter('b-link_class_albums-title-link')
479 | ).__unicode__()),
480 | cover=cover_a.find('img')['src'],
481 | artist__id=artist_a['href'].split('/')[-1],
482 | artist__title=self._remove_html(artist_a.__unicode__())
483 | )
484 |
485 | def _get_artists(self, soup):
486 | for artist in imap(
487 | lambda obj: obj.find('a'),
488 | soup.findAll('div', self._class_filter('b-artist-group'))
489 | ):
490 | yield Artist.get(
491 | id=artist['href'].split('/')[-1],
492 | title=self._remove_html(artist.__unicode__())
493 | )
494 |
495 | def _get(self, type, soup):
496 | if type == self.TYPE_TRACKS:
497 | return self._get_tracks(soup)
498 | elif type == self.TYPE_ALBUMS:
499 | return self._get_albums(soup)
500 | elif type == self.TYPE_ARTISTS:
501 | return self._get_artists(soup)
502 |
503 | def _get_result(self, type, text): # start from 0!
504 | pages_count = 1
505 | current_page = 0
506 | while pages_count > current_page:
507 | result = self.open(self.URL % {
508 | 'text': text,
509 | 'type': self.TYPES[type],
510 | 'page': current_page,
511 | }).read()
512 | soup = BeautifulSoup(result)
513 | try:
514 | try:
515 | pages_count = int(soup.findAll('a', self._class_filter('b-pager__page'))[-1].text) # start form 1!
516 | except UnicodeEncodeError: # fix work with ... page
517 | pages_count = int(soup.findAll('a', self._class_filter('b-pager__page'))[-2].text) # start form 1!
518 | current_page = int(soup.find('b', self._class_filter('b-pager__current')).text) # start from 1!
519 | except IndexError:
520 | current_page += 1 # if only one page
521 | for obj in self._get(type, soup):
522 | yield obj
523 |
524 | def search(self, type, text, single=False):
525 | if type not in self.TYPES:
526 | raise AttributeError('Wrong type')
527 | result = self._get_result(type, text)
528 | if single:
529 | return list(islice(result, 1))[0]
530 | else:
531 | return result
532 |
533 | cursor = Search()
534 |
--------------------------------------------------------------------------------