1
0
mirror of https://github.com/hanxi/xiaomusic.git synced 2025-12-06 14:52:50 +08:00

Compare commits

...

52 Commits

Author SHA1 Message Date
涵曦
790a4cd808 bump: version 0.3.53 → 0.3.54 2024-12-04 01:41:29 +08:00
涵曦
70d029f4d1 build: update static version 2024-12-04 01:41:28 +08:00
52fisher
0472ad3188 fix: 安卓低版本webview对audio的src为空值的报错 (#289) 2024-12-04 01:30:07 +08:00
涵曦
733c44d12f feat: 新增最近新增歌单 close #273 2024-12-03 21:44:48 +08:00
涵曦
8c92afd09b fix: 修复M01语音播放问题,X08C X08E X8F 型号默认采用型号兼容模式 see #30 2024-12-03 21:25:51 +08:00
涵曦
742929b9d5 Update README.md 2024-12-03 21:18:26 +08:00
涵曦
4766e08ff4 Update README.md 2024-12-03 21:17:11 +08:00
涵曦
99c4296c7a bump: version 0.3.52 → 0.3.53 2024-12-03 12:15:09 +08:00
涵曦
0ae24f4810 build: update static version 2024-12-03 12:15:08 +08:00
涵曦
77cca0d18c fix: 解决播放接口修改后播放失败的问题 2024-12-03 12:14:57 +08:00
涵曦
ddc24595df bump: version 0.3.51 → 0.3.52 2024-12-03 12:02:39 +08:00
涵曦
ce1bfb164c build: update static version 2024-12-03 12:02:39 +08:00
涵曦
78dbba6c1f fix: 修复播放接口参数错误的问题 2024-12-03 12:02:14 +08:00
涵曦
7eb0ab1e46 bump: version 0.3.50 → 0.3.51 2024-12-03 09:06:51 +08:00
涵曦
0a2e3cc579 build: update static version 2024-12-03 09:06:50 +08:00
涵曦
75ec336285 fix: 修复空配置启动失败问题 close #284 2024-12-03 09:02:15 +08:00
涵曦
a19d53f000 bump: version 0.3.49 → 0.3.50 2024-12-03 07:11:40 +08:00
涵曦
cdb6190e7a build: update static version 2024-12-03 07:11:40 +08:00
涵曦
1aa9b58561 style: 更新 gitignore 2024-12-02 23:17:54 +08:00
涵曦
6a990f48d0 fix: 更新 yt-dlp ,解决 B 站下载问题 close #279 2024-12-02 23:17:12 +08:00
涵曦
82e92a0380 Update README.md 2024-12-02 21:43:56 +08:00
涵曦
ecce5c8848 feat: 修改日志文件的默认值 2024-12-02 21:38:31 +08:00
涵曦
87fb34e5c9 feat: 新增修改tag缓存信息的接口 close #266 2024-12-02 12:27:09 +08:00
涵曦
df3c4b7fa9 feat: 新增专用的播放歌曲和播放歌单接口,解决默认口令提示词被修改了导致后台失效的问题 2024-12-02 11:40:26 +08:00
涵曦
cb2af0ee9f feat: 统计设备型号 2024-12-02 11:08:19 +08:00
涵曦
8f9bba0ca3 refactor: 调整设置页面 2024-12-01 22:47:56 +08:00
涵曦
b86e8d3196 Update README.md 2024-12-01 22:41:47 +08:00
52fisher
65067346f3 feat: 页面与设置中的HOST不一致时弹窗提醒 (#281) 2024-12-01 22:23:50 +08:00
52fisher
865b412fb7 fix: 网页播放audio获取到错误url无法播放时提醒用户 (#280) 2024-12-01 12:33:05 +08:00
涵曦
441fffd59e Update README.md 2024-11-29 10:02:51 +08:00
52fisher
2ee7b956cf feat: 未发现小爱设备时给予提示 (#278)
fix: input标签自闭合
2024-11-29 07:23:57 +08:00
涵曦
126bfa43a2 feat: 优化设置页面提示 2024-11-28 23:29:06 +08:00
涵曦
3a4d702070 bump: version 0.3.48 → 0.3.49 2024-11-28 20:30:36 +08:00
涵曦
2c7a09b9d4 build: update static version 2024-11-28 20:30:36 +08:00
涵曦
74837baaef feat: 临时文件目录支持配置 #99 2024-11-28 18:55:18 +08:00
涵曦
def63b2407 feat: 新增单曲播放和顺序播放功能 close #277 2024-11-28 18:07:40 +08:00
Formatter [BOT]
93d2047c7a Auto-format code 🧹🌟🤖 2024-11-27 10:31:40 +00:00
dludream
9a1b9d1949 fix: 修复中文数字转换函数对'十、十一'等数字的处理 (#275) 2024-11-27 18:31:12 +08:00
涵曦
88fa4318dc Update setting.html 2024-11-27 14:29:59 +08:00
涵曦
0bc4bc8b13 feat: 设置播放类型支持配置语音提示词,定时任务支持设置播放类型 2024-11-26 23:18:25 +08:00
涵曦
ca2ddcd89c bump: version 0.3.47 → 0.3.48 2024-11-20 00:31:57 +08:00
涵曦
78acc35a62 build: update static version 2024-11-20 00:31:56 +08:00
涵曦
323a832a9e feat: 支持替换默认口令,而不是追加 close #259 2024-11-20 00:24:11 +08:00
涵曦
0c99f4d537 feat: 新增自定义个歌单接口 #242 2024-11-20 00:09:54 +08:00
涵曦
b2737f745d fix: 锁定 PWA 应用旋转方向 2024-11-14 22:55:41 +08:00
涵曦
464a1e1bd1 bump: version 0.3.46 → 0.3.47 2024-11-14 08:15:40 +08:00
涵曦
7bd613f74c build: update static version 2024-11-14 08:15:39 +08:00
涵曦
3fb0a3bae9 fix: 修复 PWA 应用有密码时报错的问题 2024-11-14 00:51:53 +08:00
涵曦
b4f0e7b349 feat: 支持 PWA 应用安装 2024-11-14 00:26:19 +08:00
Formatter [BOT]
7c1ce81b5e Auto-format code 🧹🌟🤖 2024-11-13 15:28:49 +00:00
涵曦
052027fb50 feat: 新增模糊匹配测试用例 2024-11-13 23:28:10 +08:00
涵曦
b4d8434507 fix: 修复播放顺序没有按数字排序的问题 close #249 2024-11-12 09:14:20 +08:00
137 changed files with 1391 additions and 221 deletions

1
.gitignore vendored
View File

@@ -169,3 +169,4 @@ setting.json
.DS_Store
cache
tmp/
xiaomusic.log.txt

View File

@@ -1,3 +1,89 @@
## v0.3.54 (2024-12-04)
### Feat
- 新增最近新增歌单 close #273
### Fix
- 安卓低版本webview对audio的src为空值的报错 (#289)
- 修复M01语音播放问题X08C X08E X8F 型号默认采用型号兼容模式 see #30
## v0.3.53 (2024-12-03)
### Fix
- 解决播放接口修改后播放失败的问题
## v0.3.52 (2024-12-03)
### Fix
- 修复播放接口参数错误的问题
## v0.3.51 (2024-12-03)
### Fix
- 修复空配置启动失败问题 close #284
## v0.3.50 (2024-12-03)
### Feat
- 修改日志文件的默认值
- 新增修改tag缓存信息的接口 close #266
- 新增专用的播放歌曲和播放歌单接口,解决默认口令提示词被修改了导致后台失效的问题
- 统计设备型号
- 页面与设置中的HOST不一致时弹窗提醒 (#281)
- 未发现小爱设备时给予提示 (#278)
- 优化设置页面提示
### Fix
- 更新 yt-dlp ,解决 B 站下载问题 close #279
- 网页播放audio获取到错误url无法播放时提醒用户 (#280)
- input标签自闭合
### Refactor
- 调整设置页面
## v0.3.49 (2024-11-28)
### Feat
- 临时文件目录支持配置 #99
- 新增单曲播放和顺序播放功能 close #277
- 设置播放类型支持配置语音提示词,定时任务支持设置播放类型
### Fix
- 修复中文数字转换函数对'十、十一'等数字的处理 (#275)
## v0.3.48 (2024-11-20)
### Feat
- 支持替换默认口令,而不是追加 close #259
- 新增自定义个歌单接口 #242
### Fix
- 锁定 PWA 应用旋转方向
## v0.3.47 (2024-11-14)
### Feat
- 支持 PWA 应用安装
- 新增模糊匹配测试用例
### Fix
- 修复 PWA 应用有密码时报错的问题
- 修复播放顺序没有按数字排序的问题 close #249
## v0.3.46 (2024-11-08)
### Feat

View File

@@ -6,7 +6,8 @@
[![PyPI - Downloads](https://img.shields.io/pypi/dm/xiaomusic)](https://pypi.org/project/xiaomusic/)
[![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fhanxi%2Fxiaomusic%2Fmain%2Fpyproject.toml)](https://pypi.org/project/xiaomusic/)
[![GitHub Release](https://img.shields.io/github/v/release/hanxi/xiaomusic)](https://github.com/hanxi/xiaomusic/releases)
[![Visitors](https://api.visitorbadge.io/api/daily?path=hanxi%2Fxiaomusic&label=daily%20visitor&countColor=%232ccce4&style=flat)](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
[![Visitors](https://api.visitorbadge.io/api/visitors?path=hanxi%2Fxiaomusic&label=total%20visitor&countColor=%232ccce4&style=flat)](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
使用小爱音箱播放音乐,音乐使用 yt-dlp 下载。
@@ -72,6 +73,29 @@ docker 和 docker compose 二选一即可,启动成功后,在 web 页面可
### 🔥 修改默认8090端口映射
#### 方法1 不修改监听端口 8090
【监听端口】保持为默认的 8090 不变,把【外网访问端口】改为 5678 。
```yaml
services:
xiaomusic:
image: hanxi/xiaomusic
container_name: xiaomusic
restart: unless-stopped
ports:
- 5678:8090
volumes:
- /xiaomusic/music:/app/music
- /xiaomusic/conf:/app/conf
environment:
XIAOMUSIC_PUBLIC_PORT: 5678
```
XIAOMUSIC_PUBLIC_PORT 对应后台设置里的【外网访问端口】,修改后可以不用重启。
#### 方法2 修改监听端口 8090 为 5678
如果需要修改 8090 端口为其他端口,比如 5678需要这样配3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
```yaml
@@ -94,7 +118,7 @@ services:
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
> [!IMPORTANT]
> XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口。
> XIAOMUSIC_PORT 也可以在后台配置,对应的是监听端口,修改后记得重启
### 🤐 支持语音口令
@@ -201,7 +225,8 @@ docker build -t xiaomusic .
| L05C | [小米小爱音箱Play 增强版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05c) |
| L09A | [小米音箱Art](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l09a) |
| LX04 X10A X08A | 已经支持的触屏版 |
| M01/XMYX01JY | 小米小爱音箱HD (获取对话记录的接口比较特殊) |
| X08C X08E X8F | 需要设置【型号兼容模式】选项为 true |
| M01/XMYX01JY | 小米小爱音箱HD 需要设置【特殊型号获取对话记录】选项为 true 才能语音播放|
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>

160
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default", "dev", "lint"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:10d18ad4e1cc30b6683ac4c5fcdf73ab7aa3e6a5910b94e5ded3183e108bbbe1"
content_hash = "sha256:d22d4b03450cd369683d3ddf827359cd96541ee09e29bfa1fa666d72aa7d3ab0"
[[metadata.targets]]
requires_python = "==3.10.12"
@@ -39,23 +39,24 @@ files = [
[[package]]
name = "aiohttp"
version = "3.10.10"
requires_python = ">=3.8"
version = "3.11.9"
requires_python = ">=3.9"
summary = "Async http client/server framework (asyncio)"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
dependencies = [
"aiohappyeyeballs>=2.3.0",
"aiosignal>=1.1.2",
"async-timeout<5.0,>=4.0; python_version < \"3.11\"",
"async-timeout<6.0,>=4.0; python_version < \"3.11\"",
"attrs>=17.3.0",
"frozenlist>=1.1.1",
"multidict<7.0,>=4.5",
"yarl<2.0,>=1.12.0",
"propcache>=0.2.0",
"yarl<2.0,>=1.17.0",
]
files = [
{file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"},
{file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"},
{file = "aiohttp-3.11.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39625703540feb50b6b7f938b3856d1f4886d2e585d88274e62b1bd273fae09b"},
{file = "aiohttp-3.11.9.tar.gz", hash = "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c"},
]
[[package]]
@@ -108,20 +109,18 @@ files = [
[[package]]
name = "apscheduler"
version = "3.10.4"
requires_python = ">=3.6"
version = "3.11.0"
requires_python = ">=3.8"
summary = "In-process task scheduler with Cron-like capabilities"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
dependencies = [
"importlib-metadata>=3.6.0; python_version < \"3.8\"",
"pytz",
"six>=1.4.0",
"tzlocal!=3.*,>=2.0",
"backports-zoneinfo; python_version < \"3.9\"",
"tzlocal>=3.0",
]
files = [
{file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"},
{file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"},
{file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"},
{file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"},
]
[[package]]
@@ -166,6 +165,17 @@ files = [
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
]
[[package]]
name = "brotli"
version = "1.1.0"
summary = "Python bindings for the Brotli compression library"
groups = ["default"]
marker = "implementation_name == \"cpython\" and python_full_version == \"3.10.12\""
files = [
{file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"},
{file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
]
[[package]]
name = "certifi"
version = "2024.8.30"
@@ -265,8 +275,8 @@ files = [
[[package]]
name = "commitizen"
version = "3.30.0"
requires_python = ">=3.8"
version = "4.0.0"
requires_python = ">=3.9"
summary = "Python commitizen client tool"
groups = ["dev"]
marker = "python_full_version == \"3.10.12\""
@@ -282,11 +292,11 @@ dependencies = [
"questionary<3.0,>=2.0",
"termcolor<3,>=1.1",
"tomlkit<1.0.0,>=0.5.3",
"typing-extensions<5.0.0,>=4.0.1; python_version < \"3.8\"",
"typing-extensions<5.0.0,>=4.0.1; python_version < \"3.11\"",
]
files = [
{file = "commitizen-3.30.0-py3-none-any.whl", hash = "sha256:8dc226a136aee61207e396101fcd89e73de67a57c06e066db982310863caaf65"},
{file = "commitizen-3.30.0.tar.gz", hash = "sha256:ae67a47c1a700b4f35ac12de0c35c7ba96f152b9377d22b6226bb87372c527b0"},
{file = "commitizen-4.0.0-py3-none-any.whl", hash = "sha256:52873ee589a64cf77fc55570dbd3f987c6ffcd33132d179eb625c4d06ae935f7"},
{file = "commitizen-4.0.0.tar.gz", hash = "sha256:16aff27e01b43015eab1c74eabbca3e284b4988dd1b146a0963282db241dc2c0"},
]
[[package]]
@@ -315,7 +325,7 @@ files = [
[[package]]
name = "fastapi"
version = "0.115.4"
version = "0.115.5"
requires_python = ">=3.8"
summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
groups = ["default"]
@@ -326,8 +336,8 @@ dependencies = [
"typing-extensions>=4.8.0",
]
files = [
{file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"},
{file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"},
{file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"},
{file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"},
]
[[package]]
@@ -659,6 +669,18 @@ files = [
{file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"},
]
[[package]]
name = "pycryptodomex"
version = "3.21.0"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
summary = "Cryptographic library for Python"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"},
{file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"},
]
[[package]]
name = "pydantic"
version = "2.8.2"
@@ -761,25 +783,14 @@ files = [
[[package]]
name = "python-multipart"
version = "0.0.17"
version = "0.0.19"
requires_python = ">=3.8"
summary = "A streaming multipart parser for Python"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "python_multipart-0.0.17-py3-none-any.whl", hash = "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d"},
{file = "python_multipart-0.0.17.tar.gz", hash = "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538"},
]
[[package]]
name = "pytz"
version = "2024.2"
summary = "World timezone definitions, modern and historical"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
{file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
{file = "python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d"},
{file = "python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc"},
]
[[package]]
@@ -868,26 +879,14 @@ files = [
[[package]]
name = "ruff"
version = "0.7.2"
version = "0.8.1"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["lint"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
{file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
]
[[package]]
name = "six"
version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
]
[[package]]
@@ -904,7 +903,7 @@ files = [
[[package]]
name = "starlette"
version = "0.41.2"
version = "0.41.3"
requires_python = ">=3.8"
summary = "The little ASGI library that shines."
groups = ["default"]
@@ -914,8 +913,8 @@ dependencies = [
"typing-extensions>=3.10.0; python_version < \"3.10\"",
]
files = [
{file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"},
{file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"},
{file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7"},
{file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835"},
]
[[package]]
@@ -947,7 +946,7 @@ name = "typing-extensions"
version = "4.12.2"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
groups = ["default"]
groups = ["default", "dev"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
@@ -984,7 +983,7 @@ files = [
[[package]]
name = "uvicorn"
version = "0.32.0"
version = "0.32.1"
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
groups = ["default"]
@@ -995,8 +994,8 @@ dependencies = [
"typing-extensions>=4.0; python_version < \"3.11\"",
]
files = [
{file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"},
{file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"},
{file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"},
{file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"},
]
[[package]]
@@ -1013,6 +1012,19 @@ files = [
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
]
[[package]]
name = "websockets"
version = "14.1"
requires_python = ">=3.9"
summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199"},
{file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e"},
{file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8"},
]
[[package]]
name = "yarl"
version = "1.17.1"
@@ -1033,12 +1045,36 @@ files = [
[[package]]
name = "yt-dlp"
version = "2024.11.4"
version = "2024.12.1.232904.dev0"
requires_python = ">=3.9"
summary = "A feature-rich command-line audio/video downloader"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
files = [
{file = "yt_dlp-2024.11.4-py3-none-any.whl", hash = "sha256:589d51ed9f154624a45c1f0ceb3d68d0d1e2031460e8dbc62139be631c20b388"},
{file = "yt_dlp-2024.11.4.tar.gz", hash = "sha256:ed204c1b61bc563e134447766d1ab343173540799e13ebb953e887ce7dcf6865"},
{file = "yt_dlp-2024.12.1.232904.dev0-py3-none-any.whl", hash = "sha256:67c1fa0986662f2f26ba70789c79c6d04fda45eb530944872f4a47ef4f94047e"},
{file = "yt_dlp-2024.12.1.232904.dev0.tar.gz", hash = "sha256:4fcce6637e70bad7c63220e4dd69a3a6b8969cbc17c93bfe569940dd173c0548"},
]
[[package]]
name = "yt-dlp"
version = "2024.12.1.232904.dev0"
extras = ["default"]
requires_python = ">=3.9"
summary = "A feature-rich command-line audio/video downloader"
groups = ["default"]
marker = "python_full_version == \"3.10.12\""
dependencies = [
"brotli; implementation_name == \"cpython\"",
"brotlicffi; implementation_name != \"cpython\"",
"certifi",
"mutagen",
"pycryptodomex",
"requests<3,>=2.32.2",
"urllib3<3,>=1.26.17",
"websockets>=13.0",
"yt-dlp==2024.12.1.232904.dev0",
]
files = [
{file = "yt_dlp-2024.12.1.232904.dev0-py3-none-any.whl", hash = "sha256:67c1fa0986662f2f26ba70789c79c6d04fda45eb530944872f4a47ef4f94047e"},
{file = "yt_dlp-2024.12.1.232904.dev0.tar.gz", hash = "sha256:4fcce6637e70bad7c63220e4dd69a3a6b8969cbc17c93bfe569940dd173c0548"},
]

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.3.46"
version = "0.3.54"
description = "Play Music with xiaomi AI speaker"
authors = [
{name = "涵曦", email = "im.hanxi@gmail.com"},
@@ -9,7 +9,7 @@ dependencies = [
"aiohttp>=3.8.6",
"miservice-fork>=2.7.0",
"mutagen>=1.47.0",
"yt-dlp>=2024.07.01",
"yt-dlp[default]>=2024.12.1.232904.dev0",
"uvicorn>=0.30.1",
"fastapi>=0.115.4",
"starlette>=0.37.2",

33
test/test_difflib.py Normal file
View File

@@ -0,0 +1,33 @@
import difflib
from xiaomusic.utils import (
find_best_match,
keyword_detection,
)
if __name__ == "__main__":
user_input = "八年的爱"
s1 = "冰冰超人 - 八年的爱新版"
s2 = "冰冰超人 - 八年的爱"
r1 = difflib.SequenceMatcher(None, s1, user_input).ratio()
r2 = difflib.SequenceMatcher(None, s2, user_input).ratio()
print(s1, r1)
print(s2, r2)
s3 = "其他"
str_list = [s2, s1, s3]
matches, remains = keyword_detection(user_input, str_list, n=10)
print(matches, remains)
extra_search_index = {}
extra_search_index["1"] = s1
extra_search_index["2"] = s2
extra_search_index["3"] = s3
real_names = find_best_match(
user_input,
str_list,
cutoff=0.4,
n=100,
extra_search_index=extra_search_index,
)
print(real_names)

View File

@@ -1 +1 @@
__version__ = "0.3.46"
__version__ = "0.3.54"

View File

@@ -30,6 +30,7 @@ class Analytics:
async def run_with_cancel(self, func, *args, **kwargs):
try:
asyncio.ensure_future(asyncio.to_thread(func, *args, **kwargs))
self.log.info("analytics run_with_cancel success")
except Exception as e:
self.log.warning(f"analytics run_with_cancel failed {e}")
return None
@@ -66,17 +67,18 @@ class Analytics:
self.gtag.send(events=event_list)
self.current_date = current_date
async def send_play_event(self, name, sec):
async def send_play_event(self, name, sec, hardware):
try:
await self.run_with_cancel(self._send_play_event, name, sec)
await self.run_with_cancel(self._send_play_event, name, sec, hardware)
except Exception as e:
self.log.warning(f"analytics send_play_event failed {e}")
self.init()
def _send_play_event(self, name, sec):
def _send_play_event(self, name, sec, hardware):
event = self.gtag.create_new_event(name="play")
event.set_event_param(name="version", value=__version__)
event.set_event_param(name="music", value=name)
event.set_event_param(name="sec", value=sec)
event.set_event_param(name="hardware", value=hardware)
event_list = [event]
self.gtag.send(events=event_list)

View File

@@ -6,22 +6,27 @@ import os
from dataclasses import asdict, dataclass, field
from typing import get_type_hints
from xiaomusic.const import (
PLAY_TYPE_ALL,
PLAY_TYPE_ONE,
PLAY_TYPE_RND,
PLAY_TYPE_SEQ,
PLAY_TYPE_SIN,
)
from xiaomusic.utils import validate_proxy
# 默认口令
def default_key_word_dict():
return {
"播放歌曲": "play",
"播放本地歌曲": "playlocal",
"关机": "stop",
"下一首": "play_next",
"上一首": "play_prev",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "set_random_play",
"随机播放": "set_play_type_rnd",
"单曲播放": "set_play_type_sin",
"顺序播放": "set_play_type_seq",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
"加入收藏": "add_to_favorites",
"收藏歌曲": "add_to_favorites",
@@ -47,12 +52,13 @@ KEY_WORD_ARG_BEFORE_DICT = {
def default_key_match_order():
return [
"分钟后关机",
"播放歌曲",
"下一首",
"上一首",
"单曲循环",
"全部循环",
"随机播放",
"单曲播放",
"顺序播放",
"关机",
"刷新列表",
"播放列表第",
@@ -82,9 +88,8 @@ class Config:
miio_tts_command: str = os.getenv("MIIO_TTS_CMD", "")
cookie: str = ""
verbose: bool = os.getenv("XIAOMUSIC_VERBOSE", "").lower() == "true"
music_path: str = os.getenv(
"XIAOMUSIC_MUSIC_PATH", "music"
) # 只能是music目录下的子目录
music_path: str = os.getenv("XIAOMUSIC_MUSIC_PATH", "music")
temp_path: str = os.getenv("XIAOMUSIC_TEMP_PATH", "music/tmp")
download_path: str = os.getenv("XIAOMUSIC_DOWNLOAD_PATH", "music/download")
conf_path: str = os.getenv("XIAOMUSIC_CONF_PATH", "conf")
cache_dir: str = os.getenv("XIAOMUSIC_CACHE_DIR", "cache")
@@ -98,7 +103,7 @@ class Config:
ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
active_cmd: str = os.getenv(
"XIAOMUSIC_ACTIVE_CMD",
"play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop",
"play,set_play_type_rnd,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop",
)
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir,tmp")
music_path_depth: int = int(os.getenv("XIAOMUSIC_MUSIC_PATH_DEPTH", "10"))
@@ -122,7 +127,7 @@ class Config:
"XIAOMUSIC_USE_MUSIC_AUDIO_ID", "1582971365183456177"
)
use_music_id: str = os.getenv("XIAOMUSIC_USE_MUSIC_ID", "355454500")
log_file: str = os.getenv("XIAOMUSIC_LOG_FILE", "/tmp/xiaomusic.txt")
log_file: str = os.getenv("XIAOMUSIC_LOG_FILE", "xiaomusic.log.txt")
# 模糊搜索匹配的最低相似度阈值
fuzzy_match_cutoff: float = float(os.getenv("XIAOMUSIC_FUZZY_MATCH_CUTOFF", "0.6"))
# 开启模糊搜索
@@ -166,6 +171,24 @@ class Config:
get_ask_by_mina: bool = (
os.getenv("XIAOMUSIC_GET_ASK_BY_MINA", "false").lower() == "true"
)
play_type_one_tts_msg: str = os.getenv(
"XIAOMUSIC_PLAY_TYPE_ONE_TTS_MSG", "已经设置为单曲循环"
)
play_type_all_tts_msg: str = os.getenv(
"XIAOMUSIC_PLAY_TYPE_ALL_TTS_MSG", "已经设置为全部循环"
)
play_type_rnd_tts_msg: str = os.getenv(
"XIAOMUSIC_PLAY_TYPE_RND_TTS_MSG", "已经设置为随机播放"
)
play_type_sin_tts_msg: str = os.getenv(
"XIAOMUSIC_PLAY_TYPE_SIN_TTS_MSG", "已经设置为单曲播放"
)
play_type_seq_tts_msg: str = os.getenv(
"XIAOMUSIC_PLAY_TYPE_SEQ_TTS_MSG", "已经设置为顺序播放"
)
recently_added_playlist_len: int = int(
os.getenv("XIAOMUSIC_RECENTLY_ADDED_PLAYLIST_LEN", "50")
)
def append_keyword(self, keys, action):
for key in keys.split(","):
@@ -188,6 +211,9 @@ class Config:
self.append_keyword(self.keywords_stop, "stop")
self.append_keyword(self.keywords_playlist, "play_music_list")
self.append_user_keyword()
self.key_match_order = [
x for x in self.key_match_order if x in self.key_word_dict
]
def __post_init__(self) -> None:
if self.proxy:
@@ -282,3 +308,22 @@ class Config:
os.makedirs(self.conf_path)
cookies_path = os.path.join(self.conf_path, "yt-dlp-cookie.txt")
return cookies_path
@property
def temp_dir(self):
if not os.path.exists(self.temp_path):
os.makedirs(self.temp_path)
return self.temp_path
def get_play_type_tts(self, play_type):
if play_type == PLAY_TYPE_ONE:
return self.play_type_one_tts_msg
if play_type == PLAY_TYPE_ALL:
return self.play_type_all_tts_msg
if play_type == PLAY_TYPE_RND:
return self.play_type_rnd_tts_msg
if play_type == PLAY_TYPE_SIN:
return self.play_type_sin_tts_msg
if play_type == PLAY_TYPE_SEQ:
return self.play_type_seq_tts_msg
return ""

View File

@@ -13,14 +13,17 @@ COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={u
PLAY_TYPE_ONE = 0 # 单曲循环
PLAY_TYPE_ALL = 1 # 全部循环
PLAY_TYPE_RND = 2 # 随机播放
PLAY_TYPE_TTS = {
PLAY_TYPE_ONE: "已经设置为单曲循环",
PLAY_TYPE_ALL: "已经设置为全部循环",
PLAY_TYPE_RND: "已经设置为随机播放",
}
PLAY_TYPE_SIN = 3 # 单曲播放
PLAY_TYPE_SEQ = 4 # 顺序播放
# 需要采用 mina 获取对话记录的设备型号
GET_ASK_BY_MINA = {
"M01",
}
# 需要使用 play_musci 接口的设备型号
NEED_USE_PLAY_MUSIC_API = {
"X08C",
"X08E",
"X8F",
}

View File

@@ -63,6 +63,14 @@ class Crontab:
self.add_job(expression, job)
# 设置播放类型任务
def add_job_set_play_type(self, expression, xiaomusic, did, arg1, **kwargs):
async def job():
play_type = int(arg1)
await xiaomusic.set_play_type(did, play_type, False)
self.add_job(expression, job)
def add_job_cron(self, xiaomusic, cron):
expression = cron["expression"] # cron 计划格式
name = cron["name"] # stop, play, play_music_list, tts

View File

@@ -303,6 +303,23 @@ async def musicinfos(
return ret
class MusicInfoObj(BaseModel):
musicname: str
title: str = ""
artist: str = ""
album: str = ""
year: str = ""
genre: str = ""
lyrics: str = ""
picture: str = "" # base64
@app.post("/setmusictag")
async def setmusictag(info: MusicInfoObj, Verifcation=Depends(verification)):
ret = xiaomusic.set_music_tag(info.musicname, info)
return {"ret": ret}
@app.get("/curplaylist")
async def curplaylist(did: str = "", Verifcation=Depends(verification)):
if not xiaomusic.did_exist(did):
@@ -325,6 +342,44 @@ class UrlInfo(BaseModel):
url: str
class DidPlayMusic(BaseModel):
did: str
musicname: str = ""
searchkey: str = ""
@app.post("/playmusic")
async def playmusic(data: DidPlayMusic, Verifcation=Depends(verification)):
did = data.did
musicname = data.musicname
searchkey = data.searchkey
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
log.info(f"playmusic {did} musicname:{musicname} searchkey:{searchkey}")
await xiaomusic.do_play(did, musicname, searchkey)
return {"ret": "OK"}
class DidPlayMusicList(BaseModel):
did: str
listname: str = ""
musicname: str = ""
@app.post("/playmusiclist")
async def playmusiclist(data: DidPlayMusicList, Verifcation=Depends(verification)):
did = data.did
listname = data.listname
musicname = data.musicname
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
log.info(f"playmusiclist {did} listname:{listname} musicname:{musicname}")
await xiaomusic.do_play_music_list(did, listname, musicname)
return {"ret": "OK"}
@app.post("/downloadjson")
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
log.info(data)
@@ -468,6 +523,51 @@ async def upload_yt_dlp_cookie(file: UploadFile = File(...)):
}
class PlayListObj(BaseModel):
name: str = "" # 歌单名
# 新增歌单
@app.post("/playlistadd")
async def playlistadd(data: PlayListObj, Verifcation=Depends(verification)):
ret = xiaomusic.play_list_add(data.name)
if ret:
return {"ret": "OK"}
return {"ret": "Add failed, may be already exist."}
# 移除歌单
@app.post("/playlistdel")
async def playlistdel(data: PlayListObj, Verifcation=Depends(verification)):
ret = xiaomusic.play_list_del(data.name)
if ret:
return {"ret": "OK"}
return {"ret": "Del failed, may be not exist."}
class PlayListMusicObj(BaseModel):
name: str = "" # 歌单名
music_list: list[str] # 歌曲名列表
# 歌单新增歌曲
@app.post("/playlistaddmusic")
async def playlistaddmusic(data: PlayListMusicObj, Verifcation=Depends(verification)):
ret = xiaomusic.play_list_add_music(data.name, data.music_list)
if ret:
return {"ret": "OK"}
return {"ret": "Add failed, may be playlist not exist."}
# 歌单移除歌曲
@app.post("/playlistdelmusic")
async def playlistdelmusic(data: PlayListMusicObj, Verifcation=Depends(verification)):
ret = xiaomusic.play_list_del_music(data.name, data.music_list)
if ret:
return {"ret": "OK"}
return {"ret": "Del failed, may be playlist not exist."}
async def file_iterator(file_path, start, end):
async with aiofiles.open(file_path, mode="rb") as file:
await file.seek(start)

View File

@@ -4,17 +4,21 @@ $(function(){
append_op_button_name("加入收藏");
append_op_button_name("取消收藏");
const PLAY_TYPE_ONE = 0; // 单曲循环
const PLAY_TYPE_ALL = 1; // 全部循环
const PLAY_TYPE_RND = 2; // 随机播放
append_op_button("play_type_all", "全部循环", "全部循环");
append_op_button("play_type_one", "单曲循环", "单曲循环");
append_op_button("play_type_rnd", "随机播放", "随机播放");
append_op_button_name("上一首");
append_op_button_name("关机");
append_op_button_name("下一首");
const PLAY_TYPE_ONE = 0; // 单曲循环
const PLAY_TYPE_ALL = 1; // 全部循环
const PLAY_TYPE_RND = 2; // 随机播放
const PLAY_TYPE_SIN = 3; // 单曲播放
const PLAY_TYPE_SEQ = 4; // 顺序播放
append_op_button("play_type_all", "全部循环", "全部循环");
append_op_button("play_type_one", "单曲循环", "单曲循环");
append_op_button("play_type_rnd", "随机播放", "随机播放");
append_op_button("play_type_sin", "单曲播放", "单曲播放");
append_op_button("play_type_seq", "顺序播放", "顺序播放");
append_op_button_name("刷新列表");
$container.append($("<hr>"));
@@ -25,7 +29,7 @@ $(function(){
var offset = 0;
var duration = 0;
let no_warning = localStorage.getItem('no-warning');
// 拉取现有配置
$.get("/getsetting", function(data, status) {
console.log(data, status);
@@ -71,6 +75,12 @@ $(function(){
} else if (cur_device.play_type == PLAY_TYPE_RND) {
$("#play_type_rnd").css('background-color', '#b1a8f3');
$("#play_type_rnd").text('✔️ 随机播放');
} else if (cur_device.play_type == PLAY_TYPE_SIN) {
$("#play_type_sin").css('background-color', '#b1a8f3');
$("#play_type_sin").text('✔️ 单曲播放');
} else if (cur_device.play_type == PLAY_TYPE_SEQ) {
$("#play_type_seq").css('background-color', '#b1a8f3');
$("#play_type_seq").text('✔️ 顺序播放');
}
}
}
@@ -182,11 +192,34 @@ $(function(){
});
}
function do_play_music_list(listname, musicname) {
$.ajax({
type: "POST",
url: "/playmusiclist",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({did: did, listname: listname, musicname: musicname}),
success: () => {
console.log("do_play_music_list succ", listname, musicname);
},
error: () => {
console.log("do_play_music_list failed", listname, musicname);
}
});
}
$("#play_music_list").on("click", () => {
var music_list = $("#music_list").val();
var music_name = $("#music_name").val();
let cmd = "播放列表" + music_list + "|" + music_name;
sendcmd(cmd);
if (no_warning) {
do_play_music_list(music_list, music_name);
return;
}
$.get(`/musicinfo?name=${music_name}`, function(data, status) {
console.log(data);
if (data.ret == "OK") {
validHost(data.url) && do_play_music_list(music_list, music_name);
}
});
});
$("#web_play").on("click", () => {
@@ -194,7 +227,7 @@ $(function(){
$.get(`/musicinfo?name=${music_name}`, function(data, status) {
console.log(data);
if (data.ret == "OK") {
$('audio').attr('src',data.url);
validHost(data.url) && $('audio').attr('src',data.url);
}
});
});
@@ -249,17 +282,32 @@ $(function(){
$container.append($button);
}
function do_play_music(musicname, searchkey) {
$.ajax({
type: "POST",
url: "/playmusic",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({did: did, musicname: musicname, searchkey: searchkey}),
success: () => {
console.log("do_play_music succ", musicname, searchkey);
},
error: () => {
console.log("do_play_music failed", musicname, searchkey);
}
});
}
$("#play").on("click", () => {
var search_key = $("#music-name").val();
if (search_key == null) {
search_key = "";
}
var filename = $("#music-filename").val();
if (filename == null) {
filename = "";
if (filename == null || filename == "") {
filename = search_key;
}
let cmd = "播放歌曲" + search_key + "|" + filename;
sendcmd(cmd);
do_play_music(filename, search_key);
});
$("#volume").on('change', function () {
@@ -298,7 +346,7 @@ $(function(){
if (cmd == "刷新列表") {
check_status_refresh_music_list(3); // 最多重试3次
}
if (["全部循环", "单曲循环", "随机播放"].includes(cmd)) {
if (["全部循环", "单曲循环", "随机播放", "单曲播放", "顺序播放"].includes(cmd)) {
location.reload();
}
},
@@ -365,7 +413,7 @@ $(function(){
.catch(error => {
console.error('Error fetching data:', error);
});
}, 300));
}, 500));
// 动态显示保存文件名输入框
const musicNameSelect = document.getElementById('music-name');
@@ -425,5 +473,48 @@ $(function(){
var minutes = Math.floor(seconds / 60);
var remainingSeconds =Math.floor(seconds % 60);
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
}
}
$("audio").on("error", (e) => {
//如果audio标签的src为空则不做任何操作兼容安卓端的低版本webview
if ($("audio").attr("src") === "") {
return;
}
console.log('%c网页播放出现错误: ', 'color: #007acc;', e.currentTarget.error.code,e.currentTarget.error.message);
alert(e.currentTarget.error.code==4 ? "无法打开媒体文件XIAOMUSIC_HOSTNAME或端口地址错误请重新设置" : "在线播放失败,请截图反馈: "+e.currentTarget.error.message);
});
function validHost(url) {
//如果 localStorage 中有 no-warning 则直接返回true
if (no_warning) {
return true;
}
const local = location.host;
const host = new URL(url).host;
// 如果当前页面的Host与设置中的XIAOMUSIC_HOSTNAME、PORT一致, 不再提醒
if (local === host) {
localStorage.setItem('no-warning', 'true');
// 设置全局变量
no_warning = true;
return true;
}
// 如果当前页面的Host与设置中的XIAOMUSIC_HOSTNAME、PORT不一致
const validHost = document.getElementById('valid-host');
let validFlag = false;
$('#local-host').text(local);
$('#setting-host').text(host);
validHost.showModal();
//监听validHost的close事件
function _handleClose() {
console.log('%c提醒HOST不一致弹窗,用户已选择: ', 'color: #007acc;', validHost.returnValue);
if (validHost.returnValue == "no-warning") {
localStorage.setItem('no-warning', 'true');
no_warning = true;
validFlag = true;
}
validHost.removeEventListener('close', _handleClose)
}
validHost.addEventListener('close', _handleClose)
return validFlag;
}
});

View File

@@ -6,9 +6,9 @@
<meta name="viewport" content="width=device-width">
<title>Debug For XiaoMusic</title>
<link rel="stylesheet" type="text/css" href="./style.css?version=1731079341">
<link rel="stylesheet" type="text/css" href="./style.css?version=1733247688">
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script src="./jquery-3.7.1.min.js?version=1731079341"></script>
<script src="./jquery-3.7.1.min.js?version=1733247688"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>歌曲下载工具</title>
<link rel="stylesheet" type="text/css" href="./style.css?version=1731079341">
<script src="./jquery-3.7.1.min.js?version=1731079341"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1733247688">
<script src="./jquery-3.7.1.min.js?version=1733247688"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>

View File

@@ -4,9 +4,9 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="./jquery-3.7.1.min.js?version=1731079341"></script>
<script src="./app.js?version=1731079341"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1731079341">
<script src="./jquery-3.7.1.min.js?version=1733247688"></script>
<script src="./app.js?version=1733247688"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1733247688">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
@@ -94,5 +94,17 @@ var vConsole = new window.VConsole();
<footer>
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
</footer>
<dialog id="valid-host">
<form method="dialog">
<p>当前页面的HOST与设置中的HOST不一致请检查是否设置错误</p>
<p>当前HOST: <span id="local-host"></span></p>
<p>设置中的HOST: <span id="setting-host"></span></p>
<div class="btn-list">
<a href="./setting.html" target="_blank">立即修改</a>
<button value="no-warning" type="submit">继续并不再显示</button>
<button value="cancle" type="submit">取消</button>
</div>
</form>
</dialog>
</body>
</html>

View File

@@ -5,7 +5,7 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width">
<title>M3U to JSON Converter</title>
<link rel="stylesheet" type="text/css" href="./style.css?version=1731079341">
<link rel="stylesheet" type="text/css" href="./style.css?version=1733247688">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>

View File

@@ -4,9 +4,9 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="./jquery-3.7.1.min.js?version=1731079341"></script>
<script src="./setting.js?version=1731079341"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1731079341">
<script src="./jquery-3.7.1.min.js?version=1733247688"></script>
<script src="./setting.js?version=1733247688"></script>
<link rel="stylesheet" type="text/css" href="./style.css?version=1733247688">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Z09NC1K7ZW"></script>
@@ -43,13 +43,13 @@ var vConsole = new window.VConsole();
<div id="setting">
<div class="rows">
<label for="account">*小米账号:</label>
<input id="account" type="text" placeholder="填写小米登录账号"></input>
<input id="account" type="text" placeholder="填写小米登录账号" />
<label for="password">*小米密码:</label>
<input id="password" type="password" placeholder="填写小米登录密码"></input>
<input id="password" type="password" placeholder="填写小米登录密码" />
<label for="hostname">*XIAOMUSIC_HOSTNAME(IP或域名):</label>
<input id="hostname" type="text"></input>
<input id="hostname" type="text" />
</div>
<hr>
<div class="rows">
@@ -59,36 +59,44 @@ var vConsole = new window.VConsole();
<option value="false">false</option>
</select>
<label for="port">监听端口(修改后需要重启):</label>
<input id="port" type="number" value="8090" />
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0" />
<label for="group_list">设备分组配置:<a href="https://github.com/hanxi/xiaomusic/issues/65#issuecomment-2215736529" target="_blank">文档</a></label>
<input id="group_list" type="text" placeholder="did1:组名1,did2:组名1,did3:组名2"></input>
<input id="group_list" type="text" placeholder="did1:组名1,did2:组名1,did3:组名2" />
<label for="music_path">音乐目录:</label>
<input id="music_path" type="text" value="music"></input>
<input id="music_path" type="text" value="music" />
<label for="download_path">音乐下载目录(必须是music的子目录):</label>
<input id="download_path" type="text" value='music/download'></input>
<input id="download_path" type="text" value='music/download' />
<label for="conf_path">配置文件目录:</label>
<input id="conf_path" type="text"></input>
<input id="conf_path" type="text" />
<label for="cache_dir">缓存文件目录:</label>
<input id="cache_dir" type="text"></input>
<input id="cache_dir" type="text" />
<label for="temp_path">临时文件目录:</label>
<input id="temp_path" type="text" value="music/tmp" />
<label for="ffmpeg_location">ffmpeg路径:</label>
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin"></input>
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin" />
<label for="log_file">日志路径:</label>
<input id="log_file" type="text" value="/tmp/xiaomusic.txt"></input>
<input id="log_file" type="text" value="xiaomusic.log.txt" />
<label for="active_cmd">允许唤醒的命令:</label>
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop"></input>
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop" />
<label for="exclude_dirs">忽略目录(逗号分割):</label>
<input id="exclude_dirs" type="text" value="@eaDir"></input>
<input id="exclude_dirs" type="text" value="@eaDir,tmp" />
<label for="music_path_depth">目录深度:</label>
<input id="music_path_depth" type="number" value="10"></input>
<input id="music_path_depth" type="number" value="10" />
<label for="search_prefix">XIAOMUSIC_SEARCH(歌曲下载方式):</label>
<select id="search_prefix">
@@ -97,7 +105,7 @@ var vConsole = new window.VConsole();
</select>
<label for="proxy">XIAOMUSIC_PROXY(ytsearch需要):</label>
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080"></input>
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080" />
<label for="remove_id3tag">去除MP3 ID3v2和填充:</label>
<select id="remove_id3tag">
@@ -112,7 +120,7 @@ var vConsole = new window.VConsole();
</select>
<label for="miio_tts_command">MiIO tts 指令(解决部分型号没有提示音的问题):</label>
<input id="miio_tts_command" type="text" placeholder="如5 或者 5-3"></input>
<input id="miio_tts_command" type="text" placeholder="如5 或者 5-3" />
<label for="disable_httpauth">关闭控制台密码验证:</label>
<select id="disable_httpauth">
@@ -120,9 +128,9 @@ var vConsole = new window.VConsole();
<option value="false">false</option>
</select>
<label for="httpauth_username">控制台账户:</label>
<input id="httpauth_username" type="text" value=""></input>
<input id="httpauth_username" type="text" value="" />
<label for="httpauth_password">控制台密码:</label>
<input id="httpauth_password" type="password" value=""></input>
<input id="httpauth_password" type="password" value="" />
<label for="disable_download">关闭下载功能:</label>
<select id="disable_download">
@@ -131,12 +139,12 @@ var vConsole = new window.VConsole();
</select>
<label for="use_music_audio_id">触屏版显示歌曲ID:</label>
<input id="use_music_audio_id" type="text" value="1582971365183456177"></input>
<input id="use_music_audio_id" type="text" value="1582971365183456177" />
<label for="use_music_id">触屏版显示歌曲分段ID:</label>
<input id="use_music_id" type="text" value="355454500"></input>
<input id="use_music_id" type="text" value="355454500" />
<label for="fuzzy_match_cutoff">模糊匹配阈值(0.1~0.9):</label>
<input id="fuzzy_match_cutoff" type="number" value="0.6"></input>
<input id="fuzzy_match_cutoff" type="number" value="0.6" />
<label for="enable_fuzzy_match">开启模糊搜索:</label>
<select id="enable_fuzzy_match">
@@ -156,28 +164,33 @@ var vConsole = new window.VConsole();
<option value="false" selected>false</option>
</select>
<label for="port">监听端口(修改后需要重启):</label>
<input id="port" type="number" value="8090"></input>
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0"></input>
<label for="pull_ask_sec">获取对话记录间隔(秒):</label>
<input id="pull_ask_sec" type="number" value="1"></input>
<input id="pull_ask_sec" type="number" value="1" />
<label for="delay_sec">下一首歌延迟播放秒数:</label>
<input id="delay_sec" type="number" value="3"></input>
<input id="delay_sec" type="number" value="3" />
<label for="stop_tts_msg">停止提示音:</label>
<input id="stop_tts_msg" type="text" value="收到,再见"></input>
<input id="stop_tts_msg" type="text" value="收到,再见" />
<label for="play_type_one_tts_msg">单曲循环提示音:</label>
<input id="play_type_one_tts_msg" type="text" value="已经设置为单曲循环" />
<label for="play_type_all_tts_msg">全部循环提示音:</label>
<input id="play_type_all_tts_msg" type="text" value="已经设置为全部循环" />
<label for="play_type_rnd_tts_msg">随机播放提示音:</label>
<input id="play_type_rnd_tts_msg" type="text" value="已经设置为随机播放" />
<label for="play_type_sin_tts_msg">单曲播放提示音:</label>
<input id="play_type_sin_tts_msg" type="text" value="已经设置为单曲播放" />
<label for="play_type_seq_tts_msg">顺序播放提示音:</label>
<input id="play_type_seq_tts_msg" type="text" value="已经设置为顺序播放" />
<label for="keywords_playlocal">播放本地歌曲口令:</label>
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲"></input>
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲" />
<label for="keywords_play">播放歌曲口令:</label>
<input id="keywords_play" type="text" value="播放歌曲,放歌曲"></input>
<input id="keywords_play" type="text" value="播放歌曲,放歌曲" />
<label for="keywords_playlist">播放列表口令:</label>
<input id="keywords_playlist" type="text" value="播放列表,播放歌单"></input>
<input id="keywords_playlist" type="text" value="播放列表,播放歌单" />
<label for="keywords_stop">停止口令:</label>
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放"></input>
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放" />
<label for="enable_yt_dlp_cookies">启用yt-dlp-cookies(需要先上传yt-dlp-cookies.txt文件):</label>
<select id="enable_yt_dlp_cookies">
@@ -191,8 +204,11 @@ var vConsole = new window.VConsole();
<option value="false" selected>false</option>
</select>
<label for="recently_added_playlist_len">最近新增的歌曲数量:</label>
<input id="recently_added_playlist_len" type="number" value="50" />
<label for="music_list_url">歌单地址:</label>
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json"></input>
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json" />
<label for="music_list_json">歌单内容:<a href="https://github.com/hanxi/xiaomusic/issues/78" target="_blank">格式文档</a></label>
<textarea id="music_list_json" type="text"></textarea>

View File

@@ -16,13 +16,19 @@ $(function(){
});
};
function updateCheckbox(selector, mi_did, device_list) {
function updateCheckbox(selector, mi_did, device_list,accountPassValid) {
// 清除现有的内容
$(selector).empty();
// 将 mi_did 字符串通过逗号分割转换为数组,以便于判断默认选中项
var selected_dids = mi_did.split(',');
//如果device_list为空则可能是未设置小米账号密码或者已设置密码但是没有过小米验证此处需要提示用户
if (device_list.length == 0) {
const loginTips = accountPassValid ? `<div class="login-tips">未发现可用的小爱设备,请检查账号密码是否输错,并关闭加速代理或在<a href="https://www.mi.com">小米官网</a>登陆过人脸或滑块验证。如仍未解决。请根据<a href="https://github.com/hanxi/xiaomusic/issues/99">FAQ</a>的内容解决问题。</div>` : `<div class="login-tips">未发现可用的小爱设备,请先在下面的输入框中设置小米的<b>账号、密码</b></div>`;
$(selector).append(loginTips);
return;
}
$.each(device_list, function(index, device) {
var did = device.miotDID;
var hardware = device.hardware;
@@ -64,7 +70,8 @@ $(function(){
// 拉取现有配置
$.get("/getsetting?need_device_list=true", function(data, status) {
console.log(data, status);
updateCheckbox("#mi_did", data.mi_did, data.device_list);
const accountPassValid = data.account && data.password;
updateCheckbox("#mi_did", data.mi_did, data.device_list, accountPassValid);
// 初始化显示
for (const key in data) {

View File

@@ -21,7 +21,6 @@ button:active, .button:active {
}
label {
margin-left: 10px;
width: 300px;
}
input,select {
margin-left: 5%;
@@ -100,3 +99,67 @@ footer {
animation: blink 1s infinite;
}
.login-tips {
color: red;
font-size: 12px;
margin-left: 10px;
}
.login-tips a {
color: rgb(9, 105, 218);
text-decoration: underline;
}
#valid-host {
padding: 20px;
border: none;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
#valid-host::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
#valid-host form input {
width: fit-content;
margin: 0;
height: fit-content;
}
#valid-host p {
word-break: break-all;
}
#valid-host p span {
color: red;
}
#valid-host a, #valid-host a:visited {
color: rgb(9, 105, 218);;
text-decoration: underline;
display: flex;
align-items: center;
}
#valid-host a:hover {
color: rgb(9, 95, 198);
}
#valid-host .btn-list {
display: flex;
justify-content: center;
margin-top: 20px;
}
#valid-host .btn-list button {
width: fit-content;
min-width: 60px;
height: 40px;
border: none;
color: white;
text-align: center;
text-decoration: none;
display: inline-block;
border-radius: 10px;
background-color: #008CBA;
}
#valid-host .btn-list button:hover {
font-weight:bold;
background-color: #007CBA;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 891 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Some files were not shown because too many files have changed in this diff Show More