Merge branch 'dev'

This commit is contained in:
shaonianzhentan 2023-11-02 14:06:30 +08:00
commit 6dbd3f4d6e
9 changed files with 87 additions and 93 deletions

6
.gitignore vendored
View File

@ -128,4 +128,8 @@ dmypy.json
# Pyre type checker
.pyre/
.vscode/
<<<<<<< HEAD
.vscode/
=======
.vscode/
>>>>>>> dev

View File

@ -26,6 +26,8 @@ https://github.com/Binaryify/NeteaseCloudMusicApi
不想动手不想操心也可以付费使用由我部署维护的接口服务每年30
**注意:关联媒体播放器调整为在集成选项中选择**
## 使用 - [插件图片预览](https://github.com/shaonianzhentan/image/blob/main/ha_cloud_music/README.md)
> **指定ID播放**
@ -47,21 +49,10 @@ https://github.com/Binaryify/NeteaseCloudMusicApi
> **登录后播放**
- [x] 每日推荐 `cloudmusic://163/my/daily`
- [x] 我喜欢的音乐 `cloudmusic://163/my/ilike`
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 +65,4 @@ media_player.yun_yin_le:
<img src="https://ha.jiluxinqing.com/img/alipay.png" align="left" height="160" width="160" alt="支付宝" title="支付宝"> | <img src="https://ha.jiluxinqing.com/img/wechat.png" align="left" height="160" width="160" alt="微信支付" title="微信">
#### 关注我的微信订阅号了解更多HomeAssistant相关知识
<img src="https://ha.jiluxinqing.com/img/wechat-channel.png" height="160" alt="HomeAssistant家庭助理" title="HomeAssistant家庭助理">
<img src="https://ha.jiluxinqing.com/img/wechat-channel.png" height="160" alt="HomeAssistant家庭助理" title="HomeAssistant家庭助理">

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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
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()

View File

@ -1,7 +1,7 @@
{
"domain": "ha_cloud_music",
"name": "\u4E91\u97F3\u4E50",
"version": "2023.8.13",
"version": "2023.11.1",
"config_flow": true,
"documentation": "https://github.com/shaonianzhentan/ha_cloud_music",
"requirements": [

View File

@ -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={}):

View File

@ -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": "登录失败"
}
}
}