From 1cd21aae01828aa46462c73e219c42fde8beda36 Mon Sep 17 00:00:00 2001 From: Marshal Date: Mon, 3 Jun 2019 16:16:24 +0300 Subject: [PATCH] New supported objects: Station, StationResult, StationTrackResult, Value, Sequence, RotorSettings, Restrictions, Id, Enum, DiscreteScale, DashBoard, AdParams The following methods are wrapped: - /rotor/account/status - /rotor/stations/dashboard - /rotor/stations/list - /rotor/station/genre:{genre}/feedback - /rotor/station/genre:{genre}/info - /rotor/station/genre:{genre}/tracks The following fields are now optional: Account[region, passport_phones], Status[cache_limit, subeditor, subeditor_level, plus], Subscription[auto_renewable, can_start_trial, mcdonalds] Added new fields: Subscription.end, Status[skips_per_hour, station_exists, premium_region], Track..preview_duration_ms Fixed downloading the cover of the track --- yandex_music/__init__.py | 16 ++++- yandex_music/client.py | 70 ++++++++++++++++++++- yandex_music/{radio => rotor}/__init__.py | 0 yandex_music/rotor/ad_params.py | 35 +++++++++++ yandex_music/rotor/dashboard.py | 27 ++++++++ yandex_music/rotor/discrete_scale.py | 29 +++++++++ yandex_music/rotor/enum.py | 26 ++++++++ yandex_music/rotor/id.py | 22 +++++++ yandex_music/rotor/restrictions.py | 34 ++++++++++ yandex_music/rotor/rotor_settings.py | 29 +++++++++ yandex_music/rotor/sequence.py | 37 +++++++++++ yandex_music/rotor/station.py | 45 +++++++++++++ yandex_music/rotor/station_result.py | 46 ++++++++++++++ yandex_music/rotor/station_tracks_result.py | 29 +++++++++ yandex_music/rotor/value.py | 34 ++++++++++ yandex_music/status/account.py | 4 +- yandex_music/status/status.py | 18 ++++-- yandex_music/status/subscription.py | 8 ++- yandex_music/track/track.py | 8 ++- 19 files changed, 501 insertions(+), 16 deletions(-) rename yandex_music/{radio => rotor}/__init__.py (100%) create mode 100644 yandex_music/rotor/ad_params.py create mode 100644 yandex_music/rotor/dashboard.py create mode 100644 yandex_music/rotor/discrete_scale.py create mode 100644 yandex_music/rotor/enum.py create mode 100644 yandex_music/rotor/id.py create mode 100644 yandex_music/rotor/restrictions.py create mode 100644 yandex_music/rotor/rotor_settings.py create mode 100644 yandex_music/rotor/sequence.py create mode 100644 yandex_music/rotor/station.py create mode 100644 yandex_music/rotor/station_result.py create mode 100644 yandex_music/rotor/station_tracks_result.py create mode 100644 yandex_music/rotor/value.py diff --git a/yandex_music/__init__.py b/yandex_music/__init__.py index 8115069..fbae51b 100644 --- a/yandex_music/__init__.py +++ b/yandex_music/__init__.py @@ -80,6 +80,19 @@ from genre.title import Title from genre.images import Images from genre.genre import Genre +from rotor.id import Id +from rotor.value import Value +from rotor.enum import Enum +from rotor.sequence import Sequence +from rotor.discrete_scale import DiscreteScale +from rotor.ad_params import AdParams +from rotor.restrictions import Restrictions +from rotor.rotor_settings import RotorSettings +from rotor.station import Station +from rotor.station_tracks_result import StationTracksResult +from rotor.station_result import StationResult +from rotor.dashboard import Dashboard + __all__ = ['YandexMusicObject', 'Account', 'PassportPhone', 'InvocationInfo', 'Permissions', 'Plus', 'Subscription', 'Status', 'Price', 'Product', 'AutoRenewable', 'Settings', 'PermissionAlerts', 'Experiments', 'Cover', 'Ratings', 'Counts', 'Link', 'Artist', 'User', 'CaseForms', 'MadeFor', 'Label', 'Album', 'PlayCounter', @@ -89,4 +102,5 @@ __all__ = ['YandexMusicObject', 'Account', 'PassportPhone', 'InvocationInfo', 'P 'ArtistSearchResult', 'PlaylistSearchResult', 'TrackSearchResult', 'VideoSearchResult', 'Search', 'Suggestions', 'MixLink', 'BlockEntity', 'Block', 'PlayContextsData', 'TrackId', 'TrackShortOld', 'PersonalPlaylistsData', 'Promotion', 'Landing', 'Chart', 'ChartItem', 'PlayContext', 'Title', 'Genre', - 'Icon', 'Images'] + 'Icon', 'Images', 'Id', 'Station', 'Dashboard', 'RotorSettings', 'AdParams', 'Restrictions', 'Value', 'Enum', + 'DiscreteScale', 'StationResult', 'Sequence', 'StationTracksResult'] diff --git a/yandex_music/client.py b/yandex_music/client.py index b09f6fb..1d36049 100644 --- a/yandex_music/client.py +++ b/yandex_music/client.py @@ -3,7 +3,7 @@ from datetime import datetime from yandex_music import YandexMusicObject, Status, Settings, PermissionAlerts, Experiments, Artist, Album, Playlist, \ TracksLikes, Track, AlbumsLikes, ArtistsLikes, PlaylistsLikes, Feed, PromoCodeStatus, DownloadInfo, Search, \ - Suggestions, Landing, Genre + Suggestions, Landing, Genre, Dashboard, StationResult, StationTracksResult from yandex_music.utils.request import Request from yandex_music.utils.difference import Difference from yandex_music.exceptions import InvalidToken @@ -289,6 +289,74 @@ class Client(YandexMusicObject): return self.users_playlists_change(kind, diff.to_json(), revision, user_id, timeout, *args, **kwargs) + def rotor_account_status(self, timeout=None, *args, **kwargs): + url = f'{self.base_url}/rotor/account/status' + + result = self._request.get(url, timeout=timeout, *args, **kwargs) + + return Status.de_json(result, self) + + def rotor_stations_dashboard(self, timeout=None, *args, **kwargs): + url = f'{self.base_url}/rotor/stations/dashboard' + + result = self._request.get(url, timeout=timeout, *args, **kwargs) + + return Dashboard.de_json(result, self) + + def rotor_stations_list(self, language: str = 'en', timeout=None, *args, **kwargs): + url = f'{self.base_url}/rotor/stations/list' + + result = self._request.get(url, {'language': language}, timeout=timeout, *args, **kwargs) + + return StationResult.de_list(result, self) + + def rotor_station_genre_feedback(self, genre: str, type_: str, timestamp=None, from_: str = None, + batch_id: str or int = None, track_id: str = None, timeout=None, *args, **kwargs): + if timestamp is None: + timestamp = datetime.now().timestamp() + + url = f'{self.base_url}/rotor/station/genre:{genre}/feedback' + + params = {} + data = { + 'type': type_, + 'timestamp': timestamp + } + + if batch_id and track_id: + data.update({'trackId': track_id}) + params = {'batch-id': batch_id} + + if from_: + data.update({'from': from_}) + + result = self._request.post(url, params=params, json=data, timeout=timeout, *args, **kwargs) + + return result == 'ok' + + def rotor_station_genre_feedback_radio_started(self, genre: str, from_: str, timestamp=None, + timeout=None, *args, **kwargs): + return self.rotor_station_genre_feedback(genre, 'radioStarted', timestamp, from_, timeout, *args, **kwargs) + + def rotor_station_genre_feedback_track_started(self, genre: str, track_id: str, batch_id: str or int, + timestamp=None, timeout=None, *args, **kwargs): + return self.rotor_station_genre_feedback(genre, 'trackStarted', timestamp, track_id=track_id, batch_id=batch_id, + timeout=timeout, *args, **kwargs) + + def rotor_station_genre_info(self, genre: str, timeout=None, *args, **kwargs): + url = f'{self.base_url}/rotor/station/genre:{genre}/info' + + result = self._request.get(url, timeout=timeout, *args, **kwargs) + + return StationResult.de_list(result, self) + + def rotor_station_genre_tracks(self, genre: str, timeout=None, *args, **kwargs): + url = f'{self.base_url}/rotor/station/genre:{genre}/tracks' + + result = self._request.get(url, timeout=timeout, *args, **kwargs) + + return StationTracksResult.de_json(result, self) + def _add_like(self, object_type: str, ids: str or int or list, remove: bool = False, user_id: str or int = None, timeout=None, *args, **kwargs): if user_id is None: diff --git a/yandex_music/radio/__init__.py b/yandex_music/rotor/__init__.py similarity index 100% rename from yandex_music/radio/__init__.py rename to yandex_music/rotor/__init__.py diff --git a/yandex_music/rotor/ad_params.py b/yandex_music/rotor/ad_params.py new file mode 100644 index 0000000..dcdb586 --- /dev/null +++ b/yandex_music/rotor/ad_params.py @@ -0,0 +1,35 @@ +from yandex_music import YandexMusicObject + + +class AdParams(YandexMusicObject): + def __init__(self, + partner_id, + category_id, + page_ref, + target_ref, + other_params, + ad_volume, + genre_id=None, + genre_name=None, + client=None, + **kwargs): + self.partner_id = partner_id + self.category_id = category_id + self.page_ref = page_ref + self.target_ref = target_ref + self.other_params = other_params + self.ad_volume = ad_volume + + self.genre_id = genre_id + self.genre_name = genre_name + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(AdParams, cls).de_json(data, client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/dashboard.py b/yandex_music/rotor/dashboard.py new file mode 100644 index 0000000..d0a4bd1 --- /dev/null +++ b/yandex_music/rotor/dashboard.py @@ -0,0 +1,27 @@ +from yandex_music import YandexMusicObject + + +class Dashboard(YandexMusicObject): + def __init__(self, + dashboard_id, + stations, + pumpkin, + client=None, + **kwargs): + self.dashboard_id = dashboard_id + self.stations = stations + self.pumpkin = pumpkin + + self.client = client + self._id_attrs = (self.dashboard_id, ) + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Dashboard, cls).de_json(data, client) + from yandex_music import StationResult + data['stations'] = StationResult.de_list(data.get('stations'), client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/discrete_scale.py b/yandex_music/rotor/discrete_scale.py new file mode 100644 index 0000000..86518fa --- /dev/null +++ b/yandex_music/rotor/discrete_scale.py @@ -0,0 +1,29 @@ +from yandex_music import YandexMusicObject + + +class DiscreteScale(YandexMusicObject): + def __init__(self, + type, + name, + min, + max, + client=None, + **kwargs): + self.type = type + self.name = name + self.min = min + self.max = max + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(DiscreteScale, cls).de_json(data, client) + from yandex_music import Value + data['min'] = Value.de_json(data.get('min'), client) + data['max'] = Value.de_json(data.get('max'), client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/enum.py b/yandex_music/rotor/enum.py new file mode 100644 index 0000000..a3914ec --- /dev/null +++ b/yandex_music/rotor/enum.py @@ -0,0 +1,26 @@ +from yandex_music import YandexMusicObject + + +class Enum(YandexMusicObject): + def __init__(self, + type, + name, + possible_values, + client=None, + **kwargs): + self.type = type + self.name = name + self.possible_values = possible_values + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Enum, cls).de_json(data, client) + from yandex_music import Value + data['possible_values'] = Value.de_list(data.get('possible_values'), client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/id.py b/yandex_music/rotor/id.py new file mode 100644 index 0000000..231f238 --- /dev/null +++ b/yandex_music/rotor/id.py @@ -0,0 +1,22 @@ +from yandex_music import YandexMusicObject + + +class Id(YandexMusicObject): + def __init__(self, + type, + tag, + client=None, + **kwargs): + self.type = type + self.tag = tag + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Id, cls).de_json(data, client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/restrictions.py b/yandex_music/rotor/restrictions.py new file mode 100644 index 0000000..76dbcc8 --- /dev/null +++ b/yandex_music/rotor/restrictions.py @@ -0,0 +1,34 @@ +from yandex_music import YandexMusicObject + + +class Restrictions(YandexMusicObject): + def __init__(self, + language, + diversity, + mood=None, + energy=None, + mood_energy=None, + client=None, + **kwargs): + self.language = language + self.diversity = diversity + self.mood = mood + self.energy = energy + self.mood_energy = mood_energy + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Restrictions, cls).de_json(data, client) + from yandex_music import Enum, DiscreteScale + for key, value in data.items(): + restriction_type = data[key].get('type') + + data[key] = Enum.de_json(data[key], client) if restriction_type == 'enum'\ + else DiscreteScale.de_json(data[key], client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/rotor_settings.py b/yandex_music/rotor/rotor_settings.py new file mode 100644 index 0000000..32fd7eb --- /dev/null +++ b/yandex_music/rotor/rotor_settings.py @@ -0,0 +1,29 @@ +from yandex_music import YandexMusicObject + + +class RotorSettings(YandexMusicObject): + def __init__(self, + language, + diversity, + mood=None, + energy=None, + mood_energy=None, + client=None, + **kwargs): + self.language = language + self.diversity = diversity + + self.mood = mood + self.energy = energy + self.mood_energy = mood_energy + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(RotorSettings, cls).de_json(data, client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/sequence.py b/yandex_music/rotor/sequence.py new file mode 100644 index 0000000..6bf089a --- /dev/null +++ b/yandex_music/rotor/sequence.py @@ -0,0 +1,37 @@ +from yandex_music import YandexMusicObject + + +class Sequence(YandexMusicObject): + def __init__(self, + type, + track, + liked, + client=None, + **kwargs): + self.type = type + self.track = track + self.liked = liked + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Sequence, cls).de_json(data, client) + from yandex_music import Track + data['track'] = Track.de_json(data.get('track'), client) + + return cls(client=client, **data) + + @classmethod + def de_list(cls, data, client): + if not data: + return [] + + sequences = list() + for sequence in data: + sequences.append(cls.de_json(sequence, client)) + + return sequences diff --git a/yandex_music/rotor/station.py b/yandex_music/rotor/station.py new file mode 100644 index 0000000..34bc05a --- /dev/null +++ b/yandex_music/rotor/station.py @@ -0,0 +1,45 @@ +from yandex_music import YandexMusicObject + + +class Station(YandexMusicObject): + def __init__(self, + id, + name, + icon, + mts_icon, + geocell_icon, + id_for_from, + restrictions, + restrictions2, + parent_id=None, + client=None, + **kwargs): + self.id = id + self.name = name + self.icon = icon + self.mts_icon = mts_icon + self.geocell_icon = geocell_icon + self.id_for_from = id_for_from + self.restrictions = restrictions + self.restrictions2 = restrictions2 + + self.parent_id = parent_id + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Station, cls).de_json(data, client) + from yandex_music import Id, Icon, Restrictions + data['id'] = Id.de_json(data.get('id'), client) + data['parent_id'] = Id.de_json(data.get('parent_id'), client) + data['icon'] = Icon.de_json(data.get('icon'), client) + data['mts_icon'] = Icon.de_json(data.get('mts_icon'), client) + data['geocell_icon'] = Icon.de_json(data.get('geocell_icon'), client) + data['restrictions'] = Restrictions.de_json(data.get('restrictions'), client) + data['restrictions2'] = Restrictions.de_json(data.get('restrictions2'), client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/station_result.py b/yandex_music/rotor/station_result.py new file mode 100644 index 0000000..a66d4a9 --- /dev/null +++ b/yandex_music/rotor/station_result.py @@ -0,0 +1,46 @@ +from yandex_music import YandexMusicObject + + +class StationResult(YandexMusicObject): + def __init__(self, + station, + settings, + settings2, + ad_params, + explanation=None, + prerolls=None, + client=None, + **kwargs): + self.station = station + self.settings = settings + self.settings2 = settings2 + self.ad_params = ad_params + self.explanation = explanation + self.prerolls = prerolls + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(StationResult, cls).de_json(data, client) + from yandex_music import Station, RotorSettings, AdParams + data['station'] = Station.de_json(data.get('station'), client) + data['settings'] = RotorSettings.de_json(data.get('settings'), client) + data['settings2'] = RotorSettings.de_json(data.get('settings2'), client) + data['ad_params'] = AdParams.de_json(data.get('ad_params'), client) + + return cls(client=client, **data) + + @classmethod + def de_list(cls, data, client): + if not data: + return [] + + station_results = list() + for station_result in data: + station_results.append(cls.de_json(station_result, client)) + + return station_results diff --git a/yandex_music/rotor/station_tracks_result.py b/yandex_music/rotor/station_tracks_result.py new file mode 100644 index 0000000..7549b64 --- /dev/null +++ b/yandex_music/rotor/station_tracks_result.py @@ -0,0 +1,29 @@ +from yandex_music import YandexMusicObject + + +class StationTracksResult(YandexMusicObject): + def __init__(self, + id, + sequence, + batch_id, + pumpkin, + client=None, + **kwargs): + self.id = id + self.sequence = sequence + self.batch_id = batch_id + self.pumpkin = pumpkin + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(StationTracksResult, cls).de_json(data, client) + from yandex_music import Id, Sequence + data['id'] = Id.de_json(data.get('id'), client) + data['sequence'] = Sequence.de_list(data.get('sequence'), client) + + return cls(client=client, **data) diff --git a/yandex_music/rotor/value.py b/yandex_music/rotor/value.py new file mode 100644 index 0000000..709959c --- /dev/null +++ b/yandex_music/rotor/value.py @@ -0,0 +1,34 @@ +from yandex_music import YandexMusicObject + + +class Value(YandexMusicObject): + def __init__(self, + value, + name, + client=None, + **kwargs): + self.value = value + self.name = name + + self.client = client + + @classmethod + def de_json(cls, data, client): + if not data: + return None + + data = super(Value, cls).de_json(data, client) + + return cls(client=client, **data) + + @classmethod + def de_list(cls, data, client): + if not data: + return [] + + values = list() + for value in data: + values.append(cls.de_json(value, client)) + + return values + diff --git a/yandex_music/status/account.py b/yandex_music/status/account.py index 2def8c7..b0cf344 100644 --- a/yandex_music/status/account.py +++ b/yandex_music/status/account.py @@ -8,7 +8,6 @@ class Account(YandexMusicObject): now, uid, login, - region, full_name, second_name, first_name, @@ -16,7 +15,8 @@ class Account(YandexMusicObject): birthday, service_available, hosted_user, - passport_phones, + region=None, + passport_phones=None, registered_at=None, has_info_for_app_metrica=False, client=None, diff --git a/yandex_music/status/status.py b/yandex_music/status/status.py index 4050a98..ad5c2ab 100644 --- a/yandex_music/status/status.py +++ b/yandex_music/status/status.py @@ -5,23 +5,29 @@ class Status(YandexMusicObject): def __init__(self, account, permissions, - cache_limit, subscription, - subeditor, - subeditor_level, - plus, + cache_limit=None, + subeditor=None, + subeditor_level=None, + plus=None, default_email=None, + skips_per_hour=None, + station_exists=None, + premium_region=None, client=None, **kwargs): self.account = account self.permissions = permissions - self.cache_limit = cache_limit self.subscription = subscription + + self.cache_limit = cache_limit self.subeditor = subeditor self.subeditor_level = subeditor_level self.plus = plus - self.default_email = default_email + self.skips_per_hour = skips_per_hour + self.station_exists = station_exists + self.premium_region = premium_region self.client = client self._id_attrs = (self.account,) diff --git a/yandex_music/status/subscription.py b/yandex_music/status/subscription.py index 486c4a7..106a204 100644 --- a/yandex_music/status/subscription.py +++ b/yandex_music/status/subscription.py @@ -3,14 +3,16 @@ from yandex_music import YandexMusicObject class Subscription(YandexMusicObject): def __init__(self, - auto_renewable, - can_start_trial, - mcdonalds, + auto_renewable=None, + can_start_trial=None, + mcdonalds=None, + end=None, client=None, **kwargs): self.auto_renewable = auto_renewable self.can_start_trial = can_start_trial self.mcdonalds = mcdonalds + self.end = end self.client = client diff --git a/yandex_music/track/track.py b/yandex_music/track/track.py index 27e1137..3266af9 100644 --- a/yandex_music/track/track.py +++ b/yandex_music/track/track.py @@ -24,6 +24,7 @@ class Track(YandexMusicObject): available_as_rbt=None, content_warning=None, explicit=None, + preview_duration_ms=None, client=None, **kwargs): self.id = id @@ -37,7 +38,7 @@ class Track(YandexMusicObject): self.real_id = real_id self.og_image = og_image self.type = type - self.cover_uri = cover_uri + self.cover_uri = 'https://' + cover_uri self.major = major self.duration_ms = duration_ms self.storage_dir = storage_dir @@ -48,6 +49,7 @@ class Track(YandexMusicObject): self.available_as_rbt = available_as_rbt self.content_warning = content_warning self.explicit = explicit + self.preview_duration_ms = preview_duration_ms self.download_info = None @@ -59,8 +61,8 @@ class Track(YandexMusicObject): return self.download_info - def download_cover(self, filename): - self.client._request.download(self.cover_uri, filename) + def download_cover(self, filename, size='200x200'): + self.client._request.download(self.cover_uri.replace('%%', size), filename) def download(self, filename, codec='mp3', bitrate_in_kbps=192): if self.download_info is None: