From 68842ed304875c3f82ccd4f8a32c6e609a4fa15c Mon Sep 17 00:00:00 2001 From: shaonianzhentan Date: Mon, 23 Oct 2023 14:21:56 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=B8=8D=E5=85=BC=E5=AE=B9=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 ++---- .../ha_cloud_music/config_flow.py | 38 +++++++----- custom_components/ha_cloud_music/http_api.py | 9 ++- .../ha_cloud_music/manifest.json | 2 +- .../ha_cloud_music/media_player.py | 36 ++--------- .../ha_cloud_music/translations/en.json | 60 +++++++++---------- 6 files changed, 71 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index cf8bb9f..e30862f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ https://github.com/Binaryify/NeteaseCloudMusicApi 不想动手不想操心,也可以付费使用由我部署维护的接口服务(每年30) +**注意:关联媒体播放器调整为在集成选项中选择** + ## 使用 - [插件图片预览](https://github.com/shaonianzhentan/image/blob/main/ha_cloud_music/README.md) > **指定ID播放** @@ -48,20 +50,8 @@ https://github.com/Binaryify/NeteaseCloudMusicApi > **登录后播放** - [x] 每日推荐 `cloudmusic://163/my/daily` -configuration.yaml -```yaml -homeassistant: - customize: !include customize.yaml -``` -customize.yaml -```yaml -media_player.yun_yin_le: - media_player: - - media_player.源实体1 - - media_player.源实体2 - - media_player.源实体3 -``` + ## 关联项目 @@ -74,4 +64,4 @@ media_player.yun_yin_le: 支付宝 | 微信支付 #### 关注我的微信订阅号,了解更多HomeAssistant相关知识 -HomeAssistant家庭助理 \ No newline at end of file +HomeAssistant家庭助理 \ No newline at end of file diff --git a/custom_components/ha_cloud_music/config_flow.py b/custom_components/ha_cloud_music/config_flow.py index ed3a9c1..c45bac9 100644 --- a/custom_components/ha_cloud_music/config_flow.py +++ b/custom_components/ha_cloud_music/config_flow.py @@ -12,14 +12,14 @@ from homeassistant.core import callback import os from .manifest import manifest -from .http_api import http_cookie +from .http_api import fetch_data from homeassistant.util.json import save_json DOMAIN = manifest.domain class SimpleConfigFlow(ConfigFlow, domain=DOMAIN): - VERSION = 1 + VERSION = 2 async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -30,8 +30,15 @@ class SimpleConfigFlow(ConfigFlow, domain=DOMAIN): errors = {} if user_input is not None: url = user_input.get(CONF_URL).strip('/') - user_input[CONF_URL] = url - return self.async_create_entry(title=DOMAIN, data=user_input) + # 检查接口是否可用 + try: + res = await fetch_data(f'{url}/login/status') + if res['data']['code'] == 200: + user_input[CONF_URL] = url + return self.async_create_entry(title=DOMAIN, data=user_input) + except Exception as ex: + print(ex) + errors = { 'base': 'api_failed' } else: user_input = {} @@ -45,7 +52,6 @@ class SimpleConfigFlow(ConfigFlow, domain=DOMAIN): def async_get_options_flow(entry: ConfigEntry): return OptionsFlowHandler(entry) - class OptionsFlowHandler(OptionsFlow): def __init__(self, config_entry: ConfigEntry): self.config_entry = config_entry @@ -57,20 +63,22 @@ class OptionsFlowHandler(OptionsFlow): options = self.config_entry.options errors = {} if user_input is not None: - username = user_input.get(CONF_USERNAME) - password = user_input.get(CONF_PASSWORD) + return self.async_create_entry(title='', data=user_input) + + media_states = self.hass.states.async_all('media_player') + media_entities = {} - cloud_music = self.hass.data['cloud_music'] + for state in media_states: + friendly_name = state.attributes.get('friendly_name') + platform = state.attributes.get('platform') + entity_id = state.entity_id + value = f'{friendly_name}({entity_id})' - result = await cloud_music.login(username, password) - if result is not None: - return self.async_create_entry(title='', data=user_input) - else: - errors['base'] = 'login_failed' + if platform != 'cloud_music': + media_entities[entity_id] = value DATA_SCHEMA = vol.Schema({ - vol.Required(CONF_USERNAME, default=options.get(CONF_USERNAME)): str, - vol.Required(CONF_PASSWORD, default=options.get(CONF_PASSWORD)): str + vol.Required('media_player', default=options.get('media_player')): vol.In(media_entities) }) return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA, errors=errors) \ No newline at end of file diff --git a/custom_components/ha_cloud_music/http_api.py b/custom_components/ha_cloud_music/http_api.py index 4b9dfb3..f8adad9 100644 --- a/custom_components/ha_cloud_music/http_api.py +++ b/custom_components/ha_cloud_music/http_api.py @@ -24,7 +24,6 @@ async def http_cookie(url): } async def http_get(url, COOKIES={}): - print(url) headers = {'Referer': url, **HEADERS} jar = aiohttp.CookieJar(unsafe=True) async with aiohttp.ClientSession(headers=headers, cookies=COOKIES, cookie_jar=jar) as session: @@ -39,4 +38,10 @@ async def http_get(url, COOKIES={}): async def http_code(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: - return response.status \ No newline at end of file + return response.status + +async def fetch_data(url): + timeout = aiohttp.ClientTimeout(total=5) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(url) as response: + return await response.json() \ No newline at end of file diff --git a/custom_components/ha_cloud_music/manifest.json b/custom_components/ha_cloud_music/manifest.json index 2394e10..6fe2af0 100644 --- a/custom_components/ha_cloud_music/manifest.json +++ b/custom_components/ha_cloud_music/manifest.json @@ -1,7 +1,7 @@ { "domain": "ha_cloud_music", "name": "\u4E91\u97F3\u4E50", - "version": "2023.8.13", + "version": "2023.10.23", "config_flow": true, "documentation": "https://github.com/shaonianzhentan/ha_cloud_music", "requirements": [ diff --git a/custom_components/ha_cloud_music/media_player.py b/custom_components/ha_cloud_music/media_player.py index 890771e..51d91a3 100644 --- a/custom_components/ha_cloud_music/media_player.py +++ b/custom_components/ha_cloud_music/media_player.py @@ -13,7 +13,6 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_SELECT_SOURCE, - SUPPORT_SELECT_SOUND_MODE, SUPPORT_PLAY_MEDIA, SUPPORT_PLAY, SUPPORT_PAUSE, @@ -51,7 +50,6 @@ DOMAIN = manifest.domain _LOGGER = logging.getLogger(__name__) SUPPORT_FEATURES = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ - SUPPORT_SELECT_SOURCE | SUPPORT_SELECT_SOUND_MODE | \ SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ SUPPORT_BROWSE_MEDIA | SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST | SUPPORT_SHUFFLE_SET | SUPPORT_REPEAT_SET @@ -63,14 +61,14 @@ async def async_setup_entry( entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - media_player = CloudMusicMediaPlayer(hass) + media_player = CloudMusicMediaPlayer(hass, entry) await hass.async_add_executor_job(track_time_interval, hass, media_player.interval, TIME_BETWEEN_UPDATES) async_add_entities([ media_player ], True) class CloudMusicMediaPlayer(MediaPlayerEntity): - def __init__(self, hass): + def __init__(self, hass, entry): self.hass = hass self._attributes = { 'platform': 'cloud_music' @@ -81,9 +79,7 @@ class CloudMusicMediaPlayer(MediaPlayerEntity): self._attr_supported_features = SUPPORT_FEATURES # default attribute - self._attr_source_list = [] - self._attr_sound_mode = None - self._attr_sound_mode_list = [] + self.source_media_player = entry.options.get('media_player') self._attr_name = manifest.name self._attr_unique_id = manifest.documentation self._attr_state = STATE_ON @@ -140,8 +136,8 @@ class CloudMusicMediaPlayer(MediaPlayerEntity): @property def media_player(self): - if self.entity_id is not None and self._attr_sound_mode is not None: - return self.hass.states.get(self._attr_sound_mode) + if self.entity_id is not None and self.source_media_player is not None: + return self.hass.states.get(self.source_media_player) @property def device_info(self): @@ -162,15 +158,6 @@ class CloudMusicMediaPlayer(MediaPlayerEntity): async def async_browse_media(self, media_content_type=None, media_content_id=None): return await self.cloud_music.async_browse_media(self, media_content_type, media_content_id) - async def async_select_source(self, source): - if self._attr_source_list.count(source) > 0: - self._attr_source = source - - async def async_select_sound_mode(self, sound_mode): - if self._attr_sound_mode_list.count(sound_mode) > 0: - await self.async_media_pause() - self._attr_sound_mode = sound_mode - async def async_volume_up(self): await self.async_call('volume_up') @@ -240,18 +227,7 @@ class CloudMusicMediaPlayer(MediaPlayerEntity): # 更新属性 async def async_update(self): - if self.entity_id is not None: - state = self.hass.states.get(self.entity_id) - entities = state.attributes.get('media_player') - if entities is not None: - # 兼容初版 - if isinstance(entities, str): - entities = [ entities ] - - if len(entities) > 0: - self._attr_sound_mode_list = entities - if self._attr_sound_mode is None: - self._attr_sound_mode = entities[0] + pass # 调用服务 async def async_call(self, service, service_data={}): diff --git a/custom_components/ha_cloud_music/translations/en.json b/custom_components/ha_cloud_music/translations/en.json index b96664d..05568d1 100644 --- a/custom_components/ha_cloud_music/translations/en.json +++ b/custom_components/ha_cloud_music/translations/en.json @@ -1,35 +1,35 @@ { - "title": "云音乐", - "config": { - "abort": { - "single_instance_allowed": "仅允许单个配置" - }, - "step": { - "user": { - "title": "接口配置", - "description": "为防止你的账号密码泄露,建议自行部署API接口服务 \n免费部署文档:https://neteasecloudmusicapi.vercel.app \n实在是搞不来,也可以付费使用由我部署维护持续更新的接口服务😊", - "data": { - "url": "网易云音乐API" - } - } - }, - "error": { - "login_failed": "登录失败" - } + "title": "云音乐", + "config": { + "abort": { + "single_instance_allowed": "仅允许单个配置" }, - "options": { - "step": { - "user": { - "title": "网易云音乐", - "description": "登录后会将cookie保存在HomeAssistant存储目录之中", - "data": { - "username": "邮箱/手机号", - "password": "网易云音乐密码" - } - } - }, - "error": { - "login_failed": "登录失败" + "step": { + "user": { + "title": "接口配置", + "description": "为防止你的账号密码泄露,建议自行部署API接口服务 \n免费部署文档:https://neteasecloudmusicapi.vercel.app \n实在是搞不来,也可以付费使用由我部署维护持续更新的接口服务😊", + "data": { + "url": "网易云音乐API" } + } + }, + "error": { + "login_failed": "登录失败", + "api_failed": "接口地址不正确" } + }, + "options": { + "step": { + "user": { + "title": "配置", + "description": "关联的媒体播放器必须支持自定义音乐资源,可通过TTS插件自行测试是否可用", + "data": { + "media_player": "关联媒体播放器" + } + } + }, + "error": { + "login_failed": "登录失败" + } + } } \ No newline at end of file From a0f8532612c3d9c394cf41efaa466cbdb627aee6 Mon Sep 17 00:00:00 2001 From: shaonianzhentan Date: Wed, 1 Nov 2023 18:45:39 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=88=91=E5=96=9C?= =?UTF-8?q?=E6=AC=A2=E7=9A=84=E9=9F=B3=E4=B9=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ README.md | 1 + custom_components/ha_cloud_music/browse_media.py | 3 +++ custom_components/ha_cloud_music/cloud_music.py | 7 +++++++ custom_components/ha_cloud_music/manifest.json | 2 +- 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b6e4761..e72a38f 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.vscode/ diff --git a/README.md b/README.md index e30862f..b0aa053 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ https://github.com/Binaryify/NeteaseCloudMusicApi > **登录后播放** - [x] 每日推荐 `cloudmusic://163/my/daily` +- [x] 我喜欢的音乐 `cloudmusic://163/my/ilike` diff --git a/custom_components/ha_cloud_music/browse_media.py b/custom_components/ha_cloud_music/browse_media.py index 77fb8c6..1eadcab 100644 --- a/custom_components/ha_cloud_music/browse_media.py +++ b/custom_components/ha_cloud_music/browse_media.py @@ -86,6 +86,7 @@ class CloudMusicRouter(): my_login = f'{cloudmusic_protocol}my/login' my_daily = f'{cloudmusic_protocol}my/daily' + my_ilike = f'{cloudmusic_protocol}my/ilike' my_recommend_resource = f'{cloudmusic_protocol}my/recommend_resource' my_cloud = f'{cloudmusic_protocol}my/cloud' my_created = f'{cloudmusic_protocol}my/created' @@ -762,6 +763,8 @@ async def async_play_media(media_player, cloud_music, media_content_id): playlist = await cloud_music.async_get_playlist(id) elif media_content_id.startswith(CloudMusicRouter.my_daily): playlist = await cloud_music.async_get_dailySongs() + elif media_content_id.startswith(CloudMusicRouter.my_ilike): + playlist = await cloud_music.async_get_ilinkSongs() elif media_content_id.startswith(CloudMusicRouter.my_cloud): playlist = await cloud_music.async_get_cloud() elif media_content_id.startswith(CloudMusicRouter.artist_playlist): diff --git a/custom_components/ha_cloud_music/cloud_music.py b/custom_components/ha_cloud_music/cloud_music.py index 9f88116..6b08aa1 100644 --- a/custom_components/ha_cloud_music/cloud_music.py +++ b/custom_components/ha_cloud_music/cloud_music.py @@ -258,6 +258,13 @@ class CloudMusic(): return list(map(format_playlist, res['data']['dailySongs'])) + # 获取我喜欢的音乐 + async def async_get_ilinkSongs(self): + uid = self.userinfo.get('uid') + if uid is not None: + res = await self.netease_cloud_music(f'/user/playlist?uid={uid}') + return await self.async_get_playlist(res['playlist'][0]['id']) + # 乐听头条 async def async_ting_playlist(self, catalog_id): diff --git a/custom_components/ha_cloud_music/manifest.json b/custom_components/ha_cloud_music/manifest.json index 6fe2af0..4ff6afa 100644 --- a/custom_components/ha_cloud_music/manifest.json +++ b/custom_components/ha_cloud_music/manifest.json @@ -1,7 +1,7 @@ { "domain": "ha_cloud_music", "name": "\u4E91\u97F3\u4E50", - "version": "2023.10.23", + "version": "2023.11.1", "config_flow": true, "documentation": "https://github.com/shaonianzhentan/ha_cloud_music", "requirements": [