def main(): """ Пример отображения 5 последних альбомов пользователя """ login, password = '******', 'password' vk_session = vk_api.VkApi(login, password) try: vk_session.auth() except vk_api.AuthError as error_msg: print(error_msg) return vkaudio = VkAudio(vk_session) albums = vkaudio.get_albums(194957739) print('\nLast 5:') for album in albums[:5]: print(album['title']) # Ищем треки последнего альбома print('\nSearch for', albums[0]['title']) tracks = vkaudio.get(album_id=albums[0]['id']) for n, track in enumerate(tracks, 1): print('{}. {} {}'.format(n, track['title'], track['url']))
def main(): """ Пример отображения 5 последних альбомов пользователя """ login, password = '******', 'password' vk_session = vk_api.VkApi(login, password) try: vk_session.auth() except vk_api.AuthError as error_msg: print(error_msg) return vkaudio = VkAudio(vk_session) albums = [] offset = 0 while True: temp_albums = vkaudio.get_albums(owner_id='194957739', offset=offset) if not temp_albums: break albums += temp_albums offset += len(temp_albums) print('\nLast 5:') for album in albums[:5]: print(album['title']) # Ищем треки последнего альбома print('\nSearch for', albums[0]['title']) tracks = vkaudio.get(album_id=albums[0]['id']) for n, track in enumerate(tracks, 1): print('{}. {} {}'.format(n, track['title'], track['url']))
class VkSongs(object): """VK-Songs downloads VK audio files""" def __init__(self): self.logger = VkSongs.get_logger(level=logging.DEBUG) # TODO: Configurable log self.session = requests.Session() self.is_logged_in = False self.vk = None self.vk_audio = None def login(self, login_user, login_pass): """Logs in to VK""" vk_session = vk_api.VkApi( login_user, login_pass, auth_handler=self.two_factor_handler, captcha_handler=self.captcha_handler, app_id=6692066, ) try: vk_session.auth() except vk_api.AuthError as error_msg: self.logger.error('Login failed for {0}. Reason: {1}'.format(login_user, error_msg)) return self.vk = vk_session.get_api() self.vk_audio = VkAudio(vk_session) self.is_logged_in = True @staticmethod def two_factor_handler(): key = input("♫ Enter authentication code: ") remember_device = True return key, remember_device @staticmethod def captcha_handler(captcha): # TODO: Implement it using PyInquirer key = input("♫ Enter captcha code {0}: ".format(captcha.get_url())).strip() return captcha.try_again(key) @staticmethod def get_color(level): print(level) if level == 'ERROR': return Fore.RED elif level == 'WARNING': return Fore.YELLOW else: return '' @staticmethod def get_logger(level=logging.DEBUG): """Returns a logger""" logger = logging.getLogger(__name__) fh = logging.FileHandler('vk-songs.log', 'w') fh.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) fh.setLevel(level) logger.addHandler(fh) import sys sh = logging.StreamHandler(sys.stdout) sh.setFormatter(logging.Formatter(Fore.RED + '%(levelname)s: %(message)s')) sh.setLevel(logging.ERROR) logger.addHandler(sh) return logger def download(self, song, save_dir='./', tag=False): """Downloads the media file""" from pathvalidate import sanitize_filename import eyed3 from eyed3.id3 import ID3_V2_4 url = song.url base_name = '{0} - {1}.mp3'.format(song.artist.strip(), song.title.strip()) # TODO: Configurable file mask base_name = sanitize_filename(base_name, replacement_text='_') file_path = os.path.join(save_dir, base_name) if not os.path.isfile(file_path): import time with open(file_path, 'wb') as media_file: try: content = self.session.get(url).content except requests.exceptions.ConnectionError: time.sleep(5) content = self.session.get(url).content media_file.write(content) file_time = time.time() os.utime(file_path, (file_time, file_time)) if tag: audio_file = eyed3.load(file_path) eyed3.core.log.disabled = True eyed3.id3.log.disabled = True if audio_file: if not audio_file.tag: audio_file.initTag(version=ID3_V2_4) audio_file.tag.artist = song.artist audio_file.tag.album_artist = song.artist audio_file.tag.title = song.title if song.album is not None: audio_file.tag.album = song.album audio_file.tag.save(version=ID3_V2_4, encoding='utf_8') return True else: return False def search(self, query): search = self.vk_audio.search(q=query) # TODO: Generator result = [] for audio in search: audio['url'] = audio['url'].split('?', 1)[0] result.append(VkSong(num=audio['id'], artist=audio['artist'], title=audio['title'], url=audio['url'])) if len(result) == 0: self.logger.error('No songs found') return result def search_user(self, owner_id, query): search = self.vk_audio.search_user(owner_id=owner_id, q=query) result = [] for audio in search: audio['url'] = audio['url'].split('?', 1)[0] result.append(VkSong(num=audio['id'], artist=audio['artist'], title=audio['title'], url=audio['url'])) if len(result) == 0: self.logger.error('No songs found') return result def get_albums(self, owner_id): albums = self.vk_audio.get_albums(owner_id=owner_id) # TODO: Generator result = [] for album in albums: result.append(VkAlbum(title=album['title'], album_id=album['id'], owner_id=album['owner_id'])) if len(result) == 0: self.logger.error('No albums found') return result def get(self, owner_id=None, album_id=None, album_title=None): songs = self.vk_audio.get(owner_id=owner_id, album_id=album_id) # TODO: Generator result = [] for audio in songs: audio['url'] = audio['url'].split('?', 1)[0] result.append(VkSong(num=audio['id'], artist=audio['artist'], title=audio['title'], url=audio['url'], album=album_title)) if len(result) == 0: self.logger.error('No songs found') return result @staticmethod def should_login(dict): # TODO: Implement user session caching return True @staticmethod def make_dst_dir(destination='./', artist='/'): """Creates the destination directory.""" destination = destination + artist try: os.makedirs(destination) except OSError as err: import errno if err.errno == errno.EEXIST and os.path.isdir(destination): # Directory already exists pass else: # Target dir exists as a file, or a different error raise return destination
class GetAudioListThread(QThread): signal = pyqtSignal("PyQt_PyObject") str_signal = pyqtSignal(str) image_signal = pyqtSignal("QImage") def __init__(self, cookie, window): QThread.__init__(self) self.login = "" self.password = "" self.user_link = "" self.statusBar = None self.save_password = False self.authorized = False self.cookie = cookie self.window = window def __del__(self): self.wait() def _user_auth(self): if self.login: self.session = VkApi( login=self.login, password=self.password, auth_handler=self.auth_handler, captcha_handler=self.captcha_handler, config_filename=self.cookie, ) self.statusBar.showMessage("Авторизация.") self.session.auth() else: self.statusBar.showMessage( "Логин не указан, использование пароля в качестве токена") self.session = VkApi(token=self.password, captcha_handler=self.captcha_handler) self.vk_audio = VkAudio(self.session) self.authorized = True def _get_audio(self): tracks = [] albums = [] string = str() # Try to get post audio list post = self.get_group_and_post_id(self.user_link) album = self.get_album_id(self.user_link) if isinstance(post, tuple): owner_id, post_id = post self.statusBar.showMessage("Получение списка аудиозаписей поста.") string = "Аудиозаписи поста" tracks = self.vk_audio.get_post_audio(owner_id, post_id) audios = ",".join(["{owner_id}_{id}".format(**i) for i in tracks]) tracks = self.session.method(method="audio.getById", values={"audios": audios}) elif isinstance(album, tuple): owner_id, album_id, *_ = album self.statusBar.showMessage( "Получение списка аудиозаписей альбома.") string = "Аудиозаписи альбома" tracks = self._get_tracks(owner_id, album_id) else: user_id = self.get_user_id(self.user_link) # Try to get user or group audio list # noinspection PyBroadException try: owner_id = self.session.method("users.get", dict(user_ids=user_id))[0] self.statusBar.showMessage( "Получение списка аудиозаписей пользователя: {first_name} {last_name}" .format(**owner_id)) string = "Музыка пользователя: {first_name} {last_name}".format( **owner_id) except Exception: group_id = self.session.method("groups.getById", dict(group_id=user_id))[0] self.statusBar.showMessage( "Получение списка аудиозаписей сообщества: {name}".format( **group_id)) string = "Музыка сообщества: {}".format(group_id["name"]) albums = self._get_albums(-group_id["id"]) tracks = self._get_tracks(-group_id["id"]) else: albums = self._get_albums(owner_id["id"]) tracks = self._get_tracks(owner_id["id"]) for album in albums: try: album["tracks"] = self.vk_audio.get( owner_id=album["owner_id"], album_id=album["id"], access_hash=album["access_hash"], ) except: album["tracks"] = self._get_tracks(owner_id["id"], album["id"]) return tracks, string, albums def _get_tracks(self, owner_id, album_id=None, access_hash=None): try: tracks = self.vk_audio.get(owner_id, album_id, access_hash) except: values = {"owner_id": owner_id} if album_id: values.update({"album_id": album_id}) res = self.session.method( method="audio.get", values=values, ) count = res["count"] offset = 0 tracks = [] while count != 0: audios = ",".join( ["{owner_id}_{id}".format(**i) for i in res["items"]]) tracks.extend( self.session.method(method="audio.getById", values={"audios": audios})) offset += 200 if count >= 200 else count % 200 count -= 200 if count >= 200 else count % 200 values.update({"offset": offset}) res = self.session.method( method="audio.get", values=values, ) return tracks def _get_albums(self, owner_id): try: albums = self.vk_audio.get_albums(owner_id["id"]) except: res = self.session.method( method="audio.getPlaylists", values={"owner_id": owner_id}, ) count = res["count"] offset = 0 albums = [] while count != 0: albums.extend(res["items"]) offset += 10 if count >= 10 else count % 10 count -= 10 if count >= 10 else count % 10 res = self.session.method( method="audio.getPlaylists", values={ "owner_id": owner_id, "offset": offset }, ) return albums def auth_handler(self): """ При двухфакторной аутентификации вызывается эта функция. :return: key, remember_device """ self.str_signal.emit("Введите код авторизации:") while not self.window.key: pass return self.window.key, self.save_password def captcha_handler(self, captcha): url = captcha.get_url() file = TemporaryFile() res = self.session.http.get(url, stream=True) res.raw.decode_content = True shutil.copyfileobj(res.raw, file) file.seek(0) image = QImage() image.loadFromData(file.read()) self.image_signal.emit(image) while not self.window.key: pass return captcha.try_again(self.window.key) def run(self): try: if not self.authorized: self._user_auth() result = self._get_audio() self.signal.emit(result) except exceptions.BadPassword: self.signal.emit("Неверный логин или пароль.") except exceptions.LoginRequired: self.signal.emit("Требуется логин.") except exceptions.PasswordRequired: self.signal.emit("Требуется пароль.") except (IndexError, AttributeError): self.signal.emit( "Невозможно получить список аудиозаписей. Проверьте, открыты ли они у пользователя." ) except exceptions.ApiError as e: if "113" in str(e): self.signal.emit( "Неверная ссылка на профиль пользователя (неверный ID пользователя)." ) elif "100" in str(e): self.signal.emit( "Неверная ссылка на профиль пользователя (сообщества).") else: self.signal.emit(str(e)) except Exception as e: self.signal.emit(str(type(e)) + str(e)) @staticmethod def get_user_id(link): result = findall(r"(https?://m?\.?vk\.com/)?(.*)$", link)[0][1] return result if result else None @staticmethod def get_group_and_post_id(link): result = findall(r"wall(.*?)_(.*?)$", link) return result[0] if result else None @staticmethod def get_album_id(link): link = link.replace("%2F", "/") result = findall(r"playlist/(.*)_(.*)_(.*)\?", link) if not result: result = findall(r"playlist/(.*)_(.*)_(.*)", link) if not result: result = findall(r"audio_playlist(.*)_(.*)&access_hash=(.*)", link) if not result: result = findall(r"audio_playlist(.*)_(.*)/(.*)", link) if not result: result = findall(r"audio_playlist(.*)_(.*)", link) return result[0] if result else None
class VkDownloader(): def __init__(self): self._session: vk_api.VkApi = None self._audio: VkAudio = None self._cache: list = [] self._albums: list = [] def login(self, username, password, exception_handler=None, bad_password_handler=None, success_handler=None, captcha_handler=None): self._session = vk_api.VkApi(username, password, config=MemoryConfig, captcha_handler=captcha_handler) try: self._session.auth() except vk_api.BadPassword: if bad_password_handler: bad_password_handler() return False except Exception: if exception_handler: exception_handler() return False self._audio = VkAudio(self._session) return True def get_tracks(self): if self._audio and not self._cache: self._cache = self._audio.get() return self._cache def get_albums(self): if self._audio and not self._albums: self._albums = self._audio.get_albums() return self._albums def get_album(self, album_number): return self._audio.get(album_id=self._albums[album_number]['id']) def refresh(self): self._cache = self._audio.get() self._albums = self._audio.get_albums() def download(self, songs, folder, condition="True", overrite=False, start_func=None, end_func=None, while_func=None): class DownloadThread(threading.Thread): def run(self): if not os.path.exists(folder): while_func(("Creating folder...", )) os.makedirs(folder) try: eval(condition) except Exception: if (while_func): while_func(("Invalid condition!", )) if (end_func): end_func() return while_func(("Starting download...", )) for i in songs: name = "%s - %s" % (i['artist'], i['title']) if (eval(condition) or (condition == None)): song = requests.get(i['url'], stream=True) size = int(song.headers['Content-Length']) i = 0 if os.path.exists(folder + name + ".mp3") and not overrite: while_func(("Downloading %s..." % name, "File already downloaded.")) continue file = open(folder + name + ".mp3", "wb") for chunk in song.iter_content(chunk_size=1024): if chunk: i += 1 file.write(chunk) if while_func: while_func(("Downloading %s..." % name, "Downloaded %d" % (i * 1024 * 100 // size) + r'%')) file.close() if end_func: end_func() DownloadThread().start() if start_func: start_func()