Merge branch 'dev'
This commit is contained in:
commit
6dbd3f4d6e
|
@ -128,4 +128,8 @@ dmypy.json
|
|||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
.vscode/
|
||||
<<<<<<< HEAD
|
||||
.vscode/
|
||||
=======
|
||||
.vscode/
|
||||
>>>>>>> dev
|
||||
|
|
19
README.md
19
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播放**
|
||||
|
@ -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家庭助理">
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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()
|
|
@ -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": [
|
||||
|
|
|
@ -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={}):
|
||||
|
|
|
@ -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": "登录失败"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue