Showing preview only (8,214K chars total). Download the full file or copy to clipboard to get everything.
Repository: hanxi/xiaomusic
Branch: main
Commit: e5a56933d834
Files: 195
Total size: 7.8 MB
Directory structure:
gitextract_prp6ch69/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── ci.yml
│ ├── dockerhub-description.yml
│ ├── fmt.yml
│ └── static.yml
├── .gitignore
├── .gitmodules
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── check_plugins.py
├── config-example.json
├── docs/
│ ├── .vitepress/
│ │ ├── config.mts
│ │ └── vitepress-plugin-github-issues.mts
│ ├── CNAME
│ ├── index.md
│ ├── issues/
│ │ ├── 101.md
│ │ ├── 105.md
│ │ ├── 182.md
│ │ ├── 19.md
│ │ ├── 210.md
│ │ ├── 211.md
│ │ ├── 212.md
│ │ ├── 235.md
│ │ ├── 269.md
│ │ ├── 285.md
│ │ ├── 294.md
│ │ ├── 297.md
│ │ ├── 312.md
│ │ ├── 333.md
│ │ ├── 350.md
│ │ ├── 360.md
│ │ ├── 365.md
│ │ ├── 366.md
│ │ ├── 389.md
│ │ ├── 398.md
│ │ ├── 417.md
│ │ ├── 520.md
│ │ ├── 533.md
│ │ ├── 595.md
│ │ ├── 600.md
│ │ ├── 637.md
│ │ ├── 688.md
│ │ ├── 764.md
│ │ ├── 767.md
│ │ ├── 78.md
│ │ ├── 86.md
│ │ ├── 88.md
│ │ ├── 94.md
│ │ ├── 96.md
│ │ ├── 99.md
│ │ ├── changelog.md
│ │ └── index.md
│ └── package.json
├── get_release.py
├── holiday/
│ ├── 2007.json
│ ├── 2008.json
│ ├── 2009.json
│ ├── 2010.json
│ ├── 2011.json
│ ├── 2012.json
│ ├── 2013.json
│ ├── 2014.json
│ ├── 2015.json
│ ├── 2016.json
│ ├── 2017.json
│ ├── 2018.json
│ ├── 2019.json
│ ├── 2020.json
│ ├── 2021.json
│ ├── 2022.json
│ ├── 2023.json
│ ├── 2024.json
│ ├── 2025.json
│ ├── 2026.json
│ ├── 2027.json
│ ├── renovate.json
│ └── schema.json
├── install_dependencies.sh
├── newpatch.sh
├── newversion.sh
├── package.json
├── plugins/
│ ├── __init__.py
│ ├── code1.py
│ ├── httpget.py
│ └── httppost.py
├── pyproject.toml
├── test/
│ ├── test_difflib.py
│ ├── test_music_duration.py
│ ├── test_music_tags.py
│ ├── test_remove_common_prefix.py
│ └── test_update.py
├── update-holiday.sh
├── update-static-version.py
├── xiaomusic/
│ ├── __init__.py
│ ├── analytics.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── app.py
│ │ ├── dependencies.py
│ │ ├── models.py
│ │ ├── routers/
│ │ │ ├── __init__.py
│ │ │ ├── device.py
│ │ │ ├── file.py
│ │ │ ├── music.py
│ │ │ ├── playlist.py
│ │ │ ├── plugin.py
│ │ │ └── system.py
│ │ └── websocket.py
│ ├── auth.py
│ ├── cli.py
│ ├── command_handler.py
│ ├── config.py
│ ├── config_manager.py
│ ├── const.py
│ ├── conversation.py
│ ├── crontab.py
│ ├── device_manager.py
│ ├── device_player.py
│ ├── events.py
│ ├── file_watcher.py
│ ├── holiday.py
│ ├── js_adapter.py
│ ├── js_plugin_manager.py
│ ├── js_plugin_runner.js
│ ├── music_library.py
│ ├── online_music.py
│ ├── plugin.py
│ ├── plugins-config-example.json
│ ├── qrcode_login.py
│ ├── static/
│ │ ├── default/
│ │ │ ├── debug.html
│ │ │ ├── downloadtool.html
│ │ │ ├── index.html
│ │ │ ├── m3u.html
│ │ │ ├── main.css
│ │ │ ├── md.js
│ │ │ ├── merge/
│ │ │ │ ├── index.html
│ │ │ │ ├── main.js
│ │ │ │ └── tailwind.css
│ │ │ ├── setting.css
│ │ │ ├── setting.html
│ │ │ └── setting.js
│ │ ├── index.html
│ │ ├── iwebplayer/
│ │ │ └── iwebplayer.html
│ │ ├── manifest.json
│ │ ├── onlineSearch/
│ │ │ ├── config.js
│ │ │ ├── index.html
│ │ │ └── setting.html
│ │ ├── pure/
│ │ │ ├── assets/
│ │ │ │ ├── DownloadTool-BWMSO0_N.css
│ │ │ │ ├── DownloadTool-bty5M9I6.js
│ │ │ │ ├── M3u2Json-ButJ7G_D.css
│ │ │ │ ├── M3u2Json-DeAtFyPF.js
│ │ │ │ ├── index-BAPaOAUA.js
│ │ │ │ └── index-CfMOqlRg.css
│ │ │ └── index.html
│ │ ├── soundSpace/
│ │ │ ├── assets/
│ │ │ │ ├── features-animation-DOC4MC0a.js
│ │ │ │ ├── index-KGjtlaO8.js
│ │ │ │ ├── index-ckWJnWZz.js
│ │ │ │ ├── index-fie2kaim.js
│ │ │ │ ├── index-qfFWjqIn.css
│ │ │ │ └── src-UW24ZMRV-DgU5LBZm.js
│ │ │ └── index.html
│ │ ├── sw.js
│ │ ├── tailwind/
│ │ │ ├── api.js
│ │ │ ├── debug.html
│ │ │ ├── downloadtool.html
│ │ │ ├── index.html
│ │ │ ├── libs/
│ │ │ │ ├── daisyui@4.12.23.css
│ │ │ │ ├── jquery-3.6.0.js
│ │ │ │ ├── tailwind.js
│ │ │ │ └── vue@3.5.13.js
│ │ │ ├── m3u.html
│ │ │ ├── main.css
│ │ │ ├── md.js
│ │ │ ├── now_playing.html
│ │ │ ├── now_playing.js
│ │ │ ├── setting.html
│ │ │ ├── setting.js
│ │ │ ├── tailwind.js
│ │ │ └── theme.js
│ │ ├── weapp/
│ │ │ └── qrcode.html
│ │ └── xplayer/
│ │ ├── assets/
│ │ │ ├── index-2Kb1oK2G.css
│ │ │ └── index-ESKkJcHu.js
│ │ └── index.html
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── file_utils.py
│ │ ├── music_utils.py
│ │ ├── network_utils.py
│ │ ├── openai_utils.py
│ │ ├── system_utils.py
│ │ └── text_utils.py
│ └── xiaomusic.py
└── xiaomusic.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# 指定 Python 为主要语言
*.py linguist-detectable=true
# 将 HTML 和 JavaScript 归类为文档或前端资源,降低权重
*.html linguist-vendored
*.js linguist-vendored
*.css linguist-vendored
# 如果有前端框架(如 Vue 或 React),也可以添加:
*.vue linguist-vendored
*.tsx linguist-vendored
*.jsx linguist-vendored
================================================
FILE: .github/FUNDING.yml
================================================
github: [hanxi]
custom: ['https://afdian.com/a/imhanxi']
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI Workflow
on:
push:
branches:
- "*" # 所有分支触发
tags:
- "v*"
workflow_dispatch:
env:
VERSION_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}
LATEST_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:latest
STABLE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:stable
permissions:
contents: write
id-token: write
jobs:
# 构建多架构 Docker 镜像并在所有平台上运行基本测试
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [amd64, arm64, armv7]
include:
- platform: amd64
arch: amd64
- platform: arm64
arch: arm64
- platform: armv7
arch: arm/v7
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build Docker image for ${{ matrix.platform }}
uses: docker/build-push-action@v6
with:
platforms: linux/${{ matrix.arch }}
context: .
push: false
load: true
tags: ${{ env.VERSION_TAG }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
- name: Test Docker image for ${{ matrix.platform }}
run: |
docker run --rm ${{ env.VERSION_TAG }} /app/.venv/bin/python3 /app/xiaomusic.py -h
- name: Save ${{ matrix.platform }} image to tar
run: |
docker save ${{ env.VERSION_TAG }} -o xiaomusic-${{ github.ref_name }}-${{ matrix.platform }}.tar
- name: Upload Docker images as artifacts
uses: actions/upload-artifact@v4
with:
name: docker-images-${{ matrix.platform }}
path: xiaomusic-${{ github.ref_name }}-${{ matrix.platform }}.tar
retention-days: 1
# 推送多架构 Docker 镜像到 Docker Hub
push-docker:
runs-on: ubuntu-latest
needs: build
if: github.ref_name == 'main' || startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Publish to Docker Hub
if: github.ref_name == 'main'
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
context: .
push: true
tags: ${{ env.VERSION_TAG }}
cache-from: |
type=gha,scope=amd64
type=gha,scope=arm64
type=gha,scope=armv7
- name: Publish to Docker Hub latest and stable
if: startsWith(github.ref, 'refs/tags/v')
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
context: .
push: true
tags: |
${{ env.VERSION_TAG }}
${{ env.LATEST_TAG }}
${{ env.STABLE_TAG }}
cache-from: |
type=gha,scope=amd64
type=gha,scope=arm64
type=gha,scope=armv7
# 推送镜像到 GitHub Release
publish-release:
runs-on: ubuntu-latest
needs: build
if: github.ref_name == 'main' || startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- name: Download all platform artifacts
uses: actions/download-artifact@v4
with:
pattern: docker-images-*
merge-multiple: true
- name: Install GitHub CLI
run: |
sudo apt update
sudo apt install -y gh
- name: Create or update Release tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RELEASE_NAME=${{ github.ref_name }}
RELEASE_BODY="This release is automatically updated from the ${RELEASE_NAME} branch."
EXISTING_RELEASE=$(gh release view "${RELEASE_NAME}" --json id --jq .id || echo "")
if [[ -n "${EXISTING_RELEASE}" ]]; then
echo "release exist"
else
gh release create "${RELEASE_NAME}" \
--prerelease=false \
--title "${RELEASE_NAME}" \
--notes "${RELEASE_BODY}"
fi
- name: Upload assets to Release tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RELEASE_NAME=${{ github.ref_name }}
FILES=$(find . -type f -name "xiaomusic-*.tar")
for FILE in ${FILES}; do
echo "type upload ${FILE}"
gh release upload "${RELEASE_NAME}" "${FILE}" --clobber
done
# 推送 PyPI 包
publish-pypi:
runs-on: ubuntu-latest
needs: build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- uses: actions/setup-node@v4
with:
registry-url: https://registry.npmjs.org/
node-version: lts/*
- name: Generate changelog
run: npx changelogithub
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: pdm-project/setup-pdm@v3
- name: Publish package distributions to PyPI
run: pdm publish
continue-on-error: true
================================================
FILE: .github/workflows/dockerhub-description.yml
================================================
name: Update Docker Hub Description
permissions:
contents: read
on:
push:
branches:
- main
paths:
- 'README.md'
- '.github/workflows/dockerhub-description.yml'
workflow_dispatch:
jobs:
update-description:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: hanxi/xiaomusic
================================================
FILE: .github/workflows/fmt.yml
================================================
name: fmt
on:
push:
branches:
- "*"
workflow_dispatch:
permissions:
contents: read
jobs:
format:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Setup PDM
uses: pdm-project/setup-pdm@v4
- name: install ruff
run: pip install ruff
- name: Format code
run: pdm lintfmt
- name: Check for changes
id: check_changes
run: |
if [ -n "$(git diff)" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
continue-on-error: true
# Optionally, customize the user name and commit message, and can add an email as well such as Github Actions' email
- name: Set up Git and Commit Changes
run: |
if [ "${{ steps.check_changes.outputs.changed }}" == "true" ]; then
git config --local user.name "Formatter [BOT]"
git add .
git commit -m "Auto-format code 🧹🌟🤖"
git push
fi
================================================
FILE: .github/workflows/static.yml
================================================
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
paths:
- 'docs/**'
- 'README.md'
- 'CHANGELOG.md'
- '.github/workflows/static.yml'
# Runs on issue events
issues:
types: [opened, edited, reopened] # Specify events you're interested in
release:
types:
- uploaded
workflow_run:
workflows:
- CI Workflow
types:
- completed
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: write
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Install dependencies
working-directory: ./docs # 指定工作目录为 docs
run: |
npm install
- name: Build VitePress
env:
VITE_GITHUB_ISSUES_TOKEN: ${{ secrets.VITE_GITHUB_ISSUES_TOKEN }}
working-directory: ./docs # 指定工作目录为 docs
run: | # 有点小问题,得执行2次
npm run docs:build
npm run docs:build
- uses: pdm-project/setup-pdm@v3
- name: pdm
run: pdm install --prod --frozen-lockfile
- name: generate versions.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pdm run get_release.py
- name: Check for changes
id: check_changes
run: |
if [ -n "$(git diff docs)" ]; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
continue-on-error: true
# Optionally, customize the user name and commit message, and can add an email as well such as Github Actions' email
- name: Set up Git and Commit Changes
run: |
if [ "${{ steps.check_changes.outputs.changed }}" == "true" ]; then
git config --local user.name "Issues Docs [BOT]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "Auto-Generate docs 🤖"
git push
fi
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: './docs/.vitepress/dist'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
*_bak/
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
ffmpeg
music
test.sh
conf
setting.json
.DS_Store
cache
tmp/
xiaomusic.log.txt*
node_modules
reference/
.aone_copilot/
.idea/
================================================
FILE: .gitmodules
================================================
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- hooks:
- id: commitizen
- id: commitizen-branch
stages:
- push
repo: https://github.com/commitizen-tools/commitizen
rev: v3.27.0
================================================
FILE: CHANGELOG.md
================================================
## v0.4.26 (2026-03-20)
### Fix
- proxy handler CDN safeguard & content-type based FFmpeg routing (#791)
## v0.4.25 (2026-03-19)
### Fix
- proxy url too long for xiaomi speaker (#786)
- 无法上传音乐 close #783
## v0.4.24 (2026-03-13)
### Feat
- 新增适配iPhone主题 (#775)
- Add 肉肉音乐TV link to index.html (#773)
### Fix
- 修复无法收藏歌曲的问题
## v0.4.23 (2026-02-17)
### Feat
- **api**: support playlist_name in downloadonemusic (#756)
- **api**: support downloadonemusic dirname under music_path (#751)
- 二维码登录功能 (#750)
- add dirname parameter to /downloadonemusic API (#749)
- add /getplayerstatus API to get full player status (#747)
### Fix
- 漏提交base64 (#761)
- **playlist**: avoid custom/folder name collisions (#752)
- 点击 本地端口-自动填功能 报错 #742
## v0.4.22 (2026-01-26)
### Feat
- 新增 HMusic 客户端
### Fix
- 修复播放歌曲口令问题 close #731
- 修复nodejs进程无限重启的问题 see #728
## v0.4.21 (2026-01-24)
### Feat
- 优化首页
### Fix
- 修复定时任务播放临时歌单报错问题
## v0.4.20 (2026-01-23)
### Feat
- 优化默认主题的无障碍使用
- 完善默认主题本机播放功能
- 优化默认主题界面布局
- 添加测试口令入口
## v0.4.19 (2026-01-23)
### Feat
- 默认主题支持无障碍使用
### Fix
- 修复收藏歌曲报错
- 修复 playlist相关接口报错问题
## v0.4.18 (2026-01-21)
### Feat
- 默认口令都允许唤醒
- 优化默认主题设置页面
### Fix
- 修复插件获取最后一条命令的问题
- 修复 /proxy 接口问题
## v0.4.17 (2026-01-20)
### Fix
- 支持代理 m3u8 格式链接 close #711
- 兼容旧配置 hostname 报错 close #713
- 修复下载歌曲报错 close #709
- 修复报错 close #708
## v0.4.16 (2026-01-18)
### Feat
- 新增【风花雪乐】手机APP入口
- 优化获取时长卡顿问题
- 删除怀旧主题
### Fix
- 只有一单音乐,同时不在播放列表中时更新播放列表 (#703)
## v0.4.15 (2026-01-17)
### Feat
- TTS默认值修改
## v0.4.14 (2026-01-16)
### Fix
- 修复报错 close #696
## v0.4.13 (2026-01-16)
### Feat
- 登录支持仅填 cookie #688
- 网络歌单插件功能更新 (#690)
- 编译出 arm/v7 架构的镜像
### Fix
- 修复网络歌曲 proxy 处理问题
## v0.4.12 (2026-01-15)
### Fix
- 修复启动报错
## v0.4.11 (2026-01-15)
### Fix
- 修复报错 close #684
- 修复登陆报错问题
- 修复保存报错
## v0.4.10 (2026-01-15)
### Feat
- 主页新增获取对话记录开关
### Fix
- 设置token到account,解决登陆问题 (#680)
- 把cookie到.mi.token,解决登录报错问题 (#676)
- 修复ws连接错误
## v0.4.9 (2026-01-15)
### Feat
- 新增清空临时目录接口,优化tts接口
### Fix
- 修复刷新列表问题 close #621
- 修复定时器关闭问题
### Refactor
- 修改api接口
## v0.4.8 (2026-01-13)
### Feat
- 支持配置 cookie
- 接入 edge-tts 解决文字转语音的问题 close #642
### Fix
- 修复关机慢的问题,关机改完接口 close #479
- 修复一直循环播放一首歌曲的问题 close #519
## v0.4.7 (2026-01-12)
### Feat
- 默认关闭语音口令
- 修复同一个时间不能执行多个定时任务的问题
- 打包docker镜像并上传github
- 重构播放时长获取规则 see #668
### Fix
- 修改cache目录,修复播放时长问题
- 修复异常未加载插件无法禁用bug (#666)
## v0.4.6 (2026-01-12)
### Feat
- 定时任务支持选多个歌曲组成一个歌单播放
### Fix
- 修复【启用】网络歌曲过代理后,在线歌曲播放链接被base编码两次bug (#664)
- 修复报错 #660
- 修复在线推送发出设置成循环播放回复的问题
## v0.4.5 (2026-01-10)
### Feat
- 在线播放功能优化,新增AI智能提取用户口令 (#654)
### Fix
- 修复重构后的问题
- 修复部分网络歌曲无法播放的问题
- 解决在线歌曲链接转换问题
- 修复重构引起的问题
- 修复重构后遇到的问题
## v0.4.4 (2026-01-07)
### Fix
- 修复无法关闭的问题
- 修复无法关闭的问题
- 修复关闭不了的问题
- self.log.inf缺少o
### Refactor
- lint code
## v0.4.3 (2026-01-07)
### Fix
- 定时播放报错
## v0.4.2 (2026-01-06)
### Fix
- 修复不能播放的问题
## v0.4.1 (2026-01-06)
### Fix
- 修复不能播放的问题
## v0.4.0 (2026-01-06)
### Feat
- 移除第三方设备播放功能
- 新增定时任务刷新网络歌单 #616
- add last_cmd to store latest query (#635)
- 增加musicfree插件集成功能
### Fix
- 修复 python3.14 中无法运行的问题
- 修复顺序播放上一首失效的问题
- 修复本地歌曲无法播放的问题
### Refactor
- 重构 xiaomusic.py 模块
- 重构 xiaomusic.py 模块
- 重构 xiaomusic.py 模块
- 优化日志
- 优化日志
- 重构拆分 httpserver 文件
- 重构拆分 utils 文件
- 优化日志输出
- 清理 httpserver.py 中的无效代码
## v0.3.102 (2025-12-21)
### Feat
- 支持记录每个播放列表上次播放的歌曲 close #613
## v0.3.101 (2025-12-01)
### Fix
- hostname 不允许填 127.0.0.1 和 localhost
- 修复自动填端口为0的问题
- SoundSpace随机播放模式失效 #578 (#585)
- 下线失效的统计服务 close #579
## v0.3.100 (2025-11-09)
### Feat
- soundSpace兼容移动端 (#577)
## v0.3.99 (2025-11-02)
### Feat
- 新增 SoundScape 主题
- add path aliases and base URL configuration in Vite(SoundSpace) (#569)
- SoundSpace Theme (#568)
## v0.3.98 (2025-11-02)
## v0.3.97 (2025-11-01)
### Feat
- 升级 miservice
### Fix
- 优化登录问题
## v0.3.96 (2025-10-28)
### Fix
- tts问题临时处理
## v0.3.95 (2025-10-26)
### Fix
- 修复型号M01不能播放问题
## v0.3.94 (2025-10-25)
### Fix
- 尝试修复登录问题
## v0.3.93 (2025-10-25)
### Fix
- 尝试修复登录问题
## v0.3.92 (2025-10-25)
### Fix
- 尝试解决登录失败问题
## v0.3.91 (2025-10-24)
### Feat
- 删掉没用的网页更新功能
- 获取对话记录接口修改
### Fix
- 尝试修复登录问题
## v0.3.90 (2025-10-22)
### Fix
- 修复登录问题 see #547
## v0.3.89 (2025-10-21)
### Fix
- 修复缺失库报错
## v0.3.88 (2025-09-16)
### Feat
- 新增歌单合并工具
- 兼容 X6A 型号
## v0.3.87 (2025-09-11)
### Feat
- 新增 websocket 接口获取当前播放状态
### Fix
- 修复本地播放失败问题
## v0.3.86 (2025-09-08)
### Feat
- LX音源支持http_proxy
- 支持LX歌单
- 代理播放模式使用原始地址获取歌曲时长
- 网络歌曲支持使用代理的方式播放
- 新增代理播放链接功能 see: #525
## v0.3.85 (2025-08-08)
### Fix
- 修复延迟关机按钮失效问题
## v0.3.84 (2025-08-03)
### Feat
- 下一首歌延迟播放秒数支持负数,用于解决播放下一首时会播放上一首的开头几秒的问题
### Fix
- 修复谷歌字体问题
- 文件监控: 忽略非文件创建、删除和移动事件 (#514)
- 修复中文定时关机无法识别的BUG (#510)
- 修复日志文件删除失败的问题
## v0.3.83 (2025-06-12)
### Feat
- 新增开关控制是否开始谷歌统计 see #473
- 支持b站合集和收藏下载 (#487)
### Fix
- 修复安全问题
- 修复安全问题
## v0.3.82 (2025-05-30)
### Fix
- 修复节假日文件没有打包到 docker 镜像里的问题
## v0.3.81 (2025-05-28)
### Feat
- 定时任务支持工作日和休息日 see #182
### Fix
- 动态小程序码生成接口 (#478)
- 指定日志编码,避免中文乱码 (#475)
## v0.3.80 (2025-05-18)
### Feat
- 新增 OH2 型号的支持
- 支持配置最大搜索歌曲数量 see #462
### Fix
- 修复获取在线歌曲长度 (#469)
## v0.3.79 (2025-04-29)
### Fix
- 修复型号LX05不能播放问题
## v0.3.78 (2025-04-09)
### Feat
- 优化获取时长的方式
### Fix
- 修复部分型号单曲播放功能无效的问题
- 监控文件夹仅对音乐格式生效,减少不必要的刷新 (#441)
- 修复型号X4B不能播放问题
## v0.3.77 (2025-03-13)
### Feat
- OH2P 型号支持打断说话
### Fix
- 修复首页看不到设备的问题
## v0.3.76 (2025-03-11)
### Feat
- 整理第三方播放设备的代码
- 删除歌曲口令默认关闭
- 新增删除歌曲口令 see #402
- 增加音量控制for -3thplay.html by socketio.emit (#404)
- 增加大声,音量,继续的指令,用于3thplay (#401)
- 不用再手动配置 tts command, 优先使用已知的 tts command
- 新增重新初始的定时任务 #182
- 新增定时任务用于开启和关闭拉取对话记录 #182
- 加入了遥控网页播放,用于实现电视上使用 (#395)
### Fix
- tailwind播放页面有报错,暂时禁用
- tailwind 主题问题
- 修复下载后不自动播放的问题
- 修复每次打开页面都是随机播放的问题
## v0.3.75 (2025-02-18)
### Feat
- 监测音乐文件夹变化更新歌曲列表 (#394)
- 添加正在播放页面 (#386)
- 更新tailwind主题 (#383)
### Fix
- 未开启模糊匹配播放错误问题 (#393)
- 修复tailwind主题样式问题 (#384)
## v0.3.74 (2025-01-21)
### Feat
- 新增 Tailwind 主题
- 修改设置页面文档链接
### Fix
- 修复下载歌单重命名问题
## v0.3.73 (2025-01-16)
### Fix
- 当前歌曲不在列表中时才切换列表 close #359
- 修复默认主题播放进度时间问题
- 尝试修复获取对话记录失败的问题 close #362
## v0.3.72 (2025-01-11)
### Feat
- 新增播放文字功能
### Fix
- 修复默认主题没有单曲循环的问题 see #355
## v0.3.71 (2025-01-07)
### Feat
- 支持自动填 ip 和端口
### Fix
- 搜索歌曲窗口不自动关闭 see #351
- 解决歌词信息写入失败的问题
- 修复一些小问题
- 非播放中也返回歌曲时长 see #340
## v0.3.70 (2025-01-04)
### Fix
- 尝试修复部分设备无法启动的问题
- 解决首页提示翻译英文问题
- 尝试解决 supervisor 启动报错
## v0.3.69 (2025-01-01)
### Feat
- 支持关闭获取对话记录功能
### Fix
- 尝试解决网络卡顿问题
## v0.3.68 (2024-12-31)
### Feat
- umami 脚本改为异步加载
- 支持 python3.13 版本
- 增加均衡歌曲响度(可选) (#338)
### Fix
- 修复保存设置时可能出现报错的情况
## v0.3.67 (2024-12-29)
### Feat
- 简化设置,不允许修改监听端口
### Fix
- 修复默认主题搜索问题
## v0.3.66 (2024-12-26)
### Fix
- 修复歌曲批量重命名的问题
- 修复自定义歌单删除后没刷新歌单列表
- 尝试修复更新失败问题
## v0.3.65 (2024-12-24)
### Fix
- 处理图像报错
- 修改歌单名字漏更新歌单列表
- 修复获取自定义歌单接口报错
## v0.3.64 (2024-12-22)
### Fix
- 使用自己架设的 sentry 服务,解决 Cloudflare 额度超量问题
## v0.3.63 (2024-12-22)
### Perf
- 只监控报错信息
## v0.3.62 (2024-12-21)
### Fix
- 修复首次配置时,默认主题只有一个设备的问题。
- 修复一些报错问题
## v0.3.61 (2024-12-19)
### Fix
- 尝试修复更新问题
### Refactor
- 重构更新流程
## v0.3.60 (2024-12-19)
## v0.3.59 (2024-12-19)
### Feat
- 新增更多的歌单编辑相关接口
- 一键更新功能
- 接入 sentry 平台
- 实现更新接口
- 下载完成之后修改文件权限为755 close #316
### Fix
- 解决飞牛平台报错问题
### Refactor
- 优化代码日志级别
- 更新等消息推送到客户端再重启
- 更新接口修改
## v0.3.58 (2024-12-15)
### Fix
- 尝试解决七牛设备问题
- 修复 umami 统计函数报错,解决七牛环境问题
## v0.3.57 (2024-12-14)
### Feat
- 优化批量下载工具命名和下载高码率音频
- 新增搜索播放口令用于生成临时播放列表
- 新增设置项 ignore_tag_dirs 用于忽略读取目录下的标签信息,解决挂载 alist 目录的问题
- 新主题 Material (#299)
### Fix
- 默认主题刷新时选中当前播放歌曲
- 修复当前播放列表没更新的问题
- 修复搜索时不显示保存输入框问题
- 收藏 (#301)
- 修复收藏歌曲失败的问题
- 小屏幕设备主页显示问题 (#300)
### Refactor
- 修改默认主题
- 后端也加入 umami 统计
- 新增自托管 umami 统计
- XIAOMUSIC_HOSTNAME 携带端口号友好提醒并处理 (#303)
- 修改标题
- 冲突解决错误
## v0.3.56 (2024-12-07)
### Feat
- tag 信息支持写入到歌曲文件 see #266
- 开启gzip压缩
### Fix
- 播放失败设置重试次数10次,解决设备失联后无限重试的问题
- 修复最近新增歌单问题
- 小程序码移动端布局兼容 (#293)
## v0.3.55 (2024-12-04)
### Fix
- 修复播放接口报错问题
## 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
- 升级依赖库
### Fix
- 添加依赖库 requests
### Refactor
- 依赖库已经支持分段获取静态文件,重构代码
## v0.3.45 (2024-11-08)
### Feat
- 定时任务支持设置音量
- 播放歌单口令支持配置
### Fix
- 修复定时任务报错问题
## v0.3.44 (2024-11-01)
### Feat
- 日志时间里加上日期
### Fix
- 修复搜索失败的问题
## v0.3.43 (2024-10-30)
### Feat
- 播放列表可以删除当前歌曲(!危险操作,请在设置中心开启相关功能) (#250)
- 插件自定义口令支持获取语音输入内容 #105
### Fix
- 修复谷歌统计导致的卡顿问题
- 解决挂载网盘卡死的问题
## v0.3.42 (2024-10-24)
### Fix
- 尝试修复缺少 libtiff.so.6 文件的问题 #244
- 修复默认主题播放歌曲输入框空的情况
- 尝试修复停止后自动播放的问题
## v0.3.41 (2024-10-17)
### Feat
- 设置默认时区为东八区 closed #236
### Fix
- 修复获取标签信息报错问题
- remove_id3_tags return None if no id3 tag (#238)
- bug in del_music (#237)
## v0.3.40 (2024-10-16)
### Feat
- 默认主题的播放列表上显示歌曲数量
### Fix
- 修复播放卡顿问题(谷歌统计地址无法访问的情况)
## v0.3.39 (2024-10-15)
### Feat
- 固定的播放列表全部初始化
- 生产环境与开发环境接口分离、关于页面增加返回到主页的链接
update: 支持https页面未及时更新的问题
### Fix
- pure主题 当前设备与远程设备未正确区分的问题 (#234)
- static和doc添加basic auth (#231)
### Refactor
- 修改默认UI播放提示词 (#233)
## v0.3.38 (2024-10-14)
### Feat
- 播放状态接口返回当前播放列表 (#229)
- 新增口令收藏歌曲用来收藏当前播放的歌曲
- 默认UI搜索框动态显示 (#228)
- 文件转换逻辑延迟到读取文件的时候 see #218
- 重写播放组件,现在支持歌词显示了
- 使用 /cmdstatus 接口来判断异步任务是否完成
- 新增接口 /cmdstatus 用于查询异步任务是否执行完毕
- XMusicPlayer播放器主题优化 (#216)
- XMusicPlayer播放器主题 (#214)
- 新增 yt-dlp cookies 文件参数支持
- 新增批量下载歌曲工具
- 新增后台网站图标
- 加密音乐和图片访问链接 (#200)
- 歌曲信息中的图片改为url #190
- 新增更新提醒
- 定时任务新增刷新播放列表接口
- 后台设置名称优化
- 新增按钮刷新 tag 信息
- 新增 musicinfos 接口用于批量查询歌曲信息
- 增加 tags 缓存 (#193)
- 使用 opencc 将歌曲名转化为简体 (#192)
- 搜索的歌曲存成列表供前端显示,实现额外索引 (#188)
- 搜索多个结果,并更新“当前”播放列表 (#185)
- musicinfo接口新增musictag参数,用于返回歌曲额外信息
- 新增口令【播放列表第几个+列表名】来播放列表里的第几个 #158
- 新增定时任务功能 #182
- hostname can take protocol,域名支持 https 格式 (#181)
### Fix
- xplayer 收藏歌曲、取消收藏 (#230)
- 修复型号M01获取对话记录时间戳的问题
- 修复型号M01无法获取到对话记录的问题
- 使用小爱设备播放时组件异常的问题 (#217)
- 修复图片获取失败的问题
- 修复 yt-dlp-cookies 报错
- 修复自定义口令末尾多余逗号的情况
- 修复windows下路径问题
- 解决 L05C 无提示音问题 support MiIOService tts (#198)
- 解决歌曲信息乱码问题
- 修复搜索补全不生效的问题
- 修复默认主题没有选中上次播放列表的问题
- ffmpeg only output audio (#184)
### Refactor
- 新增清理缓存按钮
- 优化默认UI的搜索框#226 (#227)
- 修复告警
- 体验优化,音乐列表缓存 (#222)
- 修改为播放选中歌曲
- 更新静态文件
### Perf
- 对歌曲信息中的图片缩小到300 #190
## v0.3.37 (2024-09-20)
### Feat
- Pure主题更新 设置中心新增主题音乐列表样式选择、夜间模式、其他多项优化 (#180)
## v0.3.36 (2024-09-19)
### Feat
- Pure 主题更新 (#178)
- 支持配置获取对话记录间隔时间 #169
- 允许在后台设置监听端口
### Fix
- 修复开启继续播放时歌曲播放不完整问题 (#177)
## v0.3.35 (2024-09-18)
### Feat
- 允许跨域访问 #172
### Fix
- 修复 Pure 主题白屏无法打开的问题 (#176)
## v0.3.34 (2024-09-18)
### Feat
- 新增 pure 主题 vue + elementUI (#172)
### Fix
- 主页适配移动端
- 修复网页播放点击后没有关闭旧声音的问题 #166
- 修复单曲循环的情况下歌曲不在当前播放列表时失效的情况
### Refactor
- 优化代码:输入框处理抖动问题,网页播放修改实现方式 see #166
## v0.3.33 (2024-09-15)
### Feat
- 调整页面布局
- 支持继续播放 (#171)
### Fix
- #168 安全优化: 设置数据接口密码隐藏处理
- 修复谷歌统计报错问题
### Refactor
- 优化谷歌统计
## v0.3.32 (2024-09-14)
### Feat
- 新增谷歌统计
- 增加播放进度 (#160)
### Fix
- 优化audio_id查询方式 (#165)
- 播放链接接口支持复杂的链接
## v0.3.31 (2024-09-10)
### Feat
- 新增播放上一首歌曲功能 #90
- 新增所有歌曲列表
- 触屏版显示歌曲名称 (#156)
### Fix
- 修复插件示例报错 #105
- 修复当前播放歌曲没保存的问题 #90
## v0.3.30 (2024-09-07)
### Feat
- 修改设置按钮位置
- 新增网页播放接口 #138
## v0.3.29 (2024-09-06)
### Feat
- 设置页面新增接口文档入口
### Fix
- 修复网页开启秘密验证无法播歌的问题 #149
## v0.3.28 (2024-09-03)
### Feat
- 新增歌曲收藏功能 #87
### Fix
- docker下minetypes无法判断m4a
### Refactor
- ffmpeg_location 从配置里读取
## v0.3.27 (2024-09-02)
### Feat
- Add feature as requested in issue #143
### Fix
- 默认下载目录修改
### Refactor
- 处理 code review 问题'
## v0.3.26 (2024-08-17)
### Feat
- 删除网关模式
## v0.3.25 (2024-08-16)
### Feat
- 设置页面支持配置 use_music_api 选项
## v0.3.24 (2024-08-01)
### Fix
- #131 修复多设备切换时播放模式显示错误问题
## v0.3.23 (2024-08-01)
### Fix
- 修复部分文件获取不到播放时长问题
- 处理安全问题
## v0.3.22 (2024-08-01)
### Feat
- 网关模式支持配置,默认关闭
### Fix
- 继续优化延迟问题
## v0.3.21 (2024-07-30)
### Feat
- 尝试加个网关在前面处理静态文件来加速文件获取
### Fix
- 使用前置网关处理静态文件来加速,尝试解决延迟的问题
- 播放前先立即暂停之前的音乐
## v0.3.20 (2024-07-30)
### Fix
- 尝试修复延迟问题,修复播放停止不了的问题
## v0.3.19 (2024-07-30)
### Fix
- 调整配置,优化获取歌曲时长接口
## v0.3.18 (2024-07-29)
### Fix
- #135 修复获取不到播放时长时只播放3秒的问题
## v0.3.17 (2024-07-28)
### Fix
- 优化日志输出,尝试排查延迟播放的问题
## v0.3.16 (2024-07-28)
## v0.3.15 (2024-07-28)
### Fix
- 修复自定义口令重复的问题
- 修复日志输出问题
- 修复退出异常问题
## v0.3.14 (2024-07-28)
### Feat
- 优化播放延迟问题,并新增配置下一首播放的延迟秒数
## v0.3.13 (2024-07-24)
### Fix
- 解决 docker 镜像问题
## v0.3.12 (2024-07-24)
### Feat
- 优化获取文件播放时长接口,尝试解决播放延迟和操作面板卡顿的问题
## v0.3.11 (2024-07-22)
### Feat
- Add remove mp3 id3 tag function
### Fix
- #130 单曲循环的模式下,播放列表的指令不生效
### Refactor
- 优化代码
## v0.3.10 (2024-07-19)
### Feat
- 支持软连接的接口直接用os.walk即可
### Fix
- 修复软连接目录不能播放的问题
- 修复自定义语音口令设置不生效的问题
## v0.3.9 (2024-07-17)
### Feat
- #119 音乐目录支持软连接
### Fix
- 修复日志下载报错问题
- 兼容旧的setting.json文件中conf_path为空的情况
- 修复设置页面可能打不开的问题
## v0.3.8 (2024-07-16)
### Fix
- 修复播放url接口问题
## v0.3.7 (2024-07-16)
### Feat
- 播放链接按钮对应给个默认的链接用于测试
- Uvicorn 的日志信息合并到 xiaomusic 日志里显示
## v0.3.6 (2024-07-15)
### Fix
- #126 修复pip安装时主页打不开的问题
## v0.3.5 (2024-07-15)
### Fix
- #116 播放失败自动切下首歌
## v0.3.4 (2024-07-15)
### Fix
- #125 修复本地英文歌曲匹大小写字母配不到的问题
## v0.3.3 (2024-07-15)
### Fix
- 尝试修复播放卡顿问题 see #124
## v0.3.2 (2024-07-15)
### Fix
- #122 pip安装方式下,static目录找不到报错
- 版本更新时更新页面缓存
## v0.3.1 (2024-07-15)
### Fix
- 修复主页选择设备不生效的问题 see #120
## v0.3.0 (2024-07-14)
### Feat
- 建议音乐目录和配置目录分开不同目录
- 优化后台网络设置,同时支持ipv4和ipv6
- 使用fastapi替换flask,解决多线程问题
- #106 网页上显示音箱当前状态(播放中or空闲中)以及当前的播放模式
- 优化首页加载慢的问题
- 优化设置页面布局,方便配置必须项
- 优化配置界面,支持配置分组
- 支持多设备分开播放 see #65
### Fix
- #114 修复部分 mp3 文件长度识别错误
- 删除 armv6 的支持
- 修复编译问题
- 修复音乐路径设置后找不到音乐的问题
- 修复启动报错的问题
- 修复CI警告问题
## v0.2.0 (2024-07-09)
### Feat
- 触屏版可以不用设置 XIAOMUSIC_USE_MUSIC_API
- 升级依赖库
- 唤醒口令配置支持配语音词,简化自定义口令配置 see #105
## v0.1.101 (2024-07-07)
### Fix
- #81 修复播放列表时,当前歌曲不在列表没有更换歌曲的问题
- #110 修复配置加载问题
## v0.1.100 (2024-07-07)
### Fix
- 日志代码写错
## v0.1.99 (2024-07-07)
### Fix
- #81 修复播放列表没有继续播放上次播放的歌曲,并把随机播放,全部循环,单曲循环状态落地
## v0.1.98 (2024-07-07)
### Fix
- 修复多设备获取不到对话记录的问题 see #65
- #93 修复目录深度设置后导致目录下的歌曲无法加到播放列表里的问题
## v0.1.97 (2024-07-06)
### Fix
- 修复网页控制台设置页面保存报错
## v0.1.96 (2024-07-06)
### Feat
- 使用commitizen管理版本号
- 页面版本号链接到CHANGELOG页面
- 规范版本管理
## v0.1.95 (2024-07-06)
## v0.1.94 (2024-07-06)
### Feat
- 优化多设备接口执行效果,尽量做到同时执行
### Fix
- 新增参数配置强制打断小爱说话
- 修复多设备获取对话记录的问题
- 修复windows下路径分隔符被视为转移符导致音箱无法播放音乐的问题
- 修复播放链接报错
- 修复配置页面默认配置被置空的问题
## v0.1.93 (2024-07-05)
### Feat
- 访问账号密码默认为空
- 支持下载的目录与本地音乐目录分开 see #98
- 新增m4a文件格式支持
- 设置页面支持配置多设备
- 默认用空的后台账号和密码
- 支持多个设备同时播放 see #65
- 新增自定义口令功能 #105
### Fix
- 修复设置页面没成功初始化设置问题
- 修复镜像缺少文件问题
- 尝试解决插件路径问题
- 设置页面日志路径写错了
- 修复口令导致异常关闭的问题
## v0.1.92 (2024-07-04)
### Feat
- 启动参数新增 --port 配置监听端口
- 外网访问端口可独立配置
- 优化设置页面,新增更多配置项
- 首次保存设置后不需要重启容器
### Fix
- 日志文件配置的环境变量写错了
## v0.1.91 (2024-07-03)
### Fix
- 尝试解决触屏版不能播放的问题
## v0.1.90 (2024-07-02)
### Feat
- 优化触屏版播放页面显示歌曲
## v0.1.89 (2024-07-02)
### Feat
- 尝试解决触屏版无法播放的问题
### Fix
- 播放歌曲写成固定的了
- 播放歌曲时被其他指令打断后没有继续播放
## v0.1.88 (2024-07-02)
### Feat
- 日志里不要输出敏感信息
- 优化下载 ffmpeg 脚本,尝试解决 armv7 环境问题
- 优化日志输出信息
- 尝试解决触屏版无法播放的问题
### Fix
- 是否下载中判断错误导致播放无法自动重新开始播放
- 升级yt-dlp到2024.07.01
- 修复部分型号关机失败的问题
## v0.1.87 (2024-07-01)
### Fix
- 修复XIAOMUSIC_USE_MUSIC_API=true时播放不了的问题
## v0.1.86 (2024-07-01)
### Feat
- 优化 ffmpeg 安装脚本
- 新增调试工具用来调试 player_play_music 接口
- 升级依赖库 MiService
### Fix
- 尝试修复 armv7 的 ffmpeg 问题
- 尝试修复关机失败的问题
- 修复口令不能播放的问题
## v0.1.85 (2024-06-30)
### Feat
- 版本号链接到github的release页面,方便查看版本更新日志
### Fix
- 修复电台删除后没有从电台列表中删除的问题
## v0.1.84 (2024-06-30)
### Feat
- config.json 支持更多配置选项
- 新增 XIAOMUSIC_STOP_TTS_MSG 配置关机提示音
## v0.1.83 (2024-06-30)
## v0.1.82 (2024-06-30)
### Feat
- 优化指令匹配规则
## v0.1.81 (2024-06-30)
## v0.1.80 (2024-06-30)
### Fix
- #91 修复下载歌曲报错
## v0.1.79 (2024-06-29)
## v0.1.77 (2024-06-29)
### Fix
- #52 支持配置模糊匹配本地歌曲
## v0.1.76 (2024-06-28)
## v0.1.75 (2024-06-28)
## v0.1.74 (2024-06-28)
## v0.1.73 (2024-06-28)
## v0.1.72 (2024-06-28)
## v0.1.71 (2024-06-28)
### Fix
- #83
## v0.1.70 (2024-06-27)
## v0.1.69 (2024-06-26)
## v0.1.67 (2024-06-26)
## v0.1.66 (2024-06-26)
## v0.1.65 (2024-06-26)
## v0.1.64 (2024-06-26)
## v0.1.62 (2024-06-25)
## v0.1.61 (2024-06-25)
## v0.1.60 (2024-06-25)
## v0.1.58 (2024-06-25)
### Fix
- 登陆失败不阻塞启动
## v0.1.57 (2024-06-24)
## v0.1.56 (2024-06-24)
## v0.1.55 (2024-06-23)
### Fix
- #47 支持配置基础的BaseAuth登录
## v0.1.54 (2024-06-23)
### Fix
- #76 新增XIAOMUSIC_MUSIC_PATH_DEPTH配置生成播放列表的目录深度,默认10
- #74 配置目录可以和下载目录分开配置, 新增XIAOMUSIC_CONF_PATH用来设置配置目录,不配置时使用下载目录
## v0.1.53 (2024-06-23)
## v0.1.52 (2024-06-21)
## v0.1.51 (2024-06-20)
## v0.1.49 (2024-06-20)
## v0.1.48 (2024-06-16)
## v0.1.47 (2024-06-16)
## v0.1.46 (2024-06-15)
## v0.1.45 (2024-06-15)
## v0.1.44 (2024-06-14)
## v0.1.43 (2024-06-14)
## v0.1.41 (2024-06-14)
## v0.1.40 (2024-06-12)
## v0.1.39 (2024-06-12)
## v0.1.38 (2024-06-12)
### Fix
- #70 下一首歌曲不存在时从播放列表中删除并继续找下一首
## v0.1.37 (2024-06-04)
## v0.1.36 (2024-05-30)
## v0.1.35 (2024-05-30)
### Fix
- #67 没配置did时也允许启动 http 服务
## v0.1.34 (2024-05-19)
## v0.1.33 (2024-05-19)
### Fix
- #50 新增配置页面
- #62
## v0.1.32 (2024-05-17)
## v0.1.31 (2024-05-16)
## v0.1.30 (2024-05-16)
### Fix
- 控制台显示版本号 #59
## v0.1.29 (2024-05-16)
### Fix
- #57 #55
## v0.1.28 (2024-05-16)
## v0.1.27 (2024-05-16)
## v0.1.26 (2024-05-08)
## v0.1.25 (2024-05-06)
## v0.1.24 (2024-04-30)
## v0.1.23 (2024-04-30)
## v0.1.22 (2024-04-30)
## v0.1.21 (2024-04-08)
## v0.1.20 (2024-04-08)
## v0.1.19 (2024-04-04)
## v0.1.18 (2024-02-24)
## v0.1.16 (2024-02-24)
## v0.1.15 (2024-02-03)
## v0.1.14 (2024-02-03)
## v0.1.13 (2024-02-02)
## v0.1.12 (2024-01-30)
### Fix
- set volume failed
## v0.1.11 (2024-01-29)
## v0.1.10 (2024-01-29)
## v0.1.9 (2024-01-28)
### Fix
- arg1 漏修改
## v0.1.8 (2024-01-28)
### Fix
- http server listen host
## v0.1.7 (2024-01-28)
## v0.1.6 (2024-01-28)
## v0.1.5 (2024-01-27)
## v0.1.4 (2024-01-27)
### Fix
- error when play next
## v0.1.3 (2023-10-15)
## v0.1.2 (2023-10-15)
## v0.1.1 (2023-10-14)
================================================
FILE: Dockerfile
================================================
# 定义构建参数,用于指定架构和基础镜像
ARG PYTHON_VERSION=3.14
# 根据不同架构选择对应的基础镜像
FROM python:${PYTHON_VERSION}-alpine AS base-linux-amd64
FROM python:${PYTHON_VERSION}-alpine AS base-linux-arm64
FROM python:${PYTHON_VERSION}-bookworm AS base-linux-arm-v7
FROM python:${PYTHON_VERSION}-alpine AS run-linux-amd64
FROM python:${PYTHON_VERSION}-alpine AS run-linux-arm64
FROM python:${PYTHON_VERSION}-bookworm AS run-linux-arm-v7
FROM --platform=$BUILDPLATFORM alpine AS shelf
# 这里的逻辑是关键:接收标准的 TARGETPLATFORM (如 linux/amd64)
# 并将其转换为我们定义的别名格式 (如 linux-amd64)
ARG TARGETPLATFORM
RUN echo ${TARGETPLATFORM//\//-} > /platform_id
# 根据TARGETPLATFORM自动选择对应的builder阶段
ARG TARGETPLATFORM
FROM base-${TARGETPLATFORM//\//-} AS builder
# 安装构建依赖(根据基础镜像类型区分)
RUN if [ -f /etc/alpine-release ]; then \
# Alpine系统依赖
apk add --no-cache \
build-base \
nodejs \
npm \
zlib-dev \
jpeg-dev \
freetype-dev \
lcms2-dev \
openjpeg-dev \
tiff-dev \
libwebp-dev; \
else \
# Debian系统依赖
apt-get update && apt-get install -y --no-install-recommends \
build-essential \
nodejs \
npm \
zlib1g-dev \
libjpeg-dev \
libfreetype6-dev \
liblcms2-dev \
libopenjp2-7-dev \
libtiff5-dev \
libwebp-dev \
&& rm -rf /var/lib/apt/lists/*; \
fi
# 安装PDM
RUN pip install -U pdm
ENV PDM_CHECK_UPDATE=false
WORKDIR /app
COPY pyproject.toml README.md package.json ./
# 安装Python和Node.js依赖
RUN pdm install --prod --no-editable -v
RUN npm install --loglevel=verbose
# 复制应用代码
COPY xiaomusic/ ./xiaomusic/
COPY plugins/ ./plugins/
COPY holiday/ ./holiday/
COPY xiaomusic.py .
# -------------------------- 运行阶段 --------------------------
# 根据TARGETPLATFORM自动选择对应的runner阶段
ARG TARGETPLATFORM
FROM run-${TARGETPLATFORM//\//-} AS runner
# 安装运行时依赖(区分Alpine和Debian)
RUN if [ -f /etc/alpine-release ]; then \
# Alpine运行时依赖
apk add --no-cache \
ffmpeg \
nodejs \
npm; \
else \
# Debian运行时依赖
apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*; \
fi
# 设置工作目录
WORKDIR /app
# 从构建阶段复制产物
COPY --from=builder /app/.venv ./.venv
COPY --from=builder /app/node_modules ./node_modules/
COPY --from=builder /app/xiaomusic/ ./xiaomusic/
COPY --from=builder /app/plugins/ ./plugins/
COPY --from=builder /app/holiday/ ./holiday/
COPY --from=builder /app/xiaomusic.py .
COPY --from=builder /app/xiaomusic/__init__.py /base_version.py
COPY --from=builder /app/package.json .
# 创建FFmpeg软链接目录(兼容不同系统的ffmpeg路径)
RUN mkdir -p /app/ffmpeg/bin \
&& ln -s $(which ffmpeg) /app/ffmpeg/bin/ffmpeg \
&& ln -s $(which ffprobe) /app/ffmpeg/bin/ffprobe
RUN touch /app/.dockerenv
# 设置卷和暴露端口
VOLUME /app/conf
VOLUME /app/music
EXPOSE 8090
# 设置环境变量
ENV TZ=Asia/Shanghai
ENV PATH=/app/.venv/bin:/usr/local/bin:$PATH
# 直接启动xiaomusic应用
CMD ["/app/.venv/bin/python3", "/app/xiaomusic.py"]
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 涵曦
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# XiaoMusic: 无限听歌,解放小爱音箱
[](https://github.com/hanxi/xiaomusic)
[](https://hub.docker.com/r/hanxi/xiaomusic)
[](https://hub.docker.com/r/hanxi/xiaomusic)
[](https://pypi.org/project/xiaomusic/)
[](https://pypi.org/project/xiaomusic/)
[](https://pypi.org/project/xiaomusic/)
[](https://github.com/hanxi/xiaomusic/releases)
[](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
[](https://visitorbadge.io/status?path=hanxi%2Fxiaomusic)
---
<p align="center">
<strong>🎵 使用小爱音箱播放音乐,音乐使用 yt-dlp 下载</strong>
</p>
<p align="center">
<a href="https://github.com/hanxi/xiaomusic">🏠 GitHub</a> •
<a href="https://xdocs.hanxi.cc/">📖 文档</a> •
<a href="https://github.com/hanxi/xiaomusic/issues/99">💬 FAQ</a> •
<a href="#-讨论区">💭 讨论区</a>
</p>
---
> [!TIP]
> **新手指南**:初次安装遇到问题请查阅 [💬 FAQ问题集合](https://github.com/hanxi/xiaomusic/issues/99),一般遇到的问题都已经有解决办法。
## 👋 快速入门指南
已经支持在 web 设置页面配置其他参数,不再需要设置环境变量, docker compose 配置如下(选一个即可):
```yaml
services:
xiaomusic:
image: hanxi/xiaomusic
container_name: xiaomusic
restart: always
ports:
- 58090:8090
volumes:
- /xiaomusic_music:/app/music
- /xiaomusic_conf:/app/conf
```
🔥 国内:
```yaml
services:
xiaomusic:
image: docker.hanxi.cc/hanxi/xiaomusic
container_name: xiaomusic
restart: always
ports:
- 58090:8090
volumes:
- /xiaomusic_music:/app/music
- /xiaomusic_conf:/app/conf
```
测试版:
```yaml
services:
xiaomusic:
image: hanxi/xiaomusic:main
container_name: xiaomusic
restart: always
ports:
- 58090:8090
volumes:
- /xiaomusic_music:/app/music
- /xiaomusic_conf:/app/conf
```
对应的 docker 启动命令如下:
```bash
docker run -p 58090:8090 -v /xiaomusic_music:/app/music -v /xiaomusic_conf:/app/conf hanxi/xiaomusic
```
🔥 国内:
```bash
docker run -p 58090:8090 -v /xiaomusic_music:/app/music -v /xiaomusic_conf:/app/conf docker.hanxi.cc/hanxi/xiaomusic
```
测试版:
```
docker run -p 58090:8090 -v /xiaomusic_music:/app/music -v /xiaomusic_conf:/app/conf hanxi/xiaomusic:main
```
- 其中 conf 目录为配置文件存放目录,music 目录为音乐存放目录,建议分开配置为不同的目录。
- /xiaomusic_music 和 /xiaomusic_conf 是 docker 所在的主机的目录,可以修改为其他目录。如果报错找不到 /xiaomusic_music 目录,可以先执行 `mkdir -p /xiaomusic_{music,conf}` 命令新建目录。
- /app/music 和 /app/conf 是 docker 容器里的目录,不要去修改。
- 58090 是 NAS 本地端口的。8090 是容器端口,不要去修改。
- 后台访问地址为: http://NAS_IP:58090
> [!NOTE]
> docker 和 docker compose 二选一即可,启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
> [!TIP]
> 目前安装步骤已经是最简化了,如果还是嫌安装麻烦,可以微信或者 QQ 约我远程安装,我一般周末和晚上才有时间,需要赞助个辛苦费 :moneybag: 50 元一次。
遇到问题可以去 web 设置页面底部点击【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
> [!TIP]
> 作者新写了一个更简洁的个人音乐服务器,支持更强的插件扩展 <https://github.com/mimusic-org/mimusic>
> [!TIP]
> 作者写的一个游戏服务器开发实战课程 <https://www.lanqiao.cn/courses/2770> ,购买时记得使用优惠码: `2CZ2UA5u` 。
> [!TIP]
> - 适用于 NAS 上安装的开源工具: <https://github.com/hanxi/tiny-nav>
> - 适用于 NAS 上安装的网页打印机: <https://github.com/hanxi/cups-web>
> - PVE 移动端 UI 界面:<https://github.com/hanxi/pve-touch>
> - 喜欢听书的可以配合这个工具使用 <https://github.com/hanxi/epub2mp3>
> [!TIP]
>
> - 🔥【广告:可用于安装 frp 实现内网穿透】
> - 🔥 海外 RackNerd VPS 机器推荐,可支付宝付款。
> - <a href="https://my.racknerd.com/aff.php?aff=11177"><img src="https://racknerd.com/banners/320x50.gif" alt="RackNerd Mobile Leaderboard Banner" width="320" height="50"></a>
> - 不知道选哪个套餐可以直接买这个最便宜的 <https://my.racknerd.com/aff.php?aff=11177&pid=923>
> - 也可以用来部署代理,docker 部署方法见 <https://github.com/hanxi/blog/issues/96>
> [!TIP]
>
> - 🔥【广告: 搭建您的专属大模型主页
告别繁琐配置难题,一键即可畅享稳定流畅的AI体验!】<https://university.aliyun.com/mobile?userCode=szqvatm6>
> [!TIP]
> - 免费主机
> - <a href="https://dartnode.com?aff=SnappyPigeon570"><img src="https://dartnode.com/branding/DN-Open-Source-sm.png" alt="Powered by DartNode - Free VPS for Open Source" width="320"></a>
## 🎤 功能特性
### 🤐 支持语音口令
#### 基础播放控制
- **播放歌曲** - 播放本地的歌曲
- **播放歌曲+歌名** - 例如:播放歌曲周杰伦晴天
- **上一首** / **下一首** - 切换歌曲
- **关机** / **停止播放** - 停止播放
#### 播放模式
- **单曲循环** - 重复播放当前歌曲
- **全部循环** - 循环播放所有歌曲
- **随机播放** - 随机顺序播放
#### 歌单管理
- **播放歌单+目录名** - 例如:播放歌单其他
- **播放歌单第几个+列表名** - 详见 [#158](https://github.com/hanxi/xiaomusic/issues/158)
- **播放歌单收藏** - 播放收藏歌单
#### 收藏功能
- **加入收藏** - 将当前播放的歌曲加入收藏歌单
- **取消收藏** - 将当前播放的歌曲从收藏歌单移除
> [!TIP]
> **隐藏玩法**:对小爱同学说"播放歌曲小猪佩奇的故事",会先下载小猪佩奇的故事,然后再播放。
## 📦 安装方式
### 方式一:Docker Compose(推荐)
详见 [👋 快速入门指南](#-快速入门指南)
### 方式二:Pip 安装
```shell
# 安装
pip install -U xiaomusic
# 查看帮助
xiaomusic --help
# 启动(使用配置文件)
xiaomusic --config config.json
# 启动(使用默认端口 8090)
xiaomusic
```
> [!NOTE]
> `config.json` 文件可以参考 `config-example.json` 文件配置。详见 [#94](https://github.com/hanxi/xiaomusic/issues/94)
## 👨💻 开发指南
### 🔩 开发环境运行
1. **下载依赖**
```shell
./install_dependencies.sh
```
2. **安装环境**
```shell
pdm install
```
3. **启动服务**
```shell
pdm run xiaomusic.py
```
默认监听端口 8090,使用其他端口请自行修改。
4. **查看 API 文档**
访问 <http://localhost:8090/docs> 查看接口文档。
> [!NOTE]
> 目前的 web 控制台非常简陋,欢迎有兴趣的朋友帮忙实现一个漂亮的前端,需要什么接口可以随时提需求。
### 🚦 代码提交规范
提交前请执行以下命令检查代码和格式化代码:
```shell
pdm lintfmt
```
### 🐳 本地编译 Docker Image
```shell
docker build -t xiaomusic .
```
### 🛠️ 技术栈
- **后端**:Python + FastAPI 框架
- **容器化**:Docker
- **前端**:jQuery
## 📱 设备支持
### 已测试支持的设备
| 型号 | 设备名称 |
|------|---------|
| **L06A** | [小爱音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l06a) |
| **L07A** | [Redmi小爱音箱 Play](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l7a) |
| **S12/S12A/MDZ-25-DA** | [小米AI音箱](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.s12) |
| **LX5A** | [小爱音箱 万能遥控版](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx5a) |
| **LX05** | [小爱音箱Play(2019款)](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx05) |
| **L15A** | [小米AI音箱(第二代)](https://home.mi.com/webapp/content/baike/product/index.html?model=xiaomi.wifispeaker.l15a#/) |
| **L16A** | [Xiaomi Sound](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l16a) |
| **L17A** | [Xiaomi Sound Pro](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l17a) |
| **LX06** | [小爱音箱Pro](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx06) |
| **LX01** | [小爱音箱mini](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.lx01) |
| **L05B** | [小爱音箱Play](https://home.mi.com/baike/index.html#/detail?model=xiaomi.wifispeaker.l05b) |
| **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** | 触屏版音箱 |
| **X08C/X08E/X8F** | 触屏版音箱 |
| **M01/XMYX01JY** | 小米小爱音箱HD |
| **OH2P** | XIAOMI 智能音箱 Pro |
| **OH2** | XIAOMI 智能音箱 |
> [!NOTE]
> - 型号与产品名称对照可在 [小米IoT平台](https://home.miot-spec.com/s/xiaomi.wifispeaker) 查询
> - 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢
> - 目前应该所有设备类型都已经支持播放,有问题可随时反馈
### 🎵 支持音乐格式
- **mp3** - 标准音频格式
- **flac** - 无损音频格式
- **wav** - 无损音频格式
- **ape** - 无损音频格式
- **ogg** - 开源音频格式
- **m4a** - AAC 音频格式
> [!NOTE]
> - 本地音乐会搜索目录下上面格式的文件,下载的歌曲是 mp3 格式
> - 已知 L05B、L05C、LX06、L16A 不支持 flac 格式
> - 如果格式不能播放可以打开【转换为MP3】和【型号兼容模式】选项,详见 [#153](https://github.com/hanxi/xiaomusic/issues/153#issuecomment-2328168689)
## 🌏 网络歌单功能
可以配置一个 json 格式的歌单,支持电台和歌曲,也可以直接用别人分享的链接。同时配备了 m3u 文件格式转换工具,可以很方便地把 m3u 电台文件转换成网络歌单格式的 json 文件。
详细用法见 [#78](https://github.com/hanxi/xiaomusic/issues/78)
> [!NOTE]
> 欢迎有想法的朋友们制作更多的歌单转换工具,一起完善项目功能!
## ⚠️ 安全提醒
> [!IMPORTANT]
>
> 1. 如果配置了公网访问 xiaomusic ,请一定要开启密码登陆,并设置复杂的密码。且不要在公共场所的 WiFi 环境下使用,否则可能造成小米账号密码泄露。
> 2. 强烈不建议将小爱音箱的小米账号绑定摄像头,代码难免会有 bug ,一旦小米账号密码泄露,可能监控录像也会泄露。
## 💬 社区与支持
### 📢 讨论区
<p align="center">
<a href="https://github.com/hanxi/xiaomusic/issues">💬 GitHub Issues</a> •
<a href="https://pd.qq.com/s/e2jybz0ss">🎮 QQ频道</a> •
<a href="https://qm.qq.com/q/lxIhquqbza">👥 QQ交流群</a> •
<a href="https://github.com/hanxi/xiaomusic/issues/86">💬 微信群</a>
</p>
### 🤝 如何贡献
我们欢迎所有形式的贡献,包括但不限于:
- 🐛 **报告 Bug**:在 [Issues](https://github.com/hanxi/xiaomusic/issues) 中提交问题
- 💡 **功能建议**:分享你的想法和建议
- 📝 **改进文档**:帮助完善文档和教程
- 🎨 **前端美化**:优化 Web 控制台界面
- 🔧 **代码贡献**:提交 Pull Request
> [!TIP]
> 提交代码前请确保运行 `pdm lintfmt` 检查代码规范
## 📚 相关资源
### 👉 更多教程
更多功能见 [📝 文档汇总](https://github.com/hanxi/xiaomusic/issues/211)
### 🎨 第三方主题
- [pure 主题 xiaomusicUI](https://github.com/52fisher/xiaomusicUI)
- [移动端的播放器主题](https://github.com/52fisher/XMusicPlayer)
- [Tailwind主题](https://github.com/clarencejh/xiaomusic)
- [SoundScape主题](https://github.com/jhao0413/SoundScape)
- [第三方主题](https://github.com/DarrenWen/xiaomusicui)
### 📱 配套应用
- [微信小程序: 卯卯音乐](https://github.com/F-loat/xiaoplayer)
- [手机APP: 风花雪乐](https://github.com/jokezc/mi_music)
- [JS在线播放插件](https://github.com/boluofan/xiaomusic-online)
- [手机APP: HMusic](https://github.com/hpcll/HMusic)
- [安卓TV: 肉肉音乐TV](https://github.com/GanHuaLin/rouroumusic-tv)
### ❤️ 致谢
**核心依赖**
- [xiaomi](https://www.mi.com/) - 小米智能设备
- [xiaogpt](https://github.com/yihong0618/xiaogpt) - 项目灵感来源
- [MiService](https://github.com/yihong0618/MiService) - 小米服务接口
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) - 音乐下载工具
**开发工具**
- [PDM](https://pdm.fming.dev/latest/) - Python 包管理
- [FastAPI](https://fastapi.tiangolo.com/) - Web 框架
- [Umami](https://github.com/umami-software/umami) - 统计分析
- [Sentry](https://github.com/getsentry/sentry) - 报错监控
**参考资料**
- [实现原理](https://github.com/yihong0618/gitblog/issues/258)
- [awesome-xiaoai](https://github.com/zzz6519003/awesome-xiaoai)
**特别感谢**
- 所有帮忙调试和测试的朋友
- 所有反馈问题和建议的朋友
- 所有贡献代码和文档的开发者
## 🚨 免责声明
本项目仅供学习和研究目的,不得用于任何商业活动。用户在使用本项目时应遵守所在地区的法律法规,对于违法使用所导致的后果,本项目及作者不承担任何责任。
本项目可能存在未知的缺陷和风险(包括但不限于设备损坏和账号封禁等),使用者应自行承担使用本项目所产生的所有风险及责任。
作者不保证本项目的准确性、完整性、及时性、可靠性,也不承担任何因使用本项目而产生的任何损失或损害责任。
使用本项目即表示您已阅读并同意本免责声明的全部内容。
## Star History
[](https://star-history.com/#hanxi/xiaomusic&Date)
## 💖 支持项目
如果这个项目对你有帮助,欢迎通过以下方式支持:
### ⭐ Star 项目
点击右上角的 ⭐ Star 按钮,让更多人发现这个项目
### 💰 赞赏支持
- [💝 爱发电](https://afdian.com/a/imhanxi) - 持续支持项目发展
- 扫码请作者喝杯奶茶 ☕
<p align="center">
<img src="https://i.v2ex.co/7Q03axO5l.png" alt="赞赏码" width="300">
</p>
### 🎁 其他支持方式
- 分享给更多需要的朋友
- 提交 Bug 报告和功能建议
- 贡献代码和文档
---
<p align="center">
<strong>感谢你的支持!❤️</strong>
</p>
## License
[MIT](https://github.com/hanxi/xiaomusic/blob/main/LICENSE) License © 2024 涵曦
================================================
FILE: check_plugins.py
================================================
#!/usr/bin/env python3
"""
检查所有插件的加载状态
"""
import sys
sys.path.append(".")
from xiaomusic.config import Config
from xiaomusic.js_plugin_manager import JSPluginManager
def check_all_plugins():
print("=== 检查所有插件加载状态 ===\n")
config = Config()
config.verbose = True
class SimpleLogger:
def info(self, msg):
print(f"[INFO] {msg}")
def error(self, msg):
print(f"[ERROR] {msg}")
def debug(self, msg):
print(f"[DEBUG] {msg}")
print("1. 创建插件管理器...")
manager = JSPluginManager(None)
manager.config = config
manager.log = SimpleLogger()
import time
time.sleep(3) # 等待插件加载
print("\n2. 获取所有插件状态...")
plugins = manager.refresh_plugin_list()
print(f" 总共找到 {len(plugins)} 个插件")
# 分类插件状态
working_plugins = []
failed_plugins = []
for plugin in plugins:
if plugin.get("loaded", False) and plugin.get("enabled", False):
working_plugins.append(plugin)
else:
failed_plugins.append(plugin)
print(f"\n 正常工作的插件 ({len(working_plugins)} 个):")
for plugin in working_plugins:
print(f" ✓ {plugin['name']}")
print(f"\n 失败的插件 ({len(failed_plugins)} 个):")
for plugin in failed_plugins:
print(f" ✗ {plugin['name']}: {plugin.get('error', 'Unknown error')}")
# 清理
if hasattr(manager, "node_process") and manager.node_process:
manager.node_process.terminate()
if __name__ == "__main__":
check_all_plugins()
================================================
FILE: config-example.json
================================================
{
"account": "",
"password": "",
"mi_did": "",
"cookie": "",
"verbose": false,
"music_path": "music",
"temp_path": "music/tmp",
"download_path": "music/download",
"conf_path": "conf",
"cache_dir": "music/cache",
"hostname": "http://192.168.2.5",
"port": 8090,
"public_port": 58090,
"proxy": null,
"loudnorm": null,
"search_prefix": "bilisearch:",
"ffmpeg_location": "./ffmpeg/bin",
"get_duration_type": "ffprobe",
"active_cmd": "play,set_play_type_rnd,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,play_next,play_prev,set_play_type_one,set_play_type_all,set_play_type_sin,set_play_type_seq,gen_music_list,add_to_favorites,del_from_favorites,cmd_del_music,online_play,singer_play",
"exclude_dirs": "@eaDir,tmp",
"ignore_tag_dirs": "",
"music_path_depth": 10,
"disable_httpauth": true,
"httpauth_username": "",
"httpauth_password": "",
"music_list_url": "",
"music_list_json": "",
"custom_play_list_json": "",
"disable_download": false,
"key_word_dict": {
"下一首": "play_next",
"上一首": "play_prev",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "set_play_type_rnd",
"单曲播放": "set_play_type_sin",
"顺序播放": "set_play_type_seq",
"分钟后关机": "stop_after_minute",
"刷新列表": "gen_music_list",
"加入收藏": "add_to_favorites",
"收藏歌曲": "add_to_favorites",
"取消收藏": "del_from_favorites",
"播放列表第": "play_music_list_index",
"删除歌曲": "cmd_del_music",
"播放本地歌曲": "playlocal",
"本地播放歌曲": "playlocal",
"播放歌曲": "play",
"放歌曲": "play",
"在线播放": "online_play",
"播放歌手": "singer_play",
"关机": "stop",
"暂停": "stop",
"停止": "stop",
"停止播放": "stop",
"播放列表": "play_music_list",
"播放歌单": "play_music_list",
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
},
"key_match_order": [
"分钟后关机",
"下一首",
"上一首",
"单曲循环",
"全部循环",
"随机播放",
"单曲播放",
"顺序播放",
"关机",
"刷新列表",
"播放列表第",
"播放列表",
"加入收藏",
"收藏歌曲",
"取消收藏",
"删除歌曲",
"播放本地歌曲",
"本地播放歌曲",
"播放歌曲",
"放歌曲",
"在线播放",
"播放歌手",
"暂停",
"停止",
"停止播放",
"播放歌单",
"测试自定义口令",
"测试链接"
],
"use_music_api": false,
"use_music_audio_id": "1582971365183456177",
"use_music_id": "355454500",
"log_file": "xiaomusic.log.txt",
"fuzzy_match_cutoff": 0.6,
"enable_fuzzy_match": true,
"stop_tts_msg": "收到,再见",
"enable_config_example": true,
"keywords_playlocal": "播放本地歌曲,本地播放歌曲",
"keywords_play": "播放歌曲,放歌曲",
"keywords_online_play": "在线播放",
"keywords_singer_play": "播放歌手",
"keywords_stop": "关机,暂停,停止,停止播放",
"keywords_playlist": "播放列表,播放歌单",
"user_key_word_dict": {
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
},
"enable_force_stop": false,
"devices": {},
"group_list": "",
"remove_id3tag": false,
"convert_to_mp3": false,
"delay_sec": 0,
"continue_play": false,
"enable_file_watch": false,
"file_watch_debounce": 10,
"pull_ask_sec": 1,
"enable_pull_ask": false,
"crontab_json": "",
"enable_yt_dlp_cookies": false,
"enable_save_tag": false,
"enable_analytics": true,
"get_ask_by_mina": false,
"play_type_one_tts_msg": "已经设置为单曲循环",
"play_type_all_tts_msg": "已经设置为全部循环",
"play_type_rnd_tts_msg": "已经设置为随机播放",
"play_type_sin_tts_msg": "已经设置为单曲播放",
"play_type_seq_tts_msg": "已经设置为顺序播放",
"recently_added_playlist_len": 50,
"enable_cmd_del_music": false,
"web_music_proxy": true,
"edge_tts_voice": "zh-CN-XiaoyiNeural",
"enable_auto_clean_temp": true
}
================================================
FILE: docs/.vitepress/config.mts
================================================
import { loadEnv, defineConfig } from 'vitepress'
import AutoSidebar from 'vite-plugin-vitepress-auto-sidebar';
import GitHubIssuesPlugin from './vitepress-plugin-github-issues.mts';
export default async ({ mode }) => {
const env = loadEnv(mode || '', process.cwd())
return defineConfig({
title: "XiaoMusic",
description: "XiaoMusic doc",
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Guide', link: '/issues' },
{ text: 'Admin', link: 'https://x.hanxi.cc' },
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/hanxi/xiaomusic' }
],
footer: {
message: '基于 MIT 许可发布',
copyright: `版权所有 © 2023-${new Date().getFullYear()} 涵曦`
},
},
sitemap: {
hostname: 'https://xdocs.hanxi.cc'
},
head: [
['script', { defer: true, src: 'https://umami.hanxi.cc/script.js', 'data-website-id': '29cca3f5-e420-432b-adc7-8a1325d31c68' }]
],
lastUpdated: true,
markdown: {
lineNumbers: false, // 关闭代码块行号显示
// 自定义 markdown-it 插件
config: (md) => {
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const aIndex = tokens[idx].attrIndex('target');
if (aIndex < 0) {
tokens[idx].attrPush(['target', '_self']); // 将默认行为改为不使用 _blank
} else {
tokens[idx].attrs![aIndex][1] = '_self'; // 替换 _blank 为 _self
}
return self.renderToken(tokens, idx, options);
};
},
},
logLevel: 'warn',
vite: {
plugins: [
AutoSidebar({
path: '.',
collapsed: true,
titleFromFile: true,
}),
GitHubIssuesPlugin({
repo: 'hanxi/xiaomusic',
token: env.VITE_GITHUB_ISSUES_TOKEN,
replaceRules: [
{
baseUrl: 'https://github.com/hanxi/xiaomusic/issues',
targetUrl: '/issues',
},
],
githubProxy: 'https://gproxy.hanxi.cc/proxy',
}),
],
}
})
}
================================================
FILE: docs/.vitepress/vitepress-plugin-github-issues.mts
================================================
import axios from 'axios';
import fs from 'fs';
import path from 'path';
import type { Plugin } from 'vitepress';
interface ReplaceRule {
baseUrl: string;
targetUrl: string;
}
interface GitHubIssuesPluginOptions {
repo: string;
token: string;
replaceRules: ReplaceRule[];
githubProxy: string;
}
// 增强超时 + 重试
axios.defaults.timeout = 15000;
async function fetchAllIssues(repo: string, token: string): Promise<any[]> {
const maxRetries = 5;
let attempt = 0;
const allIssues: any[] = [];
let page = 1;
while (true) {
try {
const response = await axios.get(`https://api.github.com/repos/${repo}/issues`, {
headers: { Authorization: `token ${token}` },
params: { page, per_page: 100 },
});
if (response.data.length === 0) break;
allIssues.push(...response.data);
page++;
attempt = 0;
} catch (error: any) {
attempt++;
console.error(`[Issue 获取失败] page ${page}, 重试 ${attempt}/${maxRetries}`);
if (attempt >= maxRetries) {
console.error(`❌ 终止获取 Issue,已获取数量:${allIssues.length}`);
break;
}
await new Promise(r => setTimeout(r, 3000 * attempt));
}
}
return allIssues;
}
async function fetchIssueComments(repo: string, issueNumber: number, token: string): Promise<any[]> {
const maxRetries = 3;
let attempt = 0;
const allComments: any[] = [];
let page = 1;
while (attempt < maxRetries) {
try {
const res = await axios.get(
`https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`,
{
headers: { Authorization: `token ${token}` },
params: { page, per_page: 100 },
}
);
if (res.data.length === 0) break;
allComments.push(...res.data);
page++;
} catch (err) {
attempt++;
if (attempt >= maxRetries) break;
await new Promise(r => setTimeout(r, 2000));
}
}
return allComments;
}
function clearDirectory(dir: string) {
if (fs.existsSync(dir)) {
fs.readdirSync(dir).forEach(file => {
const p = path.join(dir, file);
fs.lstatSync(p).isDirectory() ? clearDirectory(p) : fs.unlinkSync(p);
});
}
}
function copyFile(src: string, dest: string) {
if (fs.existsSync(src)) fs.copyFileSync(src, dest);
}
function prependToFile(file: string, text: string) {
if (!fs.existsSync(file)) return;
const c = fs.readFileSync(file, 'utf8');
fs.writeFileSync(file, `${text}\n\n${c}`);
}
function replaceGithubAssetUrls(content: string, proxy: string): string {
return content
.replace(/https:\/\/github\.com\/[^\/]+\/[^\/]+\/assets\/[\w-]+/g, m => m.replace('https://github.com', proxy))
.replace(/https:\/\/github\.com\/user-attachments\/assets\/[\w-]+/g, m => m.replace('https://github.com', proxy));
}
// 核心修复:生成空文件占位,防止构建报错
function ensureIssueFile(number: number, dir: string, htmlUrl: string) {
const file = path.join(dir, `${number}.md`);
if (fs.existsSync(file)) return;
const content = `---
title: Issue #${number} (加载失败)
---
# Issue #${number} 加载失败
原因:GitHub API 请求失败 / 网络超时
[前往查看](${htmlUrl})
`;
fs.writeFileSync(file, content, 'utf8');
console.log(`⚠️ 自动生成占位文件:${file}`);
}
export default function GitHubIssuesPlugin(options: GitHubIssuesPluginOptions): Plugin {
const { repo, token, replaceRules, githubProxy } = options;
return {
name: 'vitepress-plugin-github-issues',
async buildStart() {
console.log('🚀 开始从 GitHub Issues 生成文档...');
try {
const issues = await fetchAllIssues(repo, token);
console.log(`✅ 成功获取 Issue 数量:${issues.length}`);
const issuesDir = path.join(process.cwd(), 'issues');
clearDirectory(issuesDir);
if (!fs.existsSync(issuesDir)) fs.mkdirSync(issuesDir);
// 复制 README / CHANGELOG
copyFile(path.join(process.cwd(), '../README.md'), path.join(issuesDir, 'index.md'));
copyFile(path.join(process.cwd(), '../CHANGELOG.md'), path.join(issuesDir, 'changelog.md'));
prependToFile(path.join(issuesDir, 'changelog.md'), '# 版本日志');
// 遍历处理 Issue
for (const issue of issues) {
try {
const hasDocLabel = issue.labels?.some(l => l.name === '文档');
if (!hasDocLabel) continue;
const comments = await fetchIssueComments(repo, issue.number, token);
const title = issue.title.replace(/[\/\\?%*:|"<>]/g, '-');
const fileName = `${issue.number}.md`;
let content = `---
title: ${issue.title}
---
# ${title}
${issue.body || '无内容'}
## 评论
`;
comments.forEach((c, i) => {
content += `\n### 评论 ${i + 1} - ${c.user.login}\n${c.body}\n---\n`;
});
replaceRules.forEach(({ baseUrl, targetUrl }) => {
const reg = new RegExp(`${baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}(/\\d+)`, 'g');
content = content.replace(reg, `${targetUrl}$1.html`);
});
content = replaceGithubAssetUrls(content, githubProxy);
content += `\n[Issue 链接](${issue.html_url})\n`;
fs.writeFileSync(path.join(issuesDir, fileName), content, 'utf8');
console.log(`✅ 生成:${fileName}`);
} catch (e) {
console.error(`❌ 处理 Issue #${issue.number} 失败:`, e);
ensureIssueFile(issue.number, issuesDir, issue.html_url); // 自动生成占位文件
}
}
console.log('🎉 所有 Issue 文档生成完成(失败项已自动占位)');
} catch (e) {
console.error('💥 整体流程异常:', e);
}
},
};
}
================================================
FILE: docs/CNAME
================================================
xdocs.hanxi.cc
================================================
FILE: docs/index.md
================================================
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
hero:
name: "XiaoMusic"
text: "无限听歌<br>解放小爱音箱"
tagline: 使用小爱音箱播放音乐,音乐使用 yt-dlp 下载
actions:
- theme: brand
text: 快速开始
link: /issues/index
- theme: alt
text: FAQ
link: /issues/99
- theme: alt
text: GitHub
link: https://github.com/hanxi/xiaomusic
features:
- title: MIT 开源
details: 完全开源,自主可控
- title: 一键部署
details: 支持 Docker 部署,兼容各大 NAS 平台
- title: 口令自定义
details: 可以完全自定义语音口令,可以写自己的插件
---
================================================
FILE: docs/issues/101.md
================================================
---
title: 群晖docker安装 xiaomusic
---
# 群晖docker安装 xiaomusic
由于现在群晖已经无法正常下载 docker 里的镜像了,绕了好多弯;现在用 ssh 服务命令拉取镜像来创建容器;
## 1. ssh 输入账号密码进入群晖
## 2. 输入 sudo -i 再次输入密码进入 root 权限
## 3. 输入 docker search xiaomusic来查找到该镜像名;
## 4. 然后输入 docker pull xiaomusic 试试是否能安装,如果不能;就得在命令前加个代理地址;下面列了一些代理地址可以一个个的试
```
docker.fxxk.dedyn.iodocker.io
registry-docker-hub-latest-9vgc.onrender.com
docker.chenby.cn
dockerproxy.com
hub.uuuadc.top
docker.jsdelivr.fyi
docker.registry.cyou
dockerhub.anzu.vip
```
我是用的最下面这个成功的
```
docker pull dockerhub.anzu.vip/hanxi/xiaomusic:latest
```
## 5. 安装完成后就进入群晖 DOCKER 配置 xiaomusic
<img width="491" alt="image" src="https://gproxy.hanxi.cc/proxy/hanxi/xiaomusic/assets/38914725/e318062b-bd70-464c-a8df-8ce3635f2d84">
- MI_HARDWARE=型号 前面第4 步骤获取的
- XIAOMUSIC_SEARCH=搜索方式,我填写的bilisearch: 意思是通过 bilibili 搜索
- MI_DID=前面第4 步骤获取的
- MI_USER=小米账号
- MI_PASS=小米密码
- XIAOMUSIC_FUZZY_MATCH_CUTOFF=模糊匹配,最小为 0.1 最大为 1,越小越模糊,越大越精准
## 6. 配置端口
<img width="757" alt="image (1)" src="https://gproxy.hanxi.cc/proxy/hanxi/xiaomusic/assets/38914725/2b6b9283-296f-4845-a3ff-0ebb11f548b4">
## 7. 映射路径
<img width="737" alt="image (2)" src="https://gproxy.hanxi.cc/proxy/hanxi/xiaomusic/assets/38914725/593718dd-8302-4a69-bec9-36e70f3f0407">
## 评论
### 评论 1 - kiwi5656
MI_DID=前面第4 步骤获取的,第4步骤在哪?
---
### 评论 2 - hanxi
> MI_DID=前面第4 步骤获取的,第4步骤在哪?
不用设置 MI_DID
---
### 评论 3 - hanxi
国内 docker 镜像
```
docker pull m.daocloud.io/docker.io/hanxi/xiaomusic:latest
docker tag m.daocloud.io/docker.io/hanxi/xiaomusic:latest hanxi/xiaomusic:latest
```
---
### 评论 4 - SenyFish
如果手头上有能科学上网的机器,直接把群辉的代理服务器IP填写成可以科学上网的机器IP+端口,翻墙软件打开允许局域网连接就可以
---
### 评论 5 - SmartElec
文档中 `docker pull dockerhub.anzu.vip/xiaomusic:latest`这句命令写错了
修改为
```
docker pull dockerhub.anzu.vip/hanxi/xiaomusic:latest
```
---
### 评论 6 - hanfz123

你好。为什么我这个地方的文字不是中文的?求解
---
### 评论 7 - hanxi
@hanfz123 你是不是屏蔽了谷歌的某些域名?
---
### 评论 8 - hanfz123
> [@hanfz123](https://github.com/hanfz123) 你是不是屏蔽了谷歌的某些域名?
不确定,但是我访问谷歌啥的都正常啊
---
### 评论 9 - 22555642
你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
2025/07/18 17:15:55 | stdout | [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
-- | -- | --
2025/07/18 17:15:55 | stdout | [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:55 | stdout | ==> /app/xiaomusic.log.txt <==
2025/07/18 17:15:55 | stdout |
2025/07/18 17:15:55 | stderr | tail: /app/xiaomusic.log.txt has been replaced; following end of new file
2025/07/18 17:15:52 | stdout | 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
2025/07/18 17:15:48 | stdout | 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
2025/07/18 17:15:48 | stdout | ==> /app/supervisord.log <==
2025/07/18 17:15:48 | stdout |
2025/07/18 17:15:43 | stderr | tail: /app/xiaomusic.log.txt has been replaced; following end of new file
2025/07/18 17:15:43 | stdout | [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:43 | stdout | [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:43 | stdout | ==> /app/xiaomusic.log.txt <==
2025/07/18 17:15:43 | stdout |
2025/07/18 17:15:40 | stdout | 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
2025/07/18 17:15:37 | stdout | 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
2025/07/18 17:15:37 | stdout | ==> /app/supervisord.log <==
2025/07/18 17:15:37 | stdout |
2025/07/18 17:15:33 | stdout | [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:33 | stdout | [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:33 | stdout | ==> /app/xiaomusic.log.txt <==
---
### 评论 10 - hanxi
> 你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
>
> 2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
> 2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
> 2025/07/18 17:15:55 stdout ==> /app/xiaomusic.log.txt <==
> 2025/07/18 17:15:55 stdout
> 2025/07/18 17:15:55 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
> 2025/07/18 17:15:52 stdout 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
> 2025/07/18 17:15:48 stdout 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
> 2025/07/18 17:15:48 stdout ==> /app/supervisord.log <==
> 2025/07/18 17:15:48 stdout
> 2025/07/18 17:15:43 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
> 2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
> 2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
> 2025/07/18 17:15:43 stdout ==> /app/xiaomusic.log.txt <==
> 2025/07/18 17:15:43 stdout
> 2025/07/18 17:15:40 stdout 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
> 2025/07/18 17:15:37 stdout 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
> 2025/07/18 17:15:37 stdout ==> /app/supervisord.log <==
> 2025/07/18 17:15:37 stdout
> 2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
> 2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
> 2025/07/18 17:15:33 stdout ==> /app/xiaomusic.log.txt <==
看不出来为啥
---
### 评论 11 - 22555642
我把日志发给您,您可以帮我看看嘛?
------------------ 原始邮件 ------------------
发件人: "hanxi/xiaomusic" ***@***.***>;
发送时间: 2025年7月19日(星期六) 凌晨1:31
***@***.***>;
***@***.******@***.***>;
主题: Re: [hanxi/xiaomusic] 群晖docker安装 xiaomusic (Issue #101)
hanxi left a comment (hanxi/xiaomusic#101)
你好,我在群辉DOCKER上部署运行后,一直打不开设置页面,点开日志里面写的是这个,请问要怎么办呢?
2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:55 stdout [2025-07-18 17:15:54] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:55 stdout ==> /app/xiaomusic.log.txt <==
2025/07/18 17:15:55 stdout
2025/07/18 17:15:55 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
2025/07/18 17:15:52 stdout 2025-07-18 17:15:51,292 INFO spawned: 'xiaomusic' with pid 21
2025/07/18 17:15:48 stdout 2025-07-18 17:15:47,284 WARN exited: xiaomusic (exit status 1; not expected)
2025/07/18 17:15:48 stdout ==> /app/supervisord.log <==
2025/07/18 17:15:48 stdout
2025/07/18 17:15:43 stderr tail: /app/xiaomusic.log.txt has been replaced; following end of new file
2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:43 stdout [2025-07-18 17:15:42] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:43 stdout ==> /app/xiaomusic.log.txt <==
2025/07/18 17:15:43 stdout
2025/07/18 17:15:40 stdout 2025-07-18 17:15:39,479 INFO spawned: 'xiaomusic' with pid 18
2025/07/18 17:15:37 stdout 2025-07-18 17:15:36,473 WARN exited: xiaomusic (exit status 1; not expected)
2025/07/18 17:15:37 stdout ==> /app/supervisord.log <==
2025/07/18 17:15:37 stdout
2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:33 stdout [2025-07-18 17:15:32] [0.3.83] [INFO] xiaomusic.py:1373: The file conf/setting.json does not exist.
2025/07/18 17:15:33 stdout ==> /app/xiaomusic.log.txt <==
看不出来为啥
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/101)
================================================
FILE: docs/issues/105.md
================================================
---
title: 【插件】自定义口令功能
---
# 【插件】自定义口令功能
自定义口令配置需要配置到 config.json 文件里,使用 config.json 方式启动。参考 </issues/94.html> 。
口令的配置方式见 config-example.json 文件。口令对应的代码需要写到 `plugins/` 目录下面,如果是容器启动,则需要把这个目录挂载出来。
config.json 格式是下面这样的。
```json
{
"hardware": "L07A",
"account": "",
"password": "",
"mi_did": "",
"cookie": "",
"verbose": false,
"music_path": "music",
"conf_path": null,
"hostname": "192.168.2.5",
"port": 8090,
"public_port": 0,
"proxy": null,
"search_prefix": "bilisearch:",
"ffmpeg_location": "./ffmpeg/bin",
"active_cmd": "play,random_play,playlocal,play_music_list,stop",
"exclude_dirs": "@eaDir",
"music_path_depth": 10,
"disable_httpauth": true,
"httpauth_username": "admin",
"httpauth_password": "admin",
"music_list_url": "",
"music_list_json": "",
"disable_download": false,
"key_word_dict": {
"播放歌曲": "play",
"播放本地歌曲": "playlocal",
"关机": "stop",
"下一首": "play_next",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "random_play",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
"set_volume#": "set_volume",
"get_volume#": "get_volume",
"本地播放歌曲": "playlocal",
"放歌曲": "play",
"暂停": "stop",
"停止": "stop",
"停止播放": "stop",
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
},
"key_match_order": [
"set_volume#",
"get_volume#",
"分钟后关机",
"播放歌曲",
"下一首",
"单曲循环",
"全部循环",
"随机播放",
"关机",
"刷新列表",
"播放列表",
"播放本地歌曲",
"本地播放歌曲",
"放歌曲",
"暂停",
"停止",
"停止播放",
"测试自定义口令",
"测试链接"
],
"use_music_api": false,
"use_music_audio_id": "1582971365183456177",
"use_music_id": "355454500",
"log_file": "/tmp/xiaomusic.txt",
"fuzzy_match_cutoff": 0.6,
"enable_fuzzy_match": true,
"stop_tts_msg": "收到,再见",
"keywords_playlocal": "播放本地歌曲,本地播放歌曲",
"keywords_play": "播放歌曲,放歌曲",
"keywords_stop": "关机,暂停,停止,停止播放",
"user_key_word_dict": {
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
}
}
```
配置自定义口令时,只需要配置 user_key_word_dict 即可,会自动插入到 key_word_dict 里的。配置格式是:
```
"测试自定义口令": "exec#code1(\"hello\")",
```
其中 "测试自定义口令" 就是对小爱音箱说的,`"exec#code1(\"hello\")"` 就是要执行的插件代码,代码以 `exec#` 开头,后面紧跟着执行代码。这里 code1 是一个插件函数,插件函数需要在 plugin 目录里实现,一个文件只会导出一个与文件名相同的插件函数。所以 code1 函数是在 plugin/code1.py 里实现的。
```
async def code1(arg1):
global log, xiaomusic
log.info(f"code1:{arg1}")
await xiaomusic.do_tts("你好,我是自定义的测试口令")
```
这里只是演示了打印日志和让小爱音箱说话。还有一个示例插件是 httpget ,可以用来访问 url 。
比如下面这样配置的话,当对小爱音箱说测试链接时,会去访问 url ,可以用来很多其他的事情。
```
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")
```
最后还需要在 `active_cmd` 中配上口令用于唤醒:
```
"active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,测试自定义口令",
```
感兴趣的可以体验一下,写了有什么好玩的插件也可以在这里分享,或者提 pr 合并进官方库里作为自带插件。
## 评论
### 评论 1 - carson512
如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?
---
### 评论 2 - hanxi
> 如果开启服务的状态下 如何唤醒才能调用原有的播放QQ 网易云?而特定唤醒词调用xiaoai播放?
不使用 xiaomusic 的唤醒词就会调用音箱自带的,比如说播放音乐
---
### 评论 3 - shellingford37
```
[23:26:12] [0.3.30] [INFO] xiaomusic.py:531: 收到消息:测试自定义口令 控制面板:False did:290874427
[23:26:12] [0.3.30] [INFO] xiaomusic.py:577: 完全匹配指令. query:测试自定义口令 opvalue:exec#code1("hello")
[23:26:12] [0.3.30] [INFO] code1.py:3: code1:hello
[23:26:12] [0.3.30] [ERROR] xiaomusic.py:542: Execption XiaoMusic.do_tts() missing 1 required positional argument: 'value'
Traceback (most recent call last):
File "/app/xiaomusic/xiaomusic.py", line 540, in do_check_cmd
await func(did=did, arg1=oparg)
File "/app/xiaomusic/xiaomusic.py", line 890, in exec
await self.plugin_manager.execute_plugin(code)
File "/app/xiaomusic/plugin.py", line 66, in execute_plugin
await coroutine
File "/app/plugins/code1.py", line 4, in code1
await xiaomusic.do_tts("你好,我是自定义的测试口令")
TypeError: XiaoMusic.do_tts() missing 1 required positional argument: 'value'
```
我用code1的代码执行报错,有大佬知道为什么吗?
---
### 评论 4 - hanxi
@shellingford37 重构后漏改了,修复了。
---
### 评论 5 - guoxiangke
先说播放歌曲,再说 测试自定义口令 就行
---
### 评论 6 - CZJCC
想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里
---
### 评论 7 - hanxi
> 想请教下插件那个功能,如何把用户的语音输入作为参数内容传到自定义函数里
现在获取不到,等我加个接口获取吧。
---
### 评论 8 - CZJCC
666,支持以后我可以贡献一个接入通义模型的插件
---
### 评论 9 - hanxi
@CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。
---
### 评论 10 - hanxi
文档更新了下,active_cmd 也需要配置一下才能正常唤醒。
---
### 评论 11 - CZJCC
> @CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。
我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的
---
### 评论 12 - hanxi
是的,插件函数里面再切割一下前缀就行。last_record就是当前的那条语音数据。
---
### 评论 13 - hanxi
> > @CZJCC 你可以更新看看 plugins/code1.py 的测试代码,我测试了是可以拿到语音输入的原始内容的。
>
> 我原先设想的事用户的话术是”通义提问为什么地球是圆的“,指令匹配的时候通义提问前缀匹配到类似于code1方法,为什么地球是圆的作为参数传入这个函数,但我看现在是拿历史记录实现的
是的,这样比较简单,交给插件里面处理也比较自由。
---
### 评论 14 - mogeqian
key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。
当我先按照config-example.json的模板写好如下配置并重命名为config.json
```
"key_word_dict": {
"查找歌曲": "play",
"播放本地歌曲": "playlocal",
"关机": "stop",
"下一首": "play_next",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "set_random_play",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
"本地播放歌曲": "playlocal",
"下载歌曲": "play",
"暂停": "stop",
"停止": "stop",
"停止播放": "stop",
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
},
"key_match_order": [
"分钟后关机",
"查找歌曲",
"下一首",
"单曲循环",
"全部循环",
"随机播放",
"关机",
"刷新列表",
"播放列表",
"播放本地歌曲",
"本地播放歌曲",
"下载歌曲",
"暂停",
"停止",
"停止播放",
"测试自定义口令",
"测试链接"
],
```
用以下命令安装docker
`docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json`
日志里提示的依然是:
` key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']`
似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令
---
### 评论 15 - hanxi
> key_word_dict中的“播放歌曲”口令是不能修改的是吧?因为以前用小爱同学播放歌曲说习惯了,总是触发xiaomusic自动下载歌曲,我想把口令改成“查找歌曲”,这样我当说播放歌曲的时候就调用网易云音乐或者QQ音乐,当我说查找歌曲的时候就先看本地有没有歌曲,没有话就自动下载到本地。 当我先按照config-example.json的模板写好如下配置并重命名为config.json
>
> ```
> "key_word_dict": {
> "查找歌曲": "play",
> "播放本地歌曲": "playlocal",
> "关机": "stop",
> "下一首": "play_next",
> "单曲循环": "set_play_type_one",
> "全部循环": "set_play_type_all",
> "随机播放": "set_random_play",
> "分钟后关机": "stop_after_minute",
> "播放列表": "play_music_list",
> "刷新列表": "gen_music_list",
> "本地播放歌曲": "playlocal",
> "下载歌曲": "play",
> "暂停": "stop",
> "停止": "stop",
> "停止播放": "stop",
> "测试自定义口令": "exec#code1(\"hello\")",
> "测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
> },
> "key_match_order": [
> "分钟后关机",
> "查找歌曲",
> "下一首",
> "单曲循环",
> "全部循环",
> "随机播放",
> "关机",
> "刷新列表",
> "播放列表",
> "播放本地歌曲",
> "本地播放歌曲",
> "下载歌曲",
> "暂停",
> "停止",
> "停止播放",
> "测试自定义口令",
> "测试链接"
> ],
> ```
>
> 用以下命令安装docker `docker run --name xiaomusic -p 5488:5488 -v /mnt/sharedata/audiodata/musci/xiaomusic:/app/music -v /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json -e XIAOMUSIC_PORT=5488 hanxi/xiaomusic --config /app/config.json` 日志里提示的依然是:
>
> ` key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接']`
>
> 似乎自定义的口令只能以插入的方式添加上去,并不能替换掉原来的口令
可以在网页后台设置页面改。
---
### 评论 16 - mogeqian
不行,后台设置如图

日志如下:
```
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1130: update_config_from_setting ok. data:Config(account='**', password='**', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', search_prefix='bilisearch:', ffmpeg_location='./ffmpeg/bin', active_cmd='play,set_random_play,playlocal,play_music_list,stop', exclude_dirs='@eaDir', music_path_depth=10, disable_httpauth=True, httpauth_username='******', httpauth_password='******', music_list_url='', music_list_json='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接'], use_music_api=False, use_music_audio_id='1582971365183456177', use_music_id='355454500', log_file='/tmp/xiaomusic.txt', fuzzy_match_cutoff=0.6, enable_fuzzy_match=True, stop_tts_msg='收到,再见', enable_config_example=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1133: 语音控制已启动, 用【分钟后关机/播放歌曲/下一首/上一首/单曲循环/全部循环/随机播放/关机/刷新列表/播放列表第/播放列表/加入收藏/收藏歌曲/取消收藏/播放本地歌曲/本地播放歌曲/查找歌曲/下载歌曲/暂停/停止/停止播放/播放歌单/测试自定义口令/测试链接】开头来控制
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:543: 协程时间循环未启动
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:1250: 没打乱 全部 ['乌兰托娅 火红的萨日朗', '凤凰传奇麝香夫人'] ... ['罗大佑童年', '阿嬷'] with len: 7
[2024-11-11 18:08:04] [0.3.46] [INFO] analytics.py:28: analytics init ok
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:104: Startup OK. Config(account='***', password='***', mi_did='726577518,570867755', miio_tts_command='', cookie='', verbose=False, music_path='music', download_path='music/download', conf_path='conf', cache_dir='cache', hostname='192.168.22.4', port=8090, public_port=0, proxy='', search_prefix='bilisearch:', ffmpeg_location='./ffmpeg/bin', active_cmd='play,set_random_play,playlocal,play_music_list,stop', exclude_dirs='@eaDir', music_path_depth=10, disable_httpauth=True, httpauth_username='******', httpauth_password='******', music_list_url='', music_list_json='', custom_play_list_json='', disable_download=False, key_word_dict={'播放歌曲': 'play', '播放本地歌曲': 'playlocal', '关机': 'stop', '下一首': 'play_next', '上一首': 'play_prev', '单曲循环': 'set_play_type_one', '全部循环': 'set_play_type_all', '随机播放': 'set_random_play', '分钟后关机': 'stop_after_minute', '播放列表': 'play_music_list', '刷新列表': 'gen_music_list', '加入收藏': 'add_to_favorites', '收藏歌曲': 'add_to_favorites', '取消收藏': 'del_from_favorites', '播放列表第': 'play_music_list_index', '本地播放歌曲': 'playlocal', '查找歌曲': 'play', '下载歌曲': 'play', '暂停': 'stop', '停止': 'stop', '停止播放': 'stop', '播放歌单': 'play_music_list', '测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, key_match_order=['分钟后关机', '播放歌曲', '下一首', '上一首', '单曲循环', '全部循环', '随机播放', '关机', '刷新列表', '播放列表第', '播放列表', '加入收藏', '收藏歌曲', '取消收藏', '播放本地歌曲', '本地播放歌曲', '查找歌曲', '下载歌曲', '暂停', '停止', '停止播放', '播放歌单', '测试自定义口令', '测试链接'], use_music_api=False, use_music_audio_id='1582971365183456177', use_music_id='355454500', log_file='/tmp/xiaomusic.txt', fuzzy_match_cutoff=0.6, enable_fuzzy_match=True, stop_tts_msg='收到,再见', enable_config_example=False, keywords_playlocal='播放本地歌曲,本地播放歌曲', keywords_play='查找歌曲,下载歌曲', keywords_stop='关机,暂停,停止,停止播放', keywords_playlist='播放列表,播放歌单', user_key_word_dict={'测试自定义口令': 'exec#code1("hello")', '测试链接': 'exec#httpget("https://github.com/hanxi/xiaomusic")'}, enable_force_stop=False, devices={'726577518': Device(did='726577518', device_id='*****', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}, group_list='', remove_id3tag=False, convert_to_mp3=False, delay_sec=3, continue_play=False, pull_ask_sec=1, crontab_json='', enable_yt_dlp_cookies=False, get_ask_by_mina=False)
[2024-11-11 18:08:04] [0.3.46] [INFO] httpserver.py:111: disable_httpauth:True
[18:08:04] [0.3.46] [INFO] Started server process [1]
[18:08:04] [0.3.46] [INFO] Waiting for application startup.
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:541: 启动后台构建 tag cache
[18:08:04] [0.3.46] [INFO] Application startup complete.
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:513: 已从【cache/tag_cache.json】加载 tag cache
[18:08:04] [0.3.46] [INFO] Uvicorn running on http://['0.0.0.0', '::']:8090 (Press CTRL+C to quit)
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:527: 保存:tag cache 已保存到【cache/tag_cache.json】
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:577: tag 更新完成
[2024-11-11 18:08:04] [0.3.46] [INFO] xiaomusic.py:248: 选中的设备: {'726577518': Device(did='726577518', device_id='******', hardware='LX06', name='小爱音箱Pro', play_type='', cur_music='', cur_playlist='全部'), '570867755': Device(did='570867755', device_id='*********', hardware='L15A', name='小米AI音箱(第二代)', play_type='', cur_music='', cur_playlist='全部')}
[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /static/default/setting.html HTTP/1.1" 304
[18:08:34] [0.3.46] [INFO] 172.20.0.1:35058 - "GET /getversion HTTP/1.1" 200
```
使用的docker-compose命令安装
services:
xiaomusic:
image: hanxi/xiaomusic
container_name: xiaomusic
restart: unless-stopped
ports:
- 8090:8090
volumes:
- /mnt/sharedata/audiodata/musci/xiaomusic:/app/music
- /mnt/data_sdb1/docker/xiaomusic/config.json:/app/config.json
command: ['--config', '/app/config.json']
根据日志的提示,'播放歌曲': 'play'依然存在,只是增加了 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令,所以实际上play有三条口令 “播放歌曲、查找歌曲、下载歌曲”,能否删除掉'播放歌曲': 'play'这个系统默认的口令?只使用 '查找歌曲': 'play', '下载歌曲': 'play', 这两个关于play的自定义口令
---
### 评论 17 - hanxi
@mogeqian 另外提个 issue 吧,现在应该是不支持删除默认的口令。
---
### 评论 18 - mogeqian
好的,已经重开了一个issue #259
---
### 评论 19 - wjcroom
@hanxi 你好大神 , 自定义口令基本搞定了,还有些不确定对不对
在项目命令中只有停止,没有继续.
能把stop.做成, 乒乓吗.继续. 暂停的切换.pause play
"active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,测试自定义口令",
这个激活cmd是什么作用呢, 怎么看着默认的是函数,只有最后一个是中文呢?
已经把自定义3thplay音量加入了. 其他的大点声小点声, 大点音,小点音.,还有好多.
好的好像是激活xiaomusic的 但是为啥前面的是单词?
config.json, 已经被setting.json替换了的.
```
async def set_myvolume(self, did="", arg1=0, **kwargs):
if did not in self.devices:
self.log.info(f"设备 did:{did} 不存在, 不能设置音量")
return
if arg1=="up":
await self.devices[did].thdplay('up')
elif arg1=="down":
await self.devices[did].thdplay('dow')
else:
volume = chinese_to_number(arg1)
await self.devices[did].thdplay('volume',str(volume))
```
---
### 评论 20 - hanxi
激活cmd支持配置 key 和 value,用于决定是否能在 xiaomusic 不播放的时候能否唤醒 xiaomusic 。
config.json 是基本废弃了,作为一个初始化配置存在。
暂停不方便做,没有好的接口控制音箱暂停,所以就没加这个口令。
---
### 评论 21 - wjcroom
> 激活cmd支持配置 key 和 value,用于决定是否能在 xiaomusic 不播放的时候能否唤醒 xiaomusic 。
>
> config.json 是基本废弃了,作为一个初始化配置存在。
>
> 暂停不方便做,没有好的接口控制音箱暂停,所以就没加这个口令。
1.现在有了停止,我觉得继续可以是重启最近一首歌,在停止前把正在播放存放在某处。存 stop 里的name可以吗。感觉会有重名呢?
2. 有没有可能让小爱在技能中心增加自己的【小音乐】技能,进入xiaomusic对话模式,直到退出。这样不再互相干扰。
---
### 评论 22 - hanxi
1. 目前会记录最后一次播放的歌曲,正常说播放歌曲,不带歌曲名字应该就会重头播放的。
2. 对话模式有些设备不支持,实现也比较复杂,xiaogpt那些项目是支持对话模式的,我就懒得支持了。
---
### 评论 23 - thefreezoo
我想写个可以读小说的,现在就是可以读不能关闭了,只能一次读完,我本想重新发送一条关闭命令,上一个自定义命令没有结束,似乎不能接受新的命令,包括关机,有什么语音控制接口可以关闭这个运行中的任务吗
---
### 评论 24 - hanxi
> 我想写个可以读小说的,现在就是可以读不能关闭了,只能一次读完,我本想重新发送一条关闭命令,上一个自定义命令没有结束,似乎不能接受新的命令,包括关机,有什么语音控制接口可以关闭这个运行中的任务吗
可以看看你是怎么写的?估计得用 asyncio 异步的写法。
---
### 评论 25 - thefreezoo
> > 我想写个可以读小说的,现在就是可以读不能关闭了,只能一次读完,我本想重新发送一条关闭命令,上一个自定义命令没有结束,似乎不能接受新的命令,包括关机,有什么语音控制接口可以关闭这个运行中的任务吗
>
> 可以看看你是怎么写的?估计得用 asyncio 异步的写法。
python我用的不很熟,基本逻辑都是用服务器实现的,好像python不能直接这样开启异步线程:import json
import threading
import requests
import asyncio
async def getbook(xiaomusic):
# global log, xiaomusic
offset = '0'
did = xiaomusic._cur_did
requests.get("http://192.168.1.16:8001/note/off?off=N",timeout=100)
while True:
url="http://192.168.1.16:8001/note/list?name=test&offset=" + offset
try:
response=requests.get(url,timeout=100)
if response.status_code==200:
print(response.text)
res = json.loads(response.text)
await xiaomusic.do_tts(did, res['data'])
offset=str(res["offset"])
if res['off'] == 1:
break
else:
print(f"no 200")
break
except requests.exceptions.RequestException as e:
print(f"{e}")
break
def read():
global log, xiaomusic
loop=asyncio.get_event_loop()
loop.run_until_complete(getbook(xiaomusic))
---
### 评论 26 - hanxi
@thefreezoo 你用了个死循环,把整个服务卡住了。你可以在死循环里加个 asyncio.sleep ,然后再写一个自定义口令杀掉你的这个 task 。
---
### 评论 27 - thefreezoo
> [@thefreezoo](https://github.com/thefreezoo) 你用了个死循环,把整个服务卡住了。你可以在死循环里加个 asyncio.sleep ,然后再写一个自定义口令杀掉你的这个 task 。
似乎还是不行,不能识别其他口令
import json
import threading
import requests
import asyncio
async def read():
global log, xiaomusic
offset = '0'
did = xiaomusic._cur_did
requests.get("http://192.168.1.16:8001/note/off?off=N",timeout=100)
while True:
url="http://192.168.1.16:8001/note/list?name=test&offset=" + offset
try:
response=requests.get(url,timeout=100)
if response.status_code==200:
print(response.text)
res = json.loads(response.text)
await xiaomusic.do_tts(did, res['data'])
offset=str(res["offset"])
if res['off'] == 1:
break
else:
print(f"no 200")
break
except requests.exceptions.RequestException as e:
print(f"{e}")
break
await asyncio.sleep(1)
---
### 评论 28 - hanxi
要不你问问ai吧,你缺少一个自定义指令干掉你这个死循环。
---
### 评论 29 - thefreezoo
> 要不你问问ai吧,你缺少一个自定义指令干掉你这个死循环。
插件是同步调用的,需要把插件包装成任务放回当前事件循环,我把plugins.py异步调用那里直接改成在事件循环中执行,然后放回running_task,stop的时候在终止任务,这样就可以用关机命令了
---
### 评论 30 - hu847266
能不能实现播放玩一个歌曲自动暂停
---
### 评论 31 - hanxi
> 能不能实现播放玩一个歌曲自动暂停
设置为单曲播放模式就行。
---
### 评论 32 - hu847266
问一下这个如果设置了离家后停止播放音乐,改了这个还能实现吗
---
### 评论 33 - hu847266
用服务器配置,容器端口和ip填什么,ip填公网ip吗
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/105)
================================================
FILE: docs/issues/182.md
================================================
---
title: 定时任务配置格式
---
# 定时任务配置格式
支持采用 crontab 的格式配置定时任务,已经支持下面的任务类型:
- stop 关机
- play 播放歌曲
- play_music_list 播放列表
- tts 文字转语音
- refresh_music_list 刷新播放列表
- set_volume 设置音量
- set_play_type 设置播放类型,单曲循环 0 , 全部循环 1 , 随机播放 2 , 单曲播放 3 , 顺序播放 4
- set_pull_ask 设置是否拉取对话记录,每天定时关闭,可缓解风控问题
- reinit 重新初始化,每天执行一次可缓解登录失效问题
- play_music_tmp_list 播放自定义列表任务,将多个歌曲组成一个临时歌单播放
### 示例
```json
[
{
"expression": "0 8 * * 0-4",
"name": "play",
"did": "123456789",
"arg1": "周杰伦晴天"
},
{
"expression": "10 8 * * 0-4",
"name": "stop",
"did": "123456789"
},
{
"expression": "0 9 * * *",
"name": "play",
"did": "123456789",
"arg1": "周杰伦晴天"
},
{
"expression": "0 10 * * *",
"name": "play_music_list",
"did": "123456789",
"arg1": "周杰伦"
},
{
"expression": "30 10 * * *",
"name": "play_music_list",
"did": "123456789",
"arg1": "周杰伦|晴天"
},
{
"expression": "0 7 * * *",
"name": "tts",
"did": "123456789",
"arg1": "早上好!该起床了!"
},
{
"expression": "0 3 * * *",
"name": "refresh_music_list"
},
{
"expression": "* * * * *",
"name": "set_volume",
"did": "123456789",
"arg1": "25"
},
{
"expression": "* * * * *",
"name": "set_play_type",
"did": "123456789",
"arg1": "2"
},
{
"expression": "0 6 * * *",
"name": "set_pull_ask",
"arg1": "enable"
},
{
"expression": "0 0 * * *",
"name": "set_pull_ask",
"arg1": "disable"
},
{
"expression": "0 1 * * *",
"name": "reinit"
},
{
"expression": "33 20 * * *",
"name": "play_music_tmp_list",
"did": "978479727",
"arg1": "临时列表1",
"music_list": [
"1大青树下的小学",
"7听听秋的声音",
"11宝葫芦的秘密"
],
"first": "11宝葫芦的秘密"
}
]
```
示例中的意思是:
- 周一到周五每天 8 点播放歌曲 "周杰伦晴天"
- 周一到周五每天 8 点 10 分执行关机指令
- 每天 9 点播放歌曲 "周杰伦晴天"
- 每天 10 点播放列表 "周杰伦"
- 每天 10 点 30 分播放列表 "周杰伦" 里的 "晴天"
- 每天 7 点发出语音 "早上好!该起床了!"
- 每天 3 点刷新播放列表,用于自动更新目录下的歌曲到播放列表里。
- 每分钟设置音量为 25
- 每分钟设置为随机播放
- 每天早上6点开启拉取对话记录
- 每天晚上12点关闭拉取对话记录
- 每天1点重新初始化
- 晚上8点33将3个歌曲组成名为【临时列表1】的歌单并从【11宝葫芦的秘密】开始播放,`arg1` 可选,`first` 可选
> 注意星期一是0,星期二是1,星期日是6。
> (0-6 or mon,tue,wed,thu,fri,sat,sun)
> The first weekday is always monday.
### 参数意思
- expression 的格式是标准的 crontab 的格式,用于配置任务的执行时机,如何配置可以直接用下面的 crontab ai 工具生成
- <https://cronly.app/ai>
- <https://cronify.zimo.li/>
- name 是任务名,目前只支持上面那几种。
- did 是小爱音箱的设备ID,就是设置页面的音箱型号后面的那串数字。
- arg1 根据任务不同而不同。
- play 的 arg1 表示要播放的歌曲名。
- play_music_list 的 arg1 表示要播放的播放目录名,可以加 `|` 符合加上目录下面的歌曲名,也可不加。
- tts 的 arg1 表示要说的语音文字。
可以去 <https://www.json.cn/> 里检查 json 配置是否合法。
## 评论
### 评论 1 - hanxi
0.3.38版本功能。
---
### 评论 2 - F-loat
<img src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/9e562aea-423e-479d-bf0c-9932b330cf98" width="25%" />
小程序已支持可视化配置定时任务
---
### 评论 3 - hanxi
有想法增加一个参数来决定法定工作日,法定节假日执行。
---
### 评论 4 - hanxi
计划这样配工作日或者休息日,末尾加上 ` #workday` 表示工作日才执行,末尾加上 ` #offday` 表示休息日才执行。
```json
[
{
"expression": "0 8 * * * #workday",
"name": "play",
"did": "123456789",
"arg1": "周杰伦晴天"
},
{
"expression": "0 10 * * * #offday",
"name": "play",
"did": "123456789",
"arg1": "周杰伦晴天"
}
]
```
---
### 评论 5 - sycflash
想问下这个怎么配置?修改哪个配置文件?
---
### 评论 6 - xcysy32
蹲,请问要定时从歌单地址获取歌单列表json并更新应该怎么写
---
### 评论 7 - fc1250
定时播放失效了
---
### 评论 8 - dumashsu
超小白請問
範例做好了, 要放在那個檔案內 才會生效
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/182)
================================================
FILE: docs/issues/19.md
================================================
---
title: 如何修改默认的8090端口
---
# 如何修改默认的8090端口
docker-compose 修改映射端口会播放失败
```
ports:
- 80:8090
```
从日志看继续调用了 http://10.0.0.4:8090 而不是修改映射的80,还原成
```
ports:
- 8090:8090
```
则一切正常
```
xiaomusic | [BiliBiliSearch] Playlist 安河桥北: Downloading 1 items of 1
xiaomusic | [download] Downloading item 1 of 1
xiaomusic | [BiliBili] Extracting URL: http://www.bilibili.com/video/av319943893
xiaomusic | [BiliBili] 319943893: Downloading webpage
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | [BiliBili] BV1tw411X7Rr: Extracting videos in anthology
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | [BiliBili] 319943893: Extracting chapters
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | [BiliBili] Format(s) 1080P 高码率, 1080P 高清, 720P 高清, 4K 超清 are missing; you have to login or become premium member to download them. Use --cookies-from-browser or --cookies for the authentication. See https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp for how to manually pass cookies
xiaomusic | [info] BV1tw411X7Rr: Downloading 1 format(s): 30280
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | 10.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
xiaomusic | [download] Destination: music/安河桥北.m4a
[download] 0.3% of 5.61MiB at 6.24MiB/s ETA 00:0010.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
[download] 0.5% of 5.61MiB at 2.42MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
[download] 1.1% of 5.61MiB at 1.50MiB/s ETA 00:0310.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
[download] 4.4% of 5.61MiB at 2.35MiB/s ETA 00:0210.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
[download] 8.9% of 5.61MiB at 3.59MiB/s ETA 00:0110.0.0.118 - - [21/Feb/2024 15:29:24] "POST /cmd HTTP/1.1" 200 -
[download] 100% of 5.61MiB in 00:00:00 at 10.83MiB/s
xiaomusic | [ExtractAudio] Destination: music/安河桥北.mp3
xiaomusic | Deleting original file music/安河桥北.m4a (pass -k to keep)
xiaomusic | [download] Finished downloading playlist: 安河桥北
xiaomusic | [02/21/24 15:29:29] INFO 播放 xiaomusic.py:461
xiaomusic | http://10.0.0.4:8090/music/%E5%AE%
xiaomusic | 89%E6%B2%B3%E6%A1%A5%E5%8C%97.mp3
xiaomusic | INFO 已经开始播放了 xiaomusic.py:464
xiaomusic | INFO 歌曲music/安河桥北.mp3的时长251秒 xiaomusic.py:371
xiaomusic | INFO 251秒后将会播放下一首 xiaomusic.py:385
xiaomusic | INFO 匹配到指令. opkey:set_volume# xiaomusic.py:441
xiaomusic | opvalue:set_volume oparg:24
```
## 评论
### 评论 1 - hanxi
需要添加环境变量
```
environment:
XIAOMUSIC_PORT:80
ports:
- 80:80
```
---
### 评论 2 - newrookie001
> 需要添加环境变量
>
> ```
> environment:
> XIAOMUSIC_PORT:80
> ports:
> - 80:80
> ```
自己走了点弯路,半天才搞明白。补充说明:
> environment:
> XIAOMUSIC_PORT: 5678 #就是“5678”可以根据自己要求设置,但要求上下的5678都设置成一个
> ports:
> - 5678:5678
---
### 评论 3 - hanxi
如果换端口,需要3个数字一致,比如
```
environment:
XIAOMUSIC_PORT:6874
ports:
- 6874:6874
```
---
### 评论 4 - hanxi
文档类型的我都打开下,方便其他人看到。
---
### 评论 5 - flymin
docker-compose 中对应关系应该是
```yaml
ports:
- aaaa:bbbb
environment:
XIAOMUSIC_PORT: bbbb # 配置文件中的 port,后台:监听端口(修改后需要重启)
XIAOMUSIC_PUBLIC_PORT: aaaa # 配置文件中的 public_port,后台:外网访问端口(0表示跟监听端口一致)
```
以上,docker 环境中基本不存在需要修改 bbbb 的情况,也就是不用设置 XIAOMUSIC_PORT。如果需要修改端口,只需要修改两处 aaaa
如果使用反向代理,则转发 localhost:aaaa,XIAOMUSIC_PUBLIC_PORT 设置成代理的监听端口 cccc
另外,setting 文件存在会覆盖环境变量。启动过之后需要直接修改 settings.json 或者在后台修改
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/19)
================================================
FILE: docs/issues/210.md
================================================
---
title: yt-dlp cookies 文件上传功能
---
# yt-dlp cookies 文件上传功能
此功能用于解决 yt-dlp 下载资源失败时使用,比如 **ip 被 B站或者 youtube 加入黑名单**后才需要使用。
上传的文件用于 yt-dlp 的 `--cookies` 参数。
```
--cookies FILE Netscape formatted file to read cookies from
and dump cookie jar in
```
## 获取 cookies.txt 文件
1. 下载插件 [Get cookies.txt LOCALLY](https://chromewebstore.google.com/detail/cclelndahbckbenkjhflpdbgdldlbecc)
2. 给予插件访问权限和无痕模式允许使用

3. 打开无痕窗口
4. 打开 youtube.com
5. 登陆 youtube.com
6. 打开新标签页
7. 关闭 youtube.com 的标签页
8. 保存 cookies.txt

原因见 https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies
## 上传 cookies.txt
1. 打开设置页面
2. 设置启用yt-dlp-cookies 选项为 true

3. 点击保存
4. 再点击选择文件,选择前面保存好的 cookies.txt 文件,点击上传。

## 后续用途
1. 语音下载歌曲都会带上前面上传的 cookies.txt 文件去搜索下载歌曲。
2. 歌曲批量下载工具下载歌曲或者歌单时也会带上 cookies.txt 文件。
## 评论
### 评论 1 - kingfly2016
0.3.37的版本并没有发现可以开启yt-dlp-cookies 并上传cookies文件的地方,尝试把导出的cookies.txt手工上传到conf目录下,没有生效.

---
### 评论 2 - hanxi
需要等38版本,或者用测试版本,镜像名后面加 :main
---
### 评论 3 - kingfly2016
> 需要等38版本,或者用测试版本,镜像名后面加 :main
谢谢
---
### 评论 4 - sunmiao0301
20250117实测可用
---
### 评论 5 - Lonely-Sit
> 20250117实测可用
佬你今天还可以使用吗?我按照教程操作了但是下载不下来歌曲
---
### 评论 6 - sunmiao0301
> > 20250117实测可用
>
> 佬你今天还可以使用吗?我按照教程操作了但是下载不下来歌曲
用cookie的方式可能会被封号,ytb限制的是ip,更好的办法是,切换一下梯子节点,多试几个。
---
### 评论 7 - Lonely-Sit
> > > 20250117实测可用
> >
> >
> > 大佬你今天还可以用吗?我按照教程操作了但是下载不下来歌曲
>
> 用cookie限制的方式可能会被封号,ytb是ip,更好的办法是,切换一下梯子节点,多试几个。
我使用相同ip在电脑端下载歌曲就没有问题 我无法理解
---
### 评论 8 - wyyang1978-debug
现在的提示显示formats缺失
stdout: [youtube] Extracting URL: https://www.youtube.com/watch?v=KqjgLbKZ1h0
stdout: [youtube] KqjgLbKZ1h0: Downloading webpage
stdout: [youtube] KqjgLbKZ1h0: Downloading tv downgraded player API JSON
stdout: [youtube] KqjgLbKZ1h0: Downloading web safari player API JSON
stderr: WARNING: [youtube] KqjgLbKZ1h0: Signature solving failed: Some formats may be missing. Ensure you have a supported JavaScript runtime and challenge solver script distribution installed. Review any warnings presented before this message. For more details, refer to https://github.com/yt-dlp/yt-dlp/wiki/EJS
stderr: WARNING: [youtube] KqjgLbKZ1h0: n challenge solving failed: Some formats may be missing. Ensure you have a supported JavaScript runtime and challenge solver script distribution installed. Review any warnings presented before this message. For more details, refer to https://github.com/yt-dlp/yt-dlp/wiki/EJS
---
### 评论 9 - hanxi
没办法的,yt限制越来越厉害了
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/210)
================================================
FILE: docs/issues/211.md
================================================
---
title: 📝 文档汇总
---
# 📝 文档汇总
## 1️⃣ 基础文档
- [💬 FAQ问题集合](/issues/99.html)
- [如何修改默认的8090端口](/issues/19.html)
- [如何配置网络歌单](/issues/78.html)
- [如何添加m3u格式文件的电台](/issues/88.html)
- [xiaomusic极空间安装教程](/issues/297.html)
- [docker compose 命令行安装教程 ](/issues/360.html)
- [1Panel 安装运行 xiaomusic 教程](/issues/600.html)
- [使用cookie登陆](/issues/688.html)
## 2️⃣ 进阶文档
- [设置项功能介绍](/issues/333.html)
- [采用config.json配置方式](/issues/94.html)
- [ios系统上的捷径配置](/issues/96.html)
- [【插件】自定义口令功能](/issues/105.html)
- [定时任务配置格式](/issues/182.html)
- [yt-dlp cookies 文件上传功能](/issues/210.html)
- [如何批量下载歌曲](/issues/212.html)
- [设备分组播放](/issues/65.html#issuecomment-2215736529)
- [如何播放小雅alist里的歌曲](/issues/128.html#issuecomment-2232867180)
- [如何添加 网易云音乐playlist](/issues/269.html)
- [相关工具推荐](/issues/285.html)
## 3️⃣ 其他安装文档
> [!NOTE]
> 下面教程可能比较旧,只供参考
- [群晖docker安装 xiaomusic](/issues/101.html)
- [NAS部署教程](https://post.m.smzdm.com/p/avpe7n99/)
- [群晖部署教程](https://post.m.smzdm.com/p/a7px7dol/)
- [QNAS部署教程](https://post.smzdm.com/p/a5xz5x63/)
- [视频教程-群晖1](https://www.bilibili.com/video/BV1ZZpweHEtT/)
- [视频教程-群晖2](https://www.bilibili.com/video/BV1JXxXeBEdY/)
- [视频教程-拾光坞N3](https://www.bilibili.com/video/BV1q629YMEG6/)
- [TechHive](https://mp.weixin.qq.com/s/4a41muFtPaFKtHeZYu795w)
- [弹个AI](https://mp.weixin.qq.com/s/sIsKxB7Y8b83AhnvaWiMog)
- [简单免费!教你用绿联NAS联动小爱音箱,私人音乐库也能语音点播](https://post.m.smzdm.com/p/a8pldgg7/)
- [飞牛教程](https://mp.weixin.qq.com/s?t=pages/image_detail&__biz=MzkxODc1NDMwOA==&mid=2247483725&idx=1&sn=2d615f14733b9bf989557fa766b4e1fc)
## 评论
### 评论 1 - sghuenn
redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放
打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。
---
### 评论 2 - hanxi
> redmi小爱触屏音箱8,仍然需要打开“型号兼容模式”才能播放 打开兼容模式后的问题有:每首歌曲播放完毕后都要再从头播放4、5秒才播放下一曲;语音命令小爱同学播放下一曲,它会从头开始播放当前歌曲。
播放歌曲的接口应该是有点问题,等有设备有开发能力的人来搞吧。
---
### 评论 3 - zhoukk37
想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗
---
### 评论 4 - hanxi
> 想问下如何利用反向代理,完成使得小爱外网访问nas呢,您能提供一下关键词,我自己去检索学下一下吗
内网穿透,frp能实现,就是把局域网的端口映射成公网的端口。
---
### 评论 5 - Justlook99
按照飞牛的教程,部署成功了,一直没有设备显示出来,然后我也按照相应的问题集去处理:关闭本地代理。
如果是nas运行的,网络由bridge改为host。
米家app重新登陆。
mi.com官网重新登陆。
但是还是没有办法显示设备出来,请问到底是什么原因?最新的37版本。
---
### 评论 6 - hanxi
> 按照飞牛的教程,部署成功了,一直没有设备显示出来,然后我也按照相应的问题集去处理:关闭本地代理。 如果是nas运行的,网络由bridge改为host。 米家app重新登陆。 mi.com官网重新登陆。 但是还是没有办法显示设备出来,请问到底是什么原因?最新的37版本。
目前反馈的都是飞牛的用户,可能是飞牛有问题。
---
### 评论 7 - 3794313569
在同一个容器内,前后分别启动了mi-gpt和xiaomusic两个应用,现在通过日志发现,mi-gpt的日志一直在记录,语音需求基本都在mi-gpt这个应用响应了,请问下按照您现在设计的框架内,有没有办法可以实现这两个应用同时生效,或者稍后类似应用会有专用的通讯协议,保证多项应用在同一台机器上的响应。
类似:语音命令-“播放本地歌曲”触发xiaomusic,“召唤”(mi-gpt配置的唤醒词)触发mi-gpt,等等。。。。。。
暂时的办法就是买两个小爱音箱,不同的命名,然后一个应用配置一个did。
---
### 评论 8 - hanxi
> 在同一个容器内,前后分别启动了mi-gpt和xiaomusic两个应用,现在通过日志发现,mi-gpt的日志一直在记录,语音需求基本都在mi-gpt这个应用响应了,请问下按照您现在设计的框架内,有没有办法可以实现这两个应用同时生效,或者稍后类似应用会有专用的通讯协议,保证多项应用在同一台机器上的响应。 类似:语音命令-“播放本地歌曲”触发xiaomusic,“召唤”(mi-gpt配置的唤醒词)触发mi-gpt,等等。。。。。。 暂时的办法就是买两个小爱音箱,不同的命名,然后一个应用配置一个did。
可以分别部署到两个不同的容器里,两个应用的唤醒词是不同的,不会互相干扰。
---
### 评论 9 - Tranceboox
如果网页端主页内能显示播放曲目的封面就太牛了,我知道实现起来很难,就是臆想一下
---
### 评论 10 - hanxi
> 如果网页端主页内能显示播放曲目的封面就太牛了,我知道实现起来很难,就是臆想一下
xplayer 和 pure 主题就可以,你试试。
---
### 评论 11 - aries0311
Pure主题中,设备列表中只有本机,不显示小米音箱


---
### 评论 12 - fanyan1026
电视的小爱同学可以播放nas音乐吗
---
### 评论 13 - hanxi
> 电视的小爱同学可以播放nas音乐吗
应该是不行的
---
### 评论 14 - alililala
这个部署在了旁路由里,能否将下载目录设置成同网络的nas里呢
---
### 评论 15 - hanxi
> 这个部署在了旁路由里,能否将下载目录设置成同网络的nas里呢
配公网访问吧,局域网互通不会配的话很难教会的。
---
### 评论 16 - wjcroom
> 这个部署在了旁路由里,能否将下载目录设置成同网络的nas里呢
旁路由视同本地网络. 只要是同网段,如果有子路由. 小爱和主机尽量在主路由. 如果小爱在子路由,应该问题不大.但是 xiaomusic 和 nas,网络共享.必须在主路由.然后,xiaomusic所在地方,可以映射成本地目录.这也挺麻烦. 网络共享,和目录还是有差别的.
NFS文件服务 , linux本地挂载远程NFS为目录.这个我曾搞过. 是不是有一定原因,nas不能跑docker呢.不是太耗费资源,普通nas都有的 吧.
---
### 评论 17 - rainman5170
要怎样屏蔽外网?我的nas有域名解析,发现加上后缀外网居然也能连上xiaomusic,或者加下登陆窗口也是好的,不然直接暴露在外网了
---
### 评论 18 - hanxi
> 要怎样屏蔽外网?我的nas有域名解析,发现加上后缀外网居然也能连上xiaomusic,或者加下登陆窗口也是好的,不然直接暴露在外网了
设置访问密码就行
---
### 评论 19 - rainman5170
> > 要怎样屏蔽外网?我的nas有域名解析,发现加上后缀外网居然也能连上xiaomusic,或者加下登陆窗口也是好的,不然直接暴露在外网了
>
> 设置访问密码就行
要怎样设置?麻烦说一下在哪设置
---
### 评论 20 - hanxi

@rainman5170
---
### 评论 21 - rainman5170
> 
>
> [@rainman5170](https://github.com/rainman5170)
好的,谢谢
---
### 评论 22 - xiayuxingtian

安装好后无法识别本地文件夹歌曲,下载的歌曲也不知道在哪个文件夹

下载的这些歌曲也没找到在哪里,有没有大神帮忙解答一下

---
### 评论 23 - hanxi
路径挂载错了,删掉最后一个试试。
---
### 评论 24 - wjjjw2150
之前在群辉docker上部署的xiaomusic一直使用正常,今天群辉开机掉了硬盘,恢复正常后容器就无法启动,重置后正常启动,但默认主题和Tailwind主题出现显示问题,刷新等按钮变成了英文字符显示,如图,另外两个主题没问题。清空浏览器缓存,换其他电脑和手机访问都是如此。然后删掉xiaomusic容器和镜像(0.37),重新拉了最新镜像(0.378)部署,问题依旧。虽然功能都正常但强迫症表示实在是难受。请教大神这是怎么回事呀?
抱歉不知道怎么插图,看这个链接
https://tutu.to/image/1.N0FHK
https://tutu.to/image/N0Jwj
---
### 评论 25 - hanxi
估计是网络问题
---
### 评论 26 - s493321320
有办法添加到homeassistant里吗?
---
### 评论 27 - Chill-26
> 按照飞牛的教程,部署成功了,一直没有设备显示出来,然后我也按照相应的问题集去处理:关闭本地代理。 如果是nas运行的,网络由bridge改为host。 米家app重新登陆。 mi.com官网重新登陆。 但是还是没有办法显示设备出来,请问到底是什么原因?最新的37版本。
同样问题,但我重新使用 [视频教程-群晖1]进行部署就正常了
---
### 评论 28 - davedday
这个支持在远程VPS上docker部署,然后本地小爱同学使用吗?
---
### 评论 29 - hanxi
> 这个支持在远程VPS上docker部署,然后本地小爱同学使用吗?
支持的,请不要在不相关的话题下面问。
---
### 评论 30 - zhendery
> 要怎样屏蔽外网?我的nas有域名解析,发现加上后缀外网居然也能连上xiaomusic,或者加下登陆窗口也是好的,不然直接暴露在外网了
你这问题重点不在于设置访问密码吧,你端口暴露在公网,走http的话就算设置密码也是明文传输的呀,你得防火墙关了对应端口,或者上https+访问密码。
---
### 评论 31 - maomaovip
每次拔2秒钟以后就没有声音了,但是网页后台上面显示还在播放
---
### 评论 32 - nick-tru
>  安装好后无法识别本地文件夹歌曲,下载的歌曲也不知道在哪个文件夹
>
@xiayuxingtian 破案了!
在群晖部署的时候,路径映射不能用/app/music和/app/conf,直接用/music和/conf,实测完美识别本地歌曲,下载路径正常不需要映射,也能工作并看到下载的歌曲。
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/211)
================================================
FILE: docs/issues/212.md
================================================
---
title: 如何批量下载歌曲
---
# 如何批量下载歌曲
批量下载歌曲依赖的是 yt-dlp 批量下载播放列表里的视频并转为 mp3 实现的。
先进入到歌曲下载工具页面:
> 默认主题 => 设置 => 歌曲下载工具

已经测试过 B 站和 youtube 两种播放列表,播放列表的链接是有要求,不能有其他多余参数。
比如 B 站的是这样的
https://m.bilibili.com/video/BV1WUsDezE88
youtube 的是这样的
https://m.youtube.com/playlist?list=PLUD2d-pqyvT6_ztf31hx-5SsUUvY5UsQn
输入歌单名字是用于保存的文件夹名字,最好不是已经存在的名字,每次下载歌单都取个新名字比较合适。
已知 youtube 需要上传无痕模式下的 cookies.txt 文件才能正常下载。具体步骤见 /issues/210.html 。
也支持单独下载一个链接只有一首歌曲的。
## 评论
### 评论 1 - lazybabyz
默认主题 => 设置 =>没有显示找到 歌曲下载工具,
有一个 歌单地址 歌单内容: 输入B站测试地址显示返回无效

---
### 评论 2 - hanxi
等0.3.38版本。
---
### 评论 3 - hzqgogogo
下载音乐报错
---
### 评论 4 - hanxi
> 下载音乐报错
下载b站歌曲不用配proxy
---
### 评论 5 - MakiseKurisu
是必须要下载下来么?那有没有什么方法限定最大占用的磁盘空间?文档没有网页接口的介绍不太清楚具体有哪些功能
---
### 评论 6 - hanxi
> 是必须要下载下来么?那有没有什么方法限定最大占用的磁盘空间?文档没有网页接口的介绍不太清楚具体有哪些功能
没有这功能。
---
### 评论 7 - sagadc
請問一下YT下載失敗,因為我是在台灣不需要加proxy
看了一下log也看不出原因來
yt-dlp應該不用在自己額外安裝吧?
[yt-dl.txt](https://github.com/user-attachments/files/19379757/yt-dl.txt)
---
### 评论 8 - hanxi
> 請問一下YT下載失敗,因為我是在台灣不需要加proxy 看了一下log也看不出原因來
>
> yt-dlp應該不用在自己額外安裝吧?
>
> [yt-dl.txt](https://github.com/user-attachments/files/19379757/yt-dl.txt)
你打开调试模式再复现一下,可能需要上传 cookie 才能下载 yt 的。
---
### 评论 9 - sagadc
> > 請問一下YT下載失敗,因為我是在台灣不需要加proxy 看了一下log也看不出原因來
> > yt-dlp應該不用在自己額外安裝吧?
> > [yt-dl.txt](https://github.com/user-attachments/files/19379757/yt-dl.txt)
>
> 你打开调试模式再复现一下,可能需要上传 cookie 才能下载 yt 的。
感謝,看來要安裝那邊要說明一下要開啟yt-dlp下載最好是上傳cookie比較保險
---
### 评论 10 - nfzsh
看日志好像只获取到了第一首?
---
### 评论 11 - hzqgogogo
已收到你的邮件!
---
### 评论 12 - nfzsh
> 已收到你的邮件!
哦我知道了,你这个是针对分P的,我这个是个合集,我看看哪天我提个PR吧
---
### 评论 13 - liaowuyichu
批量下载歌曲,每次只能下载21首,如何调整这个数量值
---
### 评论 14 - hzqgogogo
已收到你的邮件!
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/212)
================================================
FILE: docs/issues/235.md
================================================
---
title: xiaomusic立体声
---
# xiaomusic立体声
有多个不同版本的小爱,怎么能选择多个音箱一起播放?
## 评论
### 评论 1 - hanxi
参考这个文档,配到一个组里就能同时播放,但是会有播放进度不一致的情况。 /issues/65.html#issuecomment-2215736529
---
### 评论 2 - F-loat
我这边先用一个音箱播放,然后米家里设置全屋播放,就能多个音箱同时播了,进度也同步,而且后续会自动全屋播放
---
### 评论 3 - zazhi4
我遇到双音箱播放问题,没法立体声,没法全屋播放,详述如下:
1、基本情况:原有小爱音箱Pro,LX06,用xiaomusic,0.3.69版本,播放正常。看到issue里提及了立体声,打算尝试,新购了小爱音箱,L06A。将新音箱通过米家APP加入到自家的wifi里。
2、尝试:
2.1,在xiaomusic里,默认面板,设置,发现可以在小爱音箱设置面板里有两个音箱供选择,我都勾选了,在设备分组配置里,两个音箱的did输入,配为1个组,能实现同时驱动两个音箱发声,但是不同步,时间上差了不到1秒钟,但是听起来不舒服。
2.2 在xiaomusic里,默认面板,设置,只勾选1个音箱;设备分组配置里,删除原先配置,清空。播放,只有一只音箱有声音。然后回到米家APP。将两个音箱设置,组成立体声。完成后,依然只有一只音箱有声音。设置后,对播放没有影响。
2.3 在米家APP中,取消立体声配对,改为“全屋播放”。设置好后,依然只有一只音箱有声音。换言之,设置后对播放没有影响。
3、检查版本,在小米音箱app中,检查两只音箱的版本,均为最新稳定版。






---
### 评论 4 - zazhi4
再补充一点信息,我做的尝试,以及网上都到的信息。
1、配立体声,配好以后,我用了当前页面的“立体声音效测试”,两个音箱都能发声,声音有先后,有不同,有联系,形成了立体声效果。
2、配立体声,配好以后,还在米家app,用qq音乐放了一首歌,两个音箱都能发声,有立体声效果。
3、网上查询,一些信息均表明,只有qq或其他在米家里播放的,换言之,网络来的信息,通过米家,向音箱播音,能实现立体声。其他方法,比如蓝牙,或AUX,都没法实现立体声。推测,是米家主动发音的时候,内部拆解了左右声道的声音,发往左右音箱。
4、原本猜想,两个音箱,会一主一从,主音箱拆分声音,发往从音箱。但是在米家APP里配置立体声时,未发现主从之分。
---
### 评论 5 - hanxi
全屋播放,控制两个音箱中的任意一个都不行吗?
---
### 评论 6 - zazhi4
全屋播放,设置音箱A(设置界面只选A,当然只能选一个。共有3个选项,音箱A,音箱B,手机)播放,音箱B没声音;设置音箱B播放,音箱A没声音。
---
### 评论 7 - Bazinga-git
组立体声是刚需,大佬加油
---
### 评论 8 - zazhi4
立体声,想了几个路径:1,xiaomusic能不能拆分声音为左右两个声道,分别发往两个音箱,难题在于,怎么保证两个音箱同时接到信息,发声;2,能不能搞定米家app的接口,发送声音给米家app,由米家app发给音箱;3,或是搞定模拟米家app与音箱的接口,通过米家接口,发给音箱,让音箱以为声音来自米家app。方法2,3,要搞清楚,米家app播音,为什么能实现立体声,然后在方法2,3中选一个方向。
---
### 评论 9 - hanxi
@zazhi4 思路是对的,感兴趣可以抓包玩玩的。目前协议是没加密的。
---
### 评论 10 - winsel
我有两个l06a,在米家app里打开dlan并且两个音箱组立体声后,用安卓手机上面的椒盐音乐播放器,推送到小爱音箱dlan的时候显示的是立体声名字,推送后立体声播放,左右延迟很小,几乎察觉不到,相对群组功能好太多了。xiaomusic能不能增加一个立体声选项,采用dlan推送模式播放呢?
这样应该是立体声最优解了

---
### 评论 11 - lgcscu
全屋播放并不是立体声,同时勾选两个设备做组合会出现声音不同步的情况,都不理想。我自己目前用的是立体声,两个音箱都是小爱音箱SOUNDPRO,组合立体声的时候有一个是主音箱一个是副音箱,看你组合的顺序,我的是左边的音箱为主音箱,所以我每次都是命令左边的音箱播放音乐,右边的就会同步播放,没有偏差,用到的就是小米自己的立体声组合,不需要xiaomusic单独支持。另外我启用了连续播放,soundPRO的灯光效果就可以正常使用(立体声),如果不勾选这个连续播放,xiaomusic触发的音乐播放就没有灯光效果,小爱音箱自己的就有,所以我的解决方式就是这样,推荐给有立体声需要的朋友,感谢hanxi的xiaomusic项目!
<img width="747" height="120" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/cdb62b58-ed17-4785-bbe2-14ede0dae580" />
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/235)
================================================
FILE: docs/issues/269.md
================================================
---
title: 如何添加 网易云音乐playlist
---
# 如何添加 网易云音乐playlist
利用 NeteaseCloudMusicApi 获取歌单和播放地址
我在你基础上改了一下,但是我的逻辑不合理
https://github.com/dissipator/xiaomusic
## 评论
### 评论 1 - hanxi
建议通过插件实现或者新增一个页面工具把歌单导出 json 。
歌单的 json 格式见 /issues/78.html
---
### 评论 2 - qiujie8092916
> 利用 NeteaseCloudMusicApi 获取歌单和播放地址 我在你基础上改了一下,但是我的逻辑不合理 [dissipator/xiaomusic](https://github.com/dissipator/xiaomusic)
老哥在实现插件了吗?我也急需播放歌单
我诉求是:我用网易云音乐单独新建了一个歌单,我往里面扔歌曲,以更新歌单。希望创建一个自定义的语音命令,让小爱同学随机播放这个歌单里的音乐。然后我通过在米家里执行,比如触发了「我回来了」的智能场景时,就让小爱音箱执行这个自定义的语音命令,就会自动播放我新建的这个歌单里的音乐了。
现在的小爱音箱虽然能勉强实现,但是很垃圾。随机播放的随机性有问题,并且只能选择歌单里的 30 首歌。
(老哥如果没实现的话,我可以尝试搞搞)
---
### 评论 3 - dissipator
我都能直接利用这个直接播放NeteaseCloudMusicApi这上的歌了。在配合unblk,无敌。我就是没设备,还在路上
通过插件实现,非常好,就是不知道怎么开发,有文档我可以尝试一下。
---
### 评论 4 - dissipator
> 建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78
配置处直接填了api的接口
http://127.0.0.1:3000/playlist/detail?id=12758992226
我是直接再你歌单保存上强改的。
```python
async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
log.info(data)
url = data.url
content = "[{"
host = f"{url.split('/')[0]}//{url.split('/')[2]}"
try:
ret = "OK"
jsons = await downloadfile(url,"json")
# print(jsons)
list_name = jsons['playlist']['name']
content += '"name":"'+list_name+'","musics":['
for song in jsons['playlist']['tracks']:
content += f"{{\"name\":\"{song['name']}\",\"url\": \"{host}/song/url?br=999000&proxy=http:%2F%2F127.0.0.1:8080&realIP=211.161.244.70&id={song['id']}\"}}"
except Exception as e:
log.exception(f"Execption {e}")
ret = "Download JSON file failed."
content = content[:-1] + "]}]"
```
照着你的说明。而且能成功播发
```python
@app.get("/musicinfo")
async def musicinfo(
name: str, musictag: bool = False, Verifcation=Depends(verification)
):
url = xiaomusic.get_music_url(name)
if("song/url" in url):
jsons = await downloadfile(url,"json")
url = jsons['data'][0]['url']
```
播放处加了一个判断
---
### 评论 5 - hanxi
> > 建议通过插件实现或者新增一个页面工具把歌单导出 json 。 歌单的 json 格式见 #78
你的修改我看了,不太通用。生成json,再用现有的接口提交json更通用。
---
### 评论 6 - dissipator
是的,不通用。最好是用插件实现。
1. 就是不知道你插件的逻辑。
2. 如果用插件就考虑直接读取网易账号下所有歌单。然后选择一个导入,或者全部导入。
3. 等你完善文档后我可以尝试写一个。同理,qq等其他平台的歌单也就都可以弄了
---
### 评论 7 - hanxi
等有空我写个修改歌单内容的插件示例吧。
---
### 评论 8 - dissipator
```
import requests
def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
"""
Purpose:
"""
global log, xiaomusic
if type == "netease":
if uid:
api_url = f"{api_host}/user/playlist?uid={uid}"
# 发起请求
response = requests.get(api_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
# log.info(f"getmy_playlist url:{api_url} response:{response.text}")
music_list = response.json()
for item in music_list['playlist']:
list_name = item.get("name")
log.info(f"getmy_playlist name:{list_name}")
# if item.get("id") in [12709941656,]:
songs_url = f"{api_host}/playlist/detail?id={item['id']}"
# 发起请求
response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
# log.info(f"getmy_playlist url:{api_url} response:{response.text}")
musics = response.json()
one_music_list = []
for music in musics['playlist']['tracks']:
if (not music):
continue
# try:
name = music['name']
picUrl = music['al']['picUrl']
artist = music['ar'][0]['name']
album = music['al']['name']
name = music.get("name")
url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
if (not name) or (not url):
continue
xiaomusic.all_music[name] = url
xiaomusic.all_music_tags[name] = {
"title": name,
"artist": artist,
"album": album,
"year": "",
"genre": "",
"picture": picUrl,
"lyrics": ""
}
one_music_list.append(name)
log.debug(f"getmy_playlist name:{list_name}")
log.debug(one_music_list)
# 歌曲名字相同会覆盖
xiaomusic.music_list[list_name] = one_music_list
xiaomusic.try_save_tag_cache()
log.debug(xiaomusic.all_music)
log.debug(xiaomusic.music_list)
return
if playlist_id:
songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
# 发起请求
response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
# log.info(f"getmy_playlist url:{api_url} response:{response.text}")
musics = response.json()
list_name = musics['playlist']['name']
one_music_list = []
for music in musics['playlist']['tracks']:
if (not music):
continue
# try:
name = music['name']
picUrl = music['al']['picUrl']
artist = music['ar'][0]['name']
album = music['al']['name']
name = music.get("name")
url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
if (not name) or (not url):
continue
xiaomusic.all_music[name] = url
xiaomusic.all_music_tags[name] = {
"title": name,
"artist": artist,
"album": album,
"year": "",
"genre": "",
"picture": picUrl,
"lyrics": ""
}
one_music_list.append(name)
log.debug(f"getmy_playlist name:{list_name}")
log.debug(one_music_list)
# 歌曲名字相同会覆盖
xiaomusic.music_list[list_name] = one_music_list
xiaomusic.try_save_tag_cache()
log.debug(xiaomusic.all_music)
log.debug(xiaomusic.music_list)
return
else:
log.error(f"getmy_playlist type:{type} not support")
```
---
### 评论 9 - dissipator
> 等有空我写个修改歌单内容的插件示例吧。
不用拉,插件我已经写出来了

---
### 评论 10 - hanxi
发下你的 setting.json 配置吧,方便他人知道怎么配。
---
### 评论 11 - dissipator
我还在测试,设备到了能用了再发吧
---
### 评论 12 - guitarbug
也需要网易歌单功能, 坐等教程
---
### 评论 13 - dissipator
# 成功了

# 最好是在setting.json里去配置,我测试老是不匹配,在setting.json里去配置终于成功。可以直接调用 [NeteaseCloudMusicApi](http://localhost:3000/docs/#/?id=neteasecloudmusicapi) 接口,甚至使用UnblockNeteaseMusic 解锁灰色,但是代码需要小调整。
setting.json
```json
{
"account": "",
"password": "",
"mi_did": "",
"miio_tts_command": "",
"cookie": "",
"verbose": false,
"music_path": "music",
"download_path": "music/download",
"conf_path": "conf",
"cache_dir": "cache",
"hostname": "192.168.2.5",
"port": 8090,
"public_port": 0,
"proxy": "",
"search_prefix": "bilisearch:",
"ffmpeg_location": "./ffmpeg/bin",
"active_cmd": "play,set_random_play,playlocal,play_music_list,play_music_list_index,stop_after_minute,stop,获取歌单",
"exclude_dirs": "@eaDir,tmp",
"music_path_depth": 10,
"disable_httpauth": true,
"httpauth_username": "",
"httpauth_password": "",
"music_list_url": "",
"music_list_json": "",
"custom_play_list_json": "",
"disable_download": false,
"key_word_dict": {
"播放歌曲": "play",
"播放本地歌曲": "playlocal",
"关机": "stop",
"下一首": "play_next",
"上一首": "play_prev",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "set_random_play",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
"加入收藏": "add_to_favorites",
"收藏歌曲": "add_to_favorites",
"取消收藏": "del_from_favorites",
"播放列表第": "play_music_list_index",
"本地播放歌曲": "playlocal",
"放歌曲": "play",
"暂停": "stop",
"停止": "stop",
"停止播放": "stop",
"播放歌单": "play_music_list",
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")",
"获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
},
"key_match_order": [
"分钟后关机",
"播放歌曲",
"下一首",
"上一首",
"单曲循环",
"全部循环",
"随机播放",
"关机",
"刷新列表",
"播放列表第",
"播放列表",
"加入收藏",
"收藏歌曲",
"取消收藏",
"播放本地歌曲",
"本地播放歌曲",
"放歌曲",
"暂停",
"停止",
"停止播放",
"播放歌单",
"测试自定义口令",
"测试链接",
"获取歌单"
],
"use_music_api": false,
"use_music_audio_id": "1582971365183456177",
"use_music_id": "355454500",
"log_file": "/tmp/xiaomusic.txt",
"fuzzy_match_cutoff": 0.6,
"enable_fuzzy_match": true,
"stop_tts_msg": "收到,再见",
"enable_config_example": false,
"keywords_playlocal": "播放本地歌曲,本地播放歌曲",
"keywords_play": "播放歌曲,放歌曲",
"keywords_stop": "关机,暂停,停止,停止播放",
"keywords_playlist": "播放列表,播放歌单",
"user_key_word_dict": {
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")",
"获取歌单": "exec#getmy_playlist(playlist_id=12758992225)"
},
"enable_force_stop": false,
"devices": {
" ": {
"did": " ",
"device_id": " -17c6-4204- - ",
"hardware": "L05C",
"name": "小黑你好",
"play_type": "",
"cur_music": "",
"cur_playlist": ""
}
},
"group_list": "",
"remove_id3tag": false,
"convert_to_mp3": false,
"delay_sec": 3,
"continue_play": false,
"pull_ask_sec": 1,
"crontab_json": "",
"enable_yt_dlp_cookies": false,
"get_ask_by_mina": false
}
```
getmy_playlist.py
```python
import requests
async def getmy_playlist(type="netease",api_host="http://127.0.0.1/api", playlist_id=None,uid=None):
"""
Purpose:
"""
global log, xiaomusic
if type == "netease":
if uid:
api_url = f"{api_host}/user/playlist?uid={uid}"
# 发起请求
response = requests.get(api_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
# log.info(f"getmy_playlist url:{api_url} response:{response.text}")
music_list = response.json()
for item in music_list['playlist']:
list_name = item.get("name")
log.info(f"getmy_playlist name:{list_name}")
# if item.get("id") in [12709941656,]:
songs_url = f"{api_host}/playlist/detail?id={item['id']}"
# 发起请求
response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
# log.info(f"getmy_playlist url:{api_url} response:{response.text}")
musics = response.json()
one_music_list = []
for music in musics['playlist']['tracks']:
if (not music):
continue
# try:
name = music['name']
picUrl = music['al']['picUrl']
artist = music['ar'][0]['name']
album = music['al']['name']
name = music.get("name")
url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"
if (not name) or (not url):
continue
xiaomusic.all_music[name] = url
xiaomusic.all_music_tags[name] = {
"title": name,
"artist": artist,
"album": album,
"year": "",
"genre": "",
"picture": picUrl,
"lyrics": ""
}
one_music_list.append(name)
log.debug(f"getmy_playlist name:{list_name}")
log.debug(one_music_list)
# 歌曲名字相同会覆盖
xiaomusic.music_list[list_name] = one_music_list
xiaomusic.try_save_tag_cache()
log.debug(xiaomusic.all_music)
log.debug(xiaomusic.music_list)
return
if playlist_id:
songs_url = f"{api_host}/playlist/detail?id={playlist_id}"
# 发起请求
response = requests.get(songs_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200,引发HTTPError异常
musics = response.json()
list_name = musics['playlist']['name']
log.info(f"getmy_playlist list_name:{list_name} ")
one_music_list = []
for music in musics['playlist']['tracks']:
if (not music):
continue
# try:
name = music['name']
picUrl = music['al']['picUrl']
artist = music['ar'][0]['name']
album = music['al']['name']
name = music.get("name")
url = f"{api_host}/song/url?id={music['id']}&br=350000&proxy=HTTP:%2F%2F127.0.0.1:8080"
if (not name) or (not url):
continue
xiaomusic.all_music[name] = url
xiaomusic.all_music_tags[name] = {
"title": name,
"artist": artist,
"album": album,
"year": "",
"genre": "",
"picture": picUrl,
"lyrics": ""
}
one_music_list.append(name)
log.debug(f"getmy_playlist name:{list_name}")
log.debug(one_music_list)
# 歌曲名字相同会覆盖
xiaomusic.music_list[list_name] = one_music_list
xiaomusic.try_save_tag_cache()
return
else:
log.error(f"getmy_playlist type:{type} not support")
```
---
### 评论 14 - dissipator


---
### 评论 15 - dludream
NeteaseCloudMusicApi似乎获取不到播放地址?其他信息倒是有。

---
### 评论 16 - hanxi
看代码是另一个接口获取url的: `url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"`
---
### 评论 17 - dludream
> 看代码是另一个接口获取url的: `url = f"{api_host}/song/url?id={music['id']}&br=350000&realIP=211.161.244.70&proxy=HTTP:%2F%2F127.0.0.1:8080"`
是的,我用的是这个接口,https://registry.hub.docker.com/r/gnehs/neteasecloudmusicapi-docker/
可能网易修改了api,这些获取不了播放地址了。
我倒不是要这个功能,只是感兴趣看看。我不获取,我直接用yt把歌全下载下来播放。
---
### 评论 18 - dissipator
这个不是网易的接口,是[NeteaseCloudMusicApi](http://localhost:3000/docs/#/?id=neteasecloudmusicapi) 接口。proxy=HTTP:%2F%2F127.0.0.1:8080"是UnblockNeteaseMusic 解锁灰色;
完整的使用方式和docker 可以到 https://github.com/dissipator/xiaomusic/tree/dev 看README.md;目前没有教程。本人就在群2,有问题可以找我。
---
### 评论 19 - beibei2a
>  
“请问 registry.cn-chengdu.aliyuncs.com/dissipator/xiaomusic:dev 有没有 ARM64 版本?我用 ARMv8 设备运行时遇到 exec format error。”
---
### 评论 20 - hanxi
我的镜像是有的,他的镜像没试过。
---
### 评论 21 - dissipator
没有
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/269)
================================================
FILE: docs/issues/285.md
================================================
---
title: 相关工具推荐
---
# 相关工具推荐
- [xhongc/music-tag-web 刮削音乐歌词图片](https://github.com/xhongc/music-tag-web)
- [onlyLTY/dockerCopilot 一键更新容器](https://github.com/onlyLTY/dockerCopilot)
- [tiny-nav 非常简单的导航网站,NAS部署](https://github.com/hanxi/tiny-nav)
## 评论
### 评论 1 - F-loat
借楼加个小程序的图 :partying_face: https://github.com/F-loat/xiaoplayer
### 小程序码
<p>
<img alt="weapp" src="https://assets-1251785959.cos.ap-beijing.myqcloud.com/xiaoplayer/weappcode.jpg" width="24%" />
</p>
### 截图
<p>
<img src="https://assets-1251785959.cos.ap-beijing.myqcloud.com/xiaoplayer/screenshot/5.png" width="24%" />
<img src="https://assets-1251785959.cos.ap-beijing.myqcloud.com/xiaoplayer/screenshot/6.png" width="24%" />
<img src="https://assets-1251785959.cos.ap-beijing.myqcloud.com/xiaoplayer/screenshot/7.png" width="24%" />
<img src="https://assets-1251785959.cos.ap-beijing.myqcloud.com/xiaoplayer/screenshot/8.png" width="24%" />
</p>
---
### 评论 2 - hanxi
@F-loat 可以在欢迎页加个链接显示小程序码。
---
### 评论 3 - F-loat
@hanxi 可以的,这样还能自动把 ip 用参数带过来,我有空搞一下
---
### 评论 4 - sparkyuanquan
> 借楼加个小程序的图 🥳 https://github.com/F-loat/xiaoplayer
>
> ### 小程序码
> <img alt="weapp" width="24%" src="https://camo.githubusercontent.com/0213912ac333b24c09baf04401486b231d5ab58771bb2a7ad00e1d14eb39cdc4/68747470733a2f2f6173736574732d313235313738353935392e636f732e61702d6265696a696e672e6d7971636c6f75642e636f6d2f7869616f706c617965722f7765617070636f64652e6a7067">
>
> ### 截图
> <img alt="" width="24%" src="https://camo.githubusercontent.com/6e5a3b3b33652ee40a30c33c6fe4bbdad43bbda6f0354fd2b8172778c6cc1b28/68747470733a2f2f6173736574732d313235313738353935392e636f732e61702d6265696a696e672e6d7971636c6f75642e636f6d2f7869616f706c617965722f73637265656e73686f742f352e706e67"> <img alt="" width="24%" src="https://camo.githubusercontent.com/664b337b2ca34a2651fa4b04e7ac40939de6fd8bf9ee2740aaffadb83b619b06/68747470733a2f2f6173736574732d313235313738353935392e636f732e61702d6265696a696e672e6d7971636c6f75642e636f6d2f7869616f706c617965722f73637265656e73686f742f362e706e67"> <img alt="" width="24%" src="https://camo.githubusercontent.com/618c2dce4e280bad096a8105ed1879c26c855872223fcebf11bf66b6bbf552cb/68747470733a2f2f6173736574732d313235313738353935392e636f732e61702d6265696a696e672e6d7971636c6f75642e636f6d2f7869616f706c617965722f73637265656e73686f742f372e706e67"> <img alt="" width="24%" src="https://camo.githubusercontent.com/98b193d9fa4f918d33a9b1166e230e839e962ee8fec087f34b57742c70320881/68747470733a2f2f6173736574732d313235313738353935392e636f732e61702d6265696a696e672e6d7971636c6f75642e636f6d2f7869616f706c617965722f73637265656e73686f742f382e706e67">
你好,麻烦问一下,只能内网播放吗?我在路由上映射了8090端口, 在家外小程序可以看到NAS上的歌曲,也能控制音箱进行播放,,但是手机本机直接播放失败,麻烦问一下,是否可以在外面直接小程序播放NAS音乐,这样就不用安装像音流 DS Audio那些NAS的那些音乐播放器了。
---
### 评论 5 - F-loat
@sparkyuanquan 可以本机直接播放的,之前部分场景判断有问题,应该已经修复了,重启下小程序试试
---
### 评论 6 - sparkyuanquan
小程序删了,重开,我用手机的5G网络微信小程序登录后可以看到歌曲,在手机上播放音乐就转圈,过差不多一分钟左右,显示 INNERERRCODE:-1001,ERRMSG:请求超时。我在路由上把xiaomusic的8090端口映射到了外网60000。
---
### 评论 7 - sparkyuanquan
发现问题在哪了,如果直接映射8090到外网8090,就可以播放,但是如果8090映射到60000不行。我继续试试nginx反代https看看
---
### 评论 8 - sparkyuanquan
原来是要内网服务器地址和公网服务器地址都要填写, 内网写8090端口,公网写60000端口才行,单写公网服务器地址不写内网服务器地址就会有上面的问题。
---
### 评论 9 - F-loat
@sparkyuanquan 我有空再看一下,应该只写公网也要可以的
---
### 评论 10 - wyyang1978-debug
支持IPV6 外网访问么? 地址格式是怎样的?我用http://[ ]:port 格式无法访问
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/285)
================================================
FILE: docs/issues/294.md
================================================
---
title: 关于M01型号的注意事项
---
# 关于M01型号的注意事项
M01:在0.3.55版本【型号兼容模式】与【特殊型号获取对话记录】都设为false或true,都可以语音了。
如果【型号兼容模式】为 true,默认UI显示播放中,但音箱没声音。
型号:S12A、LX04、S12 在米家APP可以联动,比如客厅有人自定义指令:播放歌曲、关机...等
而M01无论【型号兼容模式】与【特殊型号获取对话记录】设为false或true,都无法执行任何自定义指令…

## 评论
### 评论 1 - hanxi
M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。
---
### 评论 2 - bj803
> M01 保持默认设置应该是能语音和播放的吧。自定义指令的功能应该是 M01 本身就不支持,可能属于放弃维护的产品吧。
刚试了用:pause是可以暂停(不过只暂2分钟左右又自己播放了)
---
### 评论 3 - hanxi
只能网页里点关机按钮,或者语音关机。所有型号都一样。
---
### 评论 4 - bj803
> 型号:S12A、LX04、S12
> 只能网页里点关机按钮,或者语音关机。所有型号都一样。
在型号:S12A、LX04、S12 除了能网页里点关机按钮或者语音开关机外,能在米家APP自定义指令进行播放与关机(例如当客厅客有人自定义指令"播放歌曲",无人自定义指令"关机"。M01自定义指令"pause"可以暂停,其他口令不行。
---
### 评论 5 - bj803
刚又试了一下,用自定义play stop、power off可以停止播放关机,用自定义play music可以播放音乐,可能M01不需要下划线
---
### 评论 6 - hanxi
感谢你的反馈。
---
### 评论 7 - CrazyJasonwell
LX04不能识别,为找到小爱音箱。。。
---
### 评论 8 - hanxi
> LX04不能识别,为找到小爱音箱。。。
找不到设备是登录问题。
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/294)
================================================
FILE: docs/issues/297.md
================================================
---
title: xiaomusic极空间安装教程(2024/12/28更新)
---
# xiaomusic极空间安装教程(2024-12-28更新)
> 本教程同步更新于最新版的xiaomusic
<s>看不懂/嫌麻烦/懒 但有点小钱,找 hanxi 预约微信或者 QQ 远程安装,他便宜,收费50一次,作法不成功不要钱</s>
**ARM架构自己想办法获取镜像 点名Z2PRO**
# 获取镜像
## 科学环境:
1. 在 **搜索框** 中输入 `hanxi/xiaomusic`,在搜索的结果中直接选择第一个,点击**下载**

2. 在新弹出的版本选择窗口中,根据你的情况选择。

### 版本说明
- 获取 **最新版** 直接点击 **下载** 即可,建议使用默认的 `latest`
- 获取 **特定版本** [点击此处可查看](https://github.com/hanxi/xiaomusic/releases) 用于回退出现功能不兼容、恶性bug等情况,一般建议反馈开发者,修复很及时,尽量不要回退版本 请输入如 `v0.3.55`
- 获取 **实验版本**(已修复部分bug但未推送)请输入 `main`
3. 接着弹出如图所示的页面,耐心等待下载完成。

4. 下载完成后切换到 **本地镜像** 选项卡
剩余步骤与国内环境相同,见 [部署镜像](#部署镜像)
## 国内环境:
1. 打开docker,在左侧的菜单中选择 **镜像** 切换到 **仓库** 选项卡,点击 **自定义拉取** 按钮

2. 在弹出的对话框中输入 ` m.daocloud.io/docker.io/hanxi/xiaomusic ` ,点击 **拉取** 按钮

3. 下载完成后切换到 **本地镜像** 选项卡
# 部署镜像
1. 找到刚才已经拉取好的镜像,*单击选中*,点击 **添加到容器**

2. 在弹出的 **创建容器** 菜单中,切换到 **文件夹路径** 选项卡中,按图中的提示进行配置。

**注意:**
* 装载路径中的 **配置文件目录** 和 **音乐目录** 必须进行配置,**其他目录非必要请勿配置**
* 主题目录为方便开发主题调试时的配置选项,普通用户不能理解明确用途请**不要配置主题目录**,否则会报**HTTP Status 500 – Internal Server Error** 错误
* 如有多个音乐目录,请按照下面的格式进行配置
| 文件/文件夹 | 装载路径 |
| :----------: | :----------: |
| /data/music1 | /app/music/music1 |
| /data/music2 | /app/music/music2 |
3. 切换到 **端口** 选项卡,修改成与你的极空间 *不冲突* 的本地端口号,如 `5678` (示例按照本地端口号5678来进行配置,下同)
> 友情提醒: 尽量不要修改容器端口号,否则要到配置文件目录修改对应的`setting.json`文件中的配置,会增加很多麻烦

5. 切换到 **环境** 选项卡,将`XIAOMUSIC_HOSTNAME` 修改为你的 **极空间的IP地址**
> 友情提醒:
> 1. 此处不可忽略,否则后续播放音乐会出现问题
> 2. 不要尝试修改XIAOMUSIC_PORT!除非你没有看上一条的友情提醒
> 3. 不要在此处配置`ACCOUNT`和`PASSWORD`,没有过风控仍然无法使用!上古时代的教程不要再看了,容易走火入魔!

6. 点击 **应用**按钮,此时容器已经配置完成了,切换到左侧的 **容器概况** 菜单,可查看容器详情

# 进入xiaomusic网页端进行配置
1.请关闭代理,打开浏览器,地址栏输入 **极空间IP:本地端口号** 如`192.168.2.5:5678`,打开网页后点击 **默认主题**

**注意:**
* 不要复制此处的地址,必须输入极空间的IP地址。不知道的建议上咸鱼50块换个不锈钢盆
* 不要输入容器的端口号8090,极空间不能使用这个端口号。
2. 点击 **设置** 按钮进入设置页面

3. 输入**小米账号**、**小米密码**、**XIAOMUSIC_HOSTNAME(IP或域名):**、**外网访问端口**,滑到页面最下方点击 **保存**


**注意:**
* 小米账号非手机号,请在手机设置-个人中心中查看小米ID
* 密码不要输错,账号密码错误在上面会弹出提醒,不要假装看不见上面的提醒文字
* XIAOMUSIC_HOSTNAME(IP或域名): 可以输入当前页面的IP地址(在地址栏),**不要在此处输入端口号!!!**,如果域名需要使用https协议,请加上https://
4.如果以上步骤没错,你将在设置中心看见设备列表

5. 回到首页,出现设备列表,切换对应设备即可畅享

## 评论
### 评论 1 - xiaohuobanhahaha
[xiaomusic.txt](https://github.com/user-attachments/files/18011572/xiaomusic.txt)
<img width="559" alt="截屏2024-12-05 00 43 24" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/160aeacc-e1c0-40fa-b219-6b6f5183c43c">
an'zh
无法使用语音播放歌曲,小爱s12a。极空间z4pro。
1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090.
2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传
---
### 评论 2 - 52fisher
> [xiaomusic.txt](https://github.com/user-attachments/files/18011572/xiaomusic.txt)
> <img alt="截屏2024-12-05 00 43 24" width="559" src="https://private-user-images.githubusercontent.com/20666294/392485798-160aeacc-e1c0-40fa-b219-6b6f5183c43c.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzE3NzIsIm5iZiI6MTczMzMzMTQ3MiwicGF0aCI6Ii8yMDY2NjI5NC8zOTI0ODU3OTgtMTYwYWVhY2MtZTFjMC00MGZhLWIyMTktNmI2ZjUxODNjNDNjLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE2NTc1MlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWJhMmZjMzFhMzdmYmI1NGJjZTg1ZGVhNGI2Njc1YjYwYmQxZjVmMzYyYjg3YWNlNzdhNmEwMzE4Y2UyMTRlNjUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0._OSLH4Bc0stzG4tLdPalW-mB-ASKlM9uztZ1icefPFU"> an'zh 无法使用语音播放歌曲,小爱s12a。极空间z4pro。 1. 按照教程,点击播放本地歌曲,提示hostname和设置的端口映射不匹配。映射5678,容器端口8090. 2.网络host模式,能够本地播放,无法使用语音控制,提示“下载app”。日志已上传
既然你映射的5678,为什么你又在那把监听端口改成11999? 我的教程里面全程没有写要修改监听端口
---
### 评论 3 - xiaohuobanhahaha
我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图
<img width="847" alt="截屏2024-12-05 01 46 52" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/e7f58907-a216-41e8-bafa-5d49db8eca45">
<img width="516" alt="截屏2024-12-05 01 49 11" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/4261c2e2-fe0c-4ff6-ae06-ead7f928af57">
<img width="647" alt="截屏2024-12-05 01 47 02" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/35b195d1-9512-40bb-b336-847e0bb2e6c9">
<img width="667" alt="截屏2024-12-05 01 47 15" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/b917a977-38cf-4126-8754-c46abe9360a2">
提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。
---
### 评论 4 - 52fisher
> 我没讲清楚。我试了两种极空间的桥接和host模式。桥接模式。我按照教程走的。报错如图 <img alt="截屏2024-12-05 01 46 52" width="847" src="https://private-user-images.githubusercontent.com/20666294/392507064-e7f58907-a216-41e8-bafa-5d49db8eca45.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzQ4OTEsIm5iZiI6MTczMzMzNDU5MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MDcwNjQtZTdmNTg5MDctYTIxNi00MWU4LWJhZmEtNWQ0OWRiOGVjYTQ1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE3NDk1MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBlMTYxZGI0ZjRiYTkyOGIwZWQ4YWZlZWJiY2U3MzljZTg5NTNhZjJkNzlkNzYyYzlmMWJkZjlkMGYwNWEzNzgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.XSsE7pZj7pwj4kaZBQ02fyH13bVXLzGuJcjRv98WWiQ"> <img alt="截屏2024-12-05 01 49 11" width="516" src="https://private-user-images.githubusercontent.com/20666294/392507855-4261c2e2-fe0c-4ff6-ae06-ead7f928af57.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzQ4OTEsIm5iZiI6MTczMzMzNDU5MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MDc4NTUtNDI2MWMyZTItZmUwYy00ZmY2LWFlMDYtZWFkN2Y5MjhhZjU3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE3NDk1MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTk2NzFjYTRmNzAyMTJlYzYwZDI4NThlMWQ1MDViZGU5MDI3YThjNzExZTAyNWJmM2NlYWQzZDIzYzRhMjc1MTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.NIBWfzpZ5OTIic6Gv83_PrOdUX27o19Vo1zDvFyrILE"> <img alt="截屏2024-12-05 01 47 02" width="647" src="https://private-user-images.githubusercontent.com/20666294/392507111-35b195d1-9512-40bb-b336-847e0bb2e6c9.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzQ4OTEsIm5iZiI6MTczMzMzNDU5MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MDcxMTEtMzViMTk1ZDEtOTUxMi00MGJiLWIzMzYtODQ3ZTBiYjJlNmM5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE3NDk1MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI5YTliOWQzNDUyYThjODYwZmY4NTJiMTNkYmFmNmY3ZWE5ZDBlMmQ5OGQxMTIzM2JlMmIxZTcwNTNlMmYwZTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.f9czCn43Yzm7sn6-cpJykaWIng4WJf9aoE52kbVeASY"> <img alt="截屏2024-12-05 01 47 15" width="667" src="https://private-user-images.githubusercontent.com/20666294/392507187-b917a977-38cf-4126-8754-c46abe9360a2.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzQ4OTEsIm5iZiI6MTczMzMzNDU5MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MDcxODctYjkxN2E5NzctMzhjZi00MTI2LTg3NTQtYzQ2YWJlOTM2MGEyLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE3NDk1MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQ3MDE1NjVhZjMzMTRkNjg5ZTA5NDc1MDU3OTFiODc3NTYyMTg3Y2FjNjg2NGM3MjE0N2VlNjg0YzFmZTgwZGImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.tX8iIlpvE43Krq4q__7dQ3Wuz8kYQuzmlf-XXNHO1Ws">
>
> 提到的第二个问题和日志,是我将网络模式改为host的情况,能连上音箱,但是没法使用语音控制。
把外网访问端口改成5678
---
### 评论 5 - xiaohuobanhahaha
可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。
这是日志,
[xiaomusic.txt](https://github.com/user-attachments/files/18012369/xiaomusic.txt)
---
### 评论 6 - 52fisher
> 可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, [xiaomusic.txt](https://github.com/user-attachments/files/18012369/xiaomusic.txt)
你的设置 hostname='192.168.31.165', port=8090, public_port=5678,
后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了
---
### 评论 7 - xiaohuobanhahaha
> > 可以连接上网页控制了,但是语音控制仍然不行。已经按照FAQ问题集合 #99 重启docker。 这是日志, [xiaomusic.txt](https://github.com/user-attachments/files/18012369/xiaomusic.txt)
>
> 你的设置 hostname='192.168.31.165', port=8090, public_port=5678, 后台的ip 192.168.31.143 检查一下你的地址 有可能是你的ip地址改变了
确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
---
### 评论 8 - 52fisher
> 确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧
---
### 评论 9 - xiaohuobanhahaha
<img width="660" alt="截屏2024-12-05 02 23 53" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/b9d26de9-3dcf-4e65-9460-36603735c887">
<img width="780" alt="截屏2024-12-05 02 24 49" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/6a204cdb-bb10-4f35-822d-613aeed0fae0">
> > 确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
>
> 容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧
辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。
---
### 评论 10 - 52fisher
> <img alt="截屏2024-12-05 02 23 53" width="660" src="https://private-user-images.githubusercontent.com/20666294/392519509-b9d26de9-3dcf-4e65-9460-36603735c887.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk1MDktYjlkMjZkZTktM2RjZi00ZTY1LTk0NjAtMzY2MDM3MzVjODg3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFhN2I4NzQyYjA4YzRiOTk4OTU3NmVkNjU2MjM1ODhjMmNlOWU0YTg5OTFlMzA1NTcxMTA5OTk1YjgwNThhOTgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.yrC-sn2T6PKwBJb_mal1T2yxcSz008Hb7KWVmOe6zaA"> <img alt="截屏2024-12-05 02 24 49" width="780" src="https://private-user-images.githubusercontent.com/20666294/392519852-6a204cdb-bb10-4f35-822d-613aeed0fae0.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk4NTItNmEyMDRjZGItYmIxMC00ZjM1LTgyMmQtNjEzYWVlZDBmYWUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE4MzlhODM1NDY4ZjFmN2I2Y2JkZWU5ZGFkNTNjYTNmZDg2OTU3YzA0MjRkMDA2MzRmOTk2ZGE3NmE2NjViZmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0._I8Rll2a96i-cx6WBvEwOclNInfOmZkD5HmcorzT-KI">
>
> > > 确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
> >
> >
> > 容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧
>
> 辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。
[readme](https://github.com/hanxi/xiaomusic?tab=readme-ov-file#-%E8%AE%A8%E8%AE%BA%E5%8C%BA)
---
### 评论 11 - xiaohuobanhahaha
> > <img alt="截屏2024-12-05 02 23 53" width="660" src="https://private-user-images.githubusercontent.com/20666294/392519509-b9d26de9-3dcf-4e65-9460-36603735c887.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk1MDktYjlkMjZkZTktM2RjZi00ZTY1LTk0NjAtMzY2MDM3MzVjODg3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFhN2I4NzQyYjA4YzRiOTk4OTU3NmVkNjU2MjM1ODhjMmNlOWU0YTg5OTFlMzA1NTcxMTA5OTk1YjgwNThhOTgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.yrC-sn2T6PKwBJb_mal1T2yxcSz008Hb7KWVmOe6zaA"> <img alt="截屏2024-12-05 02 24 49" width="780" src="https://private-user-images.githubusercontent.com/20666294/392519852-6a204cdb-bb10-4f35-822d-613aeed0fae0.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk4NTItNmEyMDRjZGItYmIxMC00ZjM1LTgyMmQtNjEzYWVlZDBmYWUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE4MzlhODM1NDY4ZjFmN2I2Y2JkZWU5ZGFkNTNjYTNmZDg2OTU3YzA0MjRkMDA2MzRmOTk2ZGE3NmE2NjViZmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0._I8Rll2a96i-cx6WBvEwOclNInfOmZkD5HmcorzT-KI">
> > > > 确实是变了。192.168.31.143是我电脑的ip。 hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主路由dhcp都绑定了。 大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
> > >
> > >
> > > 容器的网络模式改成bridge试试呢 没解决的话你加群明天再详聊吧
> >
> >
> > 辛苦了,这么晚还在回复。我一直用的bridge。大佬,群号多少,不行我明天群里问吧。
>
> [readme](https://github.com/hanxi/xiaomusic?tab=readme-ov-file#-%E8%AE%A8%E8%AE%BA%E5%8C%BA)
已自查解决。问题是账号问题。绑定设备的一定是创建者,不能是管理员。
---
### 评论 12 - McCree2020
这个主题目录不能设置吧?没人遇到这个issue?我原来用的张大妈平台的教程设置的,能用,后来看到这个教程后就修改了后台的路径映射,但是dockers启动正常,网页不能打开提示internal sever error,后来ssh进docker看了日志文件 提示static那个路径有问题,下边的index什么的文件找不到, 删除主题映射以后,重启docker后,网页正常显示了
---
### 评论 13 - 52fisher
> 这个主题目录不能设置吧?没人遇到这个issue?我原来用的张大妈平台的教程设置的,能用,后来看到这个教程后就修改了后台的路径映射,但是dockers启动正常,网页不能打开提示internal sever error,后来ssh进docker看了日志文件 提示static那个路径有问题,下边的index什么的文件找不到, 删除主题映射以后,重启docker后,网页正常显示了
要注意看提示:
装载路径中的 配置文件目录 和 音乐目录 必须进行配置。
其他的路径非必要不要配置,主题目录路径是方便开发调试的时候用的,普通用户不要映射主题目录。我已经把这个提示更新到文档中了
---
### 评论 14 - zxhans
就不能让xiaomusic支持服务器部署吗?服务器部署为啥设备不能读取呢?home assistant 通过xiaomi home assistant都可以读取呀
---
### 评论 15 - hanxi
> 就不能让xiaomusic支持服务器部署吗?服务器部署为啥设备不能读取呢?home assistant 通过xiaomi home assistant都可以读取呀
支持服务器部署的,你需要在服务器上装个浏览器登陆过风控。
---
### 评论 16 - Lunder-R
> > > <img alt="截屏2024-12-05 02 23 53" width="660" src="https://private-user-images.githubusercontent.com/20666294/392519509-b9d26de9-3dcf-4e65-9460-36603735c887.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk1MDktYjlkMjZkZTktM2RjZi00ZTY1LTk0NjAtMzY2MDM3MzVjODg3LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFhN2I4NzQyYjA4YzRiOTk4OTU3NmVkNjU2MjM1ODhjMmNlOWU0YTg5OTFlMzA1NTcxMTA5OTk1YjgwNThhOTgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.yrC-sn2T6PKwBJb_mal1T2yxcSz008Hb7KWVmOe6zaA"> <img alt="截屏2024-12-05 02 24 49" width="780" src="https://private-user-images.githubusercontent.com/20666294/392519852-6a204cdb-bb10-4f35-822d-613aeed0fae0.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzMzMzcwNzEsIm5iZiI6MTczMzMzNjc3MSwicGF0aCI6Ii8yMDY2NjI5NC8zOTI1MTk4NTItNmEyMDRjZGItYmIxMC00ZjM1LTgyMmQtNjEzYWVlZDBmYWUwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDEyMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQxMjA0VDE4MjYxMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE4MzlhODM1NDY4ZjFmN2I2Y2JkZWU5ZGFkNTNjYTNmZDg2OTU3YzA0MjRkMDA2MzRmOTk2ZGE3NmE2NjViZmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0._I8Rll2a96i-cx6WBvEwOclNInfOmZkD5HmcorzT-KI">
> > > > > 确实是变了。192.168.31.143是我电脑的ip。hostname='192.168.31.165'是极空间的。小爱是192.168.31.77。现在我的网络结构是电脑连nas上的istoreos旁路由。nas直连主路由,小爱直连主路由。主dhcp都绑定了。大佬,这种情况该怎么解决呢。所有设置都是默认,没修改哈。
> > > >
> > > >
> > > > 容器的网络模式改成桥试用呢没解决的话你加群明天再详聊吧
> > >
> > >
> > > 辛苦了,今天晚上还在回复。我一直用的桥。大佬,群号多少,不行我明天群里问吧。
> >
> >
> > [自述](https://github.com/hanxi/xiaomusic?tab=readme-ov-file#-%E8%AE%A8%E8%AE%BA%E5%8C%BA)
>
> 自查解决。问题是已账号问题。绑定设备的一定是创建者,不能是管理员。
能不能分享一下解决大概过程,我也是这种情况,谢谢了
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/297)
================================================
FILE: docs/issues/312.md
================================================
---
title: 同步网易云歌单
---
# 同步网易云歌单
推广一波 [netease-playlist](https://github.com/qiujie8092916/netease-playlist)
通过网易云音乐歌单 ID,下载歌曲或[生成歌单配置](/issues/269.html)
本程序为独立模块,可 docker 部署,可作为不用[插件](/issues/105.html)的另外实现方式
## 评论
### 评论 1 - Ghost-Sam1222
什么时候可以同步apple music歌单就真好了
---
### 评论 2 - fxchby
试了一下,似乎用不了
---
### 评论 3 - qiujie8092916
> 试了一下,似乎用不了
哪里有问题
---
### 评论 4 - xcysy32
这能实时同步吗🌹
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/312)
================================================
FILE: docs/issues/333.md
================================================
---
title: 设置项功能介绍
---
# 设置项功能介绍
- XIAOMUSIC_ACTIVE_CMD 环境变量,对应后台的 【允许唤醒的命令】,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指令(播放歌曲和随机播放)可以触发,触发后,xiaomusic进入playing状态,其他指令则可以正常触发。具体见 <https://github.com/hanxi/xiaomusic/pull/43>
- XIAOMUSIC_EXCLUDE_DIRS 配置歌曲目录里需要忽略的目录,对应后台的 【忽略目录】
- XIAOMUSIC_MUSIC_PATH_DEPTH 配置歌曲目录搜索深度,对应后台的 【目录深度】,具体见 </issues/76.html>
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台,对应后台的 【关闭密码验证】,具体见 </issues/47.html>
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户,对应后台的 【控制台账户】
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码,对应后台的 【控制台密码】
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,对应后台的 【配置文件目录】,记得把目录映射到主机,默认为 `/app/config` ,具体见 </issues/74.html>
- XIAOMUSIC_CACHE_DIR 用来音乐 tag 缓存,默认为 `/app/cache`,对应后台的 【缓存文件目录】。
- XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,对应后台的 【关闭下载功能】,见 </issues/82.html>
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,对应后台的 【型号兼容模式】,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 </issues/30.html>
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,对应后台的 【播放歌曲口令】,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,对应后台的 【停止口令】,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,对应后台的 【播放本地歌曲口令】,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
- XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,对应后台的 【开启模糊搜索】,支持模糊匹配歌名和歌单名。 具体见 </issues/52.html>
- XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准,对应后台的 【模糊匹配阈值】。具体见 </issues/52.html>
- XIAOMUSIC_PUBLIC_PORT 用于设置外网端口,对应后台的 【外网访问端口】,当使用反向代理时可以设置为外网端口,XIAOMUSIC_HOSTNAME 设为外网IP或者域名即可。
- XIAOMUSIC_DOWNLOAD_PATH 变量可以配置下载目录,默认为空,表示使用 music 目录为下载目录,对应后台的 【音乐下载目录】。设置这个目录必须是 music 的子目录,否则刷新列表后会找不到歌曲。具体见 </issues/98.html>
- XIAOMUSIC_PROXY 用于配置国内使用 youtube 源下载歌曲时使用的代理,参数格式参考 yt-dlp 文档说明。 见 </issues/2.html> 和 </issues/11.html>
- MIIO_TTS_CMD 用于部分机型(如:`L05C`)使用 MiIO 支持 tts 能力,默认为空,命令选择见 [MiService-fork 文档](https://github.com/yihong0618/MiService)
## 评论
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/333)
================================================
FILE: docs/issues/350.md
================================================
---
title: 播放本地歌曲无法切歌
---
# 播放本地歌曲无法切歌
播放本地歌曲时,通过语音控制小爱音箱切歌(测试指令:小爱同学,切歌;小爱同学,播放下一首;),每次都是重新播放上一首歌曲;版本为docker最新版本[0.3.70]
## 评论
### 评论 1 - Leenshady
测试出来了,只有“小爱同学,下一首”指令才能正常切歌
---
### 评论 2 - sam0773
可以在conf文件夹的setting.json中把自己习惯的命令添加进去...
---
### 评论 3 - hanxi
楼上正解。
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/350)
================================================
FILE: docs/issues/360.md
================================================
---
title: docker compose 命令行安装教程
---
# docker compose 命令行安装教程
本教程针对于有命令行环境,且已经安装好 docker compose 的用户。步骤超级简单,基本只要复制粘贴就能跑起来。如果需要修改路径或者端口,可以复制到编辑器,修改后再复制粘贴到命令行执行。编辑时注意不要修改到文件格式,包括缩进。
## 创建 docker-compose.yml 文件
假设 `docker-compose.yml` 文件的存放到路径为 `/xiaomusic/docker-compose.yml` 。执行下面的命令即可:
```shell
mkdir -p /xiaomusic
cat <<EOF > /xiaomusic/docker-compose.yml
services:
xiaomusic:
image: docker.hanxi.cc/hanxi/xiaomusic
container_name: xiaomusic
restart: unless-stopped
ports:
- 58090:8090
environment:
XIAOMUSIC_PUBLIC_PORT: 58090
volumes:
- /xiaomusic_conf:/app/conf
- /xiaomusic_music:/app/music
EOF
```
- `/xiaomusic_conf` 为配置文件存放目录,一般不需要修改。
- `/xiaomusic_music` 为音乐存放目录,你可以替换为自己想要存放的目录,注意填绝对路径,在 Linux 下是 `/` 开头的,在 Windows 下是盘符开头,比如: `D:/music`。
- 端口 8090 不要修改,是容器内的端口。
- 端口 58090 可以修改,如果想要修改,两个 58090 都需要同时修改,这个端口是访问 web 后台的端口。
## 启动
```shell
cd /xiaomusic
docker compose up -d
```
启动后,就能使用 <http://nasip:58090> 来访问 web 后台了,把 nasip 替换成你的 nas 的 IP 。
## 后台设置

填好账号密码,和自动填 IP 和端口,然后滚动到页面最下面,点击保存按钮。然后刷新设置页面,再勾选小爱音箱,再保存即可。

## 更新
想要更新镜像,只需要复制粘贴下面的命令就行,注意 `/xiaomusic` 目录是 `docker-compose.yml` 文件所在的目录。
```shell
cd /xiaomusic
docker compose pull
docker compose up -d
```
## 关闭
```shell
cd /xiaomusic
docker compose down
```
## 评论
### 评论 1 - tiger326
我用这个是可以正常启动和搭建好xiaomusic, 一切都OK.
不过我的NAS是qnap, 已禁用默认的admin用户, 使用自建的管理员账户登录.
但这个配置会默认使用admin用户执行, 导致一旦产生tmp和download目录以及相关文件, 都归属于admin用户.
结果我自建的用户在NAS上无法删除和编辑这些下载的文件.
xiaomusic是否支持创建时指定用户和用户组?
---
### 评论 2 - hanxi
@tiger326 把下面的 username 换成普通的用户名即可,换成 uid 数字也行,一般是 1000
```shell
mkdir -p /xiaomusic
cat <<EOF > /xiaomusic/docker-compose.yml
services:
xiaomusic:
image: docker.hanxi.cc/hanxi/xiaomusic
container_name: xiaomusic
restart: unless-stopped
user: username
ports:
- 58090:8090
environment:
XIAOMUSIC_PUBLIC_PORT: 58090
volumes:
- /xiaomusic_conf:/app/conf
- /xiaomusic_music:/app/music
EOF
```
---
### 评论 3 - worrywast
为啥我的播放完一首音乐之后,还会播放该歌曲开头一点点才会放下一首呢?
---
### 评论 4 - hanxi
> 为啥我的播放完一首音乐之后,还会播放该歌曲开头一点点才会放下一首呢?
正常现象。可以把延迟设置为0试试。
---
### 评论 5 - worrywast
> > 为啥我的播放完一首音乐之后,还会播放该歌曲开头一点点才会放下一首呢?
>
> 正常现象。可以把延迟设置为0试试。
是这个选项吗:“下一首歌延迟播放秒数:”
我设置为0,也会有这个现象,只是没有之前播放开头那么多
---
### 评论 6 - hanxi
@worrywast 等下个版本我优化一下,允许设置成负数吧。
---
### 评论 7 - worrywast
> [@worrywast](https://github.com/worrywast) 等下个版本我优化一下,允许设置成负数吧。
辛苦大佬~
---
### 评论 8 - hanxi
@worrywast 现在就是可以填负数的,你可以试试看。
---
### 评论 9 - jkjoy
我的一直 暂不支持下载本地 音乐。测试啥的都没问题
---
### 评论 10 - pjlpl
为什么在控制面板可以手动播放本地歌曲,口令点播本地歌曲的时候不管加什么歌名,都回复本地不存在这个歌,但是口令说播放本地歌曲就会随机播放歌曲
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/360)
================================================
FILE: docs/issues/365.md
================================================
---
title: 已知 ttsCommand
---
# 已知 ttsCommand
参考: https://github.com/idootop/mi-gpt/blob/main/docs/compatibility.md
- 小爱音箱 Pro LX06 `5-1`
- 小爱音箱 mini LX01 `5-1`
- 小爱音箱 Play(2019 款) LX05 `5-1`
- 小爱音箱 万能遥控版 LX5A `5-1`
- 小米 AI 音箱 S12 `5-1`
- 小米 AI 音箱(第二代) L15A `7-3`
- 小爱智能家庭屏 10 X10A `7-3`
- Xiaomi Sound Pro L17A `7-3`
- 小爱音箱 L06A `5-1`
- 小爱音箱 Play L05B `5-3`
- 小米小爱音箱 Play 增强版 L05C `5-3`
- Xiaomi 智能家庭屏 6 X6A `7-3`
- Redmi 小爱触屏音箱 Pro 8 英寸 X08E `7-3`
- 小爱音箱 Art L09A `3-1`
- 小爱触屏音箱 LX04 `5-1`
如果你是其他型号的小爱音箱,且不能语音转文字播放,欢迎分享你的型号的 ttsCommand 。
## 评论
### 评论 1 - hanxi
可以不用手动配置了,写到代码里了。
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/365)
================================================
FILE: docs/issues/366.md
================================================
---
title: 多设备单独播放功能设计
---
# 多设备单独播放功能设计
## 分组功能
为设备设计分组功能,可以把一个或者多个设备加入到一个分组,一个分组内的设备会被控制同时播放。设备的音量需要支持独立配置。
分组用 group_list 字段配置,比如 `did1:客厅,did2:客厅` 表示 did1和did2用同一个组名。不配置这个参数就说明一个设备一个分组。后台勾选设备的意图改为哪些设备可以接入。比如:
`319762914:a,319518426:a,1236547:b`
单个设备的存储配置结构如下:
```json
{
"devices": {
"10086": {
"cur_music": "当前播放的歌曲",
"cur_playlist": "当前播放的列表",
"name": "客厅的小爱1",
"play_type": 1
},
"10087": {
"cur_music": "当前播放的歌曲",
"cur_playlist": "当前播放的列表",
"name": "客厅的小爱2",
"play_type": 1
}
}
}
```
设备名字从音箱app设置里读取过来,不用在后台修改。
配置采用组名相同为一组,可以考虑不设置组名就用设备名作为组名,也就是一个设备为一组。
后端提供的接口都改为支持操作多个设备,
单曲循环,随机播放这些操作都会同步修改同一个组里的其他设备。
歌单列表所有设备共享,当前选择的歌单和当前播放的歌曲组内共享,一个组共用一份播放列表,也就是共用一个定时器。
## 主页设备切换
主页主要是用于播放操作的,所有操作都是针对于单个设备的,所以在顶部加入一个切换设备的功能。除了修改声音不会同步修改组内设备,其他操作都会同时操作组内所有设备。
> /issues/65.html#issuecomment-2215736529
## 评论
### 评论 1 - jason856856842388
请问家里有多个小爱,如何让每个小爱都可以拥有这个功能
---
### 评论 2 - hanxi
> 请问家里有多个小爱,如何让每个小爱都可以拥有这个功能
没理解哪个功能?
---
### 评论 3 - jason856856842388
做得好
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/366)
================================================
FILE: docs/issues/389.md
================================================
---
title: 搞了个入门配置教程
---
# 搞了个入门配置教程
官方文档不直观啊,得摸索好久才搞定,第一步怎么设置我找了好久,哈哈哈。
写了个分3步的教程,希望有帮助吧: [xiaomusic 安装、配置和使用教程,免费用小爱音箱无限播放音乐](https://garymeng.com/cn/3317.html)。
## 评论
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/389)
================================================
FILE: docs/issues/398.md
================================================
---
title: docker 镜像源收集
---
# docker 镜像源收集
- https://docker.1ms.run/
- https://dockermirror.com
## 评论
### 评论 1 - coolxy
自建代理:
* https://docker.coolxy.cn/
* https://docker.qqai.ren/
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/398)
================================================
FILE: docs/issues/417.md
================================================
---
title: Jellyfin歌单同步到映射目录
---
# Jellyfin歌单同步到映射目录
因为我一直是用jellyfin 听歌, 下载的都是在Pt网站 按照歌手几十张cd一起下载,所以有非常多不想听的歌,于是歌单功能非常重要,既然Jellyfin本上就创建了歌单,我就写一个Python 来同步这个歌单,大家放在 C:\ProgramData\Jellyfin\Server\data\playlists 或者自定义的playlists文件夹中.
```
import os
import xml.etree.ElementTree as ET
import shutil
#这个文件要放在Jellyfin的C:\ProgramData\Jellyfin\Server\data\playlists 中
#这里要改成想要硬链接到的目录地址
folder_path = r'E:\music2\歌曲列表'
def create_hard_links_from_xml(file_path):
# 解析 XML 文件
tree = ET.parse(file_path)
root = tree.getroot()
# 获取 LocalTitle 标签的值
local_title = root.find('LocalTitle').text
# 构建目标目录路径
target_dir = os.path.join(folder_path, local_title)
# 检查目标目录是否存在,如果不存在则创建
if not os.path.exists(target_dir):
os.makedirs(target_dir)
# 遍历所有 PlaylistItem 节点
for playlist_item in root.findall('.//PlaylistItem'):
# 获取 Path 标签的值
file_path = playlist_item.find('Path').text
if os.path.exists(file_path):
# 获取文件名
file_name = os.path.basename(file_path)
target_file = os.path.join(target_dir, file_name)
try:
# 创建硬链接
os.link(file_path, target_file)
print(f"成功为 {file_path} 创建硬链接到 {target_file}")
except FileExistsError:
print(f"目标文件 {target_file} 已存在,跳过。")
except Exception as e:
print(f"为 {file_path} 创建硬链接时出错: {e}")
else:
print(f"源文件 {file_path} 不存在,跳过。")
shutil.rmtree(folder_path)
# 检查文件夹是否存在
if not os.path.exists(folder_path):
# 创建单层或多层文件夹
os.makedirs(folder_path)
print(f"文件夹 '{folder_path}' 创建成功")
def print_xml_content():
# 获取当前目录
current_dir = os.getcwd()
# 遍历当前目录及其所有子目录
for root, dirs, files in os.walk(current_dir):
for file in files:
# 检查文件扩展名是否为 .xml
if file.lower().endswith('.xml'):
file_path = os.path.join(root, file)
create_hard_links_from_xml(file_path)
if __name__ == "__main__":
print_xml_content()
```
## 评论
### 评论 1 - colaKot
创建一个xmllink.py 文件 然后把代码放进去,双击运行就行,当然要装python
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/417)
================================================
FILE: docs/issues/520.md
================================================
---
title: 局域网 ip 问题,排查了两个小时。希望能对大家有所帮助
---
# 局域网 ip 问题,排查了两个小时。希望能对大家有所帮助
家里使用了两个路由器
- A 路由器 192.168.1.x
- B 路由器 192.168.31.x
- 在电脑上 docker 启动,连接`B路由器的网络` 要将 `XIAOMUSIC_HOSTNAME(IP或域名):`这一项设置为`B路由器的网络` 也就是`192.168.1.x`
- A路由器同理
## 评论
### 评论 1 - hanxi
FAQ有写的,不会配跨网段访问的话,就保持在同一个网段的同局域网就没问题的。会配多网段互通自然就没啥问题。
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/520)
================================================
FILE: docs/issues/533.md
================================================
---
title: 反向代理因为html中绝对路径导致超链接失效
---
# 反向代理因为html中绝对路径导致超链接失效
反向代理因为html中绝对路径导致超链接失效
## 评论
### 评论 1 - wuxinyumrx
更新一下解决方案,nginx反向代理配置如下(需要nginx包含http_sub_module):
```
proxy_http_version 1.1;
proxy_pass http://xx.xx.xx.xx:58090/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
proxy_set_header Accept-Encoding "";
sub_filter_types *;
sub_filter_once off;
sub_filter '/ws/' '/xm/ws/';
sub_filter '/static/' '/xm/static/';
sub_filter '/musiclist' '/xm/musiclist';
sub_filter '/musicinfo' '/xm/musicinfo';
sub_filter '/curplaylist' '/xm/curplaylist';
sub_filter '/generate_ws_token' '/xm/generate_ws_token';
sub_filter '/getversion' '/xm/getversion';
sub_filter '/getsetting' '/xm/getsetting';
sub_filter '/getvolume' '/xm/getvolume';
sub_filter '/setvolume' '/xm/setvolume';
sub_filter '/playingmusic' '/xm/playingmusic';
sub_filter '/searchmusic' '/xm/searchmusic';
sub_filter '/latestversion' '/xm/latestversion';
sub_filter '/cmd' '/xm/cmd';
sub_filter '"musiclist' '"xm/musiclist';
sub_filter '"musicinfo' '"xm/musicinfo';
sub_filter '"curplaylist' '"xm/curplaylist';
sub_filter '"generate_ws_token' '"xm/generate_ws_token';
sub_filter '"getversion' '"xm/getversion';
sub_filter '"getsetting' '"xm/getsetting';
sub_filter '"getvolume' '"xm/getvolume';
sub_filter '"setvolume' '"xm/setvolume';
sub_filter '"playingmusic' '"xm/playingmusic';
sub_filter '"searchmusic' '"xm/searchmusic';
sub_filter '"latestversion' '"xm/latestversion';
sub_filter '"cmd' '"xm/cmd';
sub_filter '/manifest.json' '/xm/manifest.json';
sub_filter '"start_url": "/"' '"start_url": "/xm/"';
sub_filter '"scope": "/"' '"scope": "/xm/"';
```
---
### 评论 2 - wooodxi
首先感谢各位大佬辛苦的付出。
我是家庭使用的威联通NAS部署的xiaomusic,NAS上也是使用的docker部署的是frpc客户端(nas上填写对应的端口号),在云服务使用docker部署的frps服务端(填写二级ssl域名)这样的反向代理,部署好以后外网通过链接https:xxx.xxx.xx是可以访问xiaomusic,播放歌曲音箱却没有声音,但是你们的测试播放连接是可以播放音乐, 包括点击停止播放音箱也是可以回应听到音箱发出再见收到的声音,就是播放歌曲音箱不出声。希望可以得到解答。 谢谢大佬
---
### 评论 3 - hanxi
@wooodxi 需要改设置页面的ip和端口。
---
### 评论 4 - 13267740993
nginx 502 要关闭浏览器再打开才行 帮忙看下啥原因
server {
listen 80;
server_name www.music.cn;
# ================================
# 根路径:原始反向代理(不做路径替换)
# ================================
location / {
# 反向代理到本机 58090 服务
proxy_pass http://127.0.0.1:58090;
# 使用 HTTP/1.1,避免 keep-alive / chunked 问题
proxy_http_version 1.1;
# 清空 Connection 头,交由 Nginx 自动管理
proxy_set_header Connection "";
# 转发客户端真实信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 连接 / 读取 / 发送超时设置,防止 502
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
# 上传文件大小限制
client_max_body_size 100m;
}
# =================================================
# /xm/ 路径:子路径部署(带 WebSocket + sub_filter)
# =================================================
location /xm/ {
# 反向代理到后端服务根路径
proxy_pass http://127.0.0.1:58090/;
# 使用 HTTP/1.1(WebSocket 必须)
proxy_http_version 1.1;
# WebSocket 升级头
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 转发客户端信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 禁用 gzip,否则 sub_filter 无法生效
proxy_set_header Accept-Encoding "";
# ========== sub_filter 内容替换 ==========
# 对所有响应类型生效(HTML / JS / JSON)
sub_filter_types *;
# 页面中出现多次也全部替换
sub_filter_once off;
# 接口路径统一加 /xm 前缀
sub_filter '"/' '"/xm/';
sub_filter "'/" "'/xm/";
# WebSocket 路径修正
sub_filter '/ws/' '/xm/ws/';
# 静态资源路径修正
sub_filter '/static/' '/xm/static/';
# PWA / manifest 相关路径修正
sub_filter '/manifest.json' '/xm/manifest.json';
sub_filter '"start_url": "/"' '"start_url": "/xm/"';
sub_filter '"scope": "/"' '"scope": "/xm/"';
# ========== CORS 跨域配置 ==========
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always;
add_header Access-Control-Allow-Headers
'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'
always;
# OPTIONS 预检请求直接返回
if ($request_method = OPTIONS) {
return 204;
}
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
# 上传大小限制
client_max_body_size 100m;
}
# ================================
# 502 错误页
# ================================
error_page 502 /502.html;
}
---
### 评论 5 - hanxi
我也不熟 nginx 配置,建议使用 https://github.com/jc21/nginx-proxy-manager 配置 proxy ,简单的 GUI 操作就行。
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/533)
================================================
FILE: docs/issues/595.md
================================================
---
title: Docker 镜像推荐
---
# Docker 镜像推荐
监控网站:https://status.anye.xyz/
<img width="1498" height="958" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/77270bec-5c92-4c73-a6c2-b21c4d655705" />
## 评论
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/595)
================================================
FILE: docs/issues/600.md
================================================
---
title: 1Panel 安装运行 xiaomusic 教程
---
# 1Panel 安装运行 xiaomusic 教程
## 1Panel 安装运行
本教程将指导您如何在 1Panel 面板中安装和配置 XiaoMusic。
### 前提条件
在开始安装前,请确保您已完成以下准备工作:
- 已安装并运行 1Panel 面板
如需详细安装 1Panel 面板,请参考 [1Panel 官方安装文档](https://1panel.cn/docs/v2/installation/online_installation/)
安装完成后,通过提示的**访问地址**和**初始账号密码**登录 1Panel。
### 安装 XiaoMusic
登录 1Panel,进入 **应用商店**,搜索 **xiaomusic**,点击**安装**即可。
#### 配置参数说明
安装时请根据实际需求配置以下参数:
- **高级设置**:根据实际需要勾选**端口外部访问**,勾选后即可通过外部 IP 访问 XiaoMusic。
> 保持**默认配置**也可以完成安装,但建议根据实际需求调整。
### 访问 XiaoMusic
安装完成后,进入 1Panel 的 **已安装** 页面,点击 **跳转** 即可进入 XiaoMusic 的 **WebUI** 页面。
> 使用前建议在 **面板设置** 页面设置好**默认访问地址**。
> 如果后续配置了**反向代理**,可以在 `已安装 → 参数` 页面修改 **Web 访问地址**。
## 评论
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/600)
================================================
FILE: docs/issues/637.md
================================================
---
title: Jellyfin 插件将音乐歌单转成支持xiaomusic的网络歌单
---
# Jellyfin 插件将音乐歌单转成支持xiaomusic的网络歌单
Jellyfin插件安装仓库地址:https://raw.githubusercontent.com/shaoqilan/jellyfin-plugin-xiaomusic/refs/heads/main/10.11/manifest.json
使用方式:https://github.com/shaoqilan/jellyfin-plugin-xiaomusic
希望 #616 能尽早支持,这样可以避免手动更新歌单,歌单可以实时和Jellyfin音乐库保持一致
## 评论
### 评论 1 - hanxi
测试版已经实现了,可以体验一下看看效果。
---
### 评论 2 - Akari787
使用这个插件播放jellyfin的歌单时,如果是m4a格式的音乐文件会被识别成mp4,导致无法正常播放。
`wget -S -O /dev/null "http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true"
--2026-01-12 14:19:32-- http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true
Connecting to 192.168.7.4:30013... connected.
HTTP request sent, awaiting response...
HTTP/1.1 200 OK
Content-Length: 16503536
Content-Type: audio/mp4
Date: Mon, 12 Jan 2026 05:19:32 GMT
Server: Kestrel
Accept-Ranges: bytes
Last-Modified: Sun, 16 Mar 2025 23:01:54 GMT
X-Response-Time-ms: 1.9493
Length: 16503536 (16M) [audio/mp4]
Saving to: '/dev/null'
/dev/null 100%[===============================================================================================================================================>] 15.74M --.-KB/s in 0.01s
2026-01-12 14:19:32 (1.51 GB/s) - '/dev/null' saved [16503536/16503536]`
---
### 评论 3 - shaoqilan
> 使用这个插件播放jellyfin的歌单时,如果是m4a格式的音乐文件会被识别成mp4,导致无法正常播放。 `wget -S -O /dev/null "http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true" --2026-01-12 14:19:32-- http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true Connecting to 192.168.7.4:30013... connected. HTTP request sent, awaiting response... HTTP/1.1 200 OK Content-Length: 16503536 Content-Type: audio/mp4 Date: Mon, 12 Jan 2026 05:19:32 GMT Server: Kestrel Accept-Ranges: bytes Last-Modified: Sun, 16 Mar 2025 23:01:54 GMT X-Response-Time-ms: 1.9493 Length: 16503536 (16M) [audio/mp4] Saving to: '/dev/null'
>
> /dev/null 100%[===============================================================================================================================================>] 15.74M --.-KB/s in 0.01s
>
> 2026-01-12 14:19:32 (1.51 GB/s) - '/dev/null' saved [16503536/16503536]`
这可能和源文件m4a内封装的编码有关系,能否提供一下源文件?
---
### 评论 4 - Akari787
> > 使用此插件播放jellyfin的歌曲单时,如果是m4a格式的音乐文件会被识别成mp4,导致无法正常播放。 `wget -S -O /dev/null " http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true " --2026-01-12 14:19:32-- http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true正在连接到 192.168.7.4:30013...已连接。已发送 HTTP 请求,正在等待响应... HTTP/1.1 200 OK Content-Length: 16503536 Content-Type: audio/mp4 Date: Mon, 12 Jan 2026 05:19:32 GMT Server: Kestrel Accept-Ranges: bytes Last-Modified: Sun, 16 Mar 2025 23:01:54 GMT X-Response-Time-ms: 1.9493 Length: 16503536 (16M) [audio/mp4] Saving to: '/dev/null'
> > /dev/null 100%[=================================================================================================================================================================================>] 15.74M --.-KB/s 用时 0.01s
> > 2026-01-12 14:19:32 (1.51 GB/s) - '/dev/null' 已保存 [16503536/16503536]
>
> 这可能和源文件m4a内部封装的编码有关系,能否提供一下源文件?
以这首【Alice in 冷凍庫】为例。这整个专辑里面的歌曲都无法播放。
链接可能打开比较慢,如果有IPv6会稍微快一点。
https://nextcl.asilante.xyz/s/3pX326jHfrcwJKY
---
### 评论 5 - shaoqilan
> > > 使用此插件播放jellyfin的歌曲单时,如果是m4a格式的音乐文件会被识别成mp4,导致无法正常播放。 `wget -S -O /dev/null " http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true " --2026-01-12 14:19:32-- [http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true正在连接到](http://192.168.7.4:30013/Audio/d7d0d9f6-a856-ab68-7725-ab03419eef59/stream?static=true%E6%AD%A3%E5%9C%A8%E8%BF%9E%E6%8E%A5%E5%88%B0) 192.168.7.4:30013...已连接。已发送 HTTP 请求,正在等待响应... HTTP/1.1 200 OK Content-Length: 16503536 Content-Type: audio/mp4 Date: Mon, 12 Jan 2026 05:19:32 GMT Server: Kestrel Accept-Ranges: bytes Last-Modified: Sun, 16 Mar 2025 23:01:54 GMT X-Response-Time-ms: 1.9493 Length: 16503536 (16M) [audio/mp4] Saving to: '/dev/null'
> > > /dev/null 100%[=================================================================================================================================================================================>] 15.74M --.-KB/s 用时 0.01s
> > > 2026-01-12 14:19:32 (1.51 GB/s) - '/dev/null' 已保存 [16503536/16503536]
> >
> >
> > 这可能和源文件m4a内部封装的编码有关系,能否提供一下源文件?
>
> 以这首【Alice in 冷凍庫】为例。这整个专辑里面的歌曲都无法播放。 链接可能打开比较慢,如果有IPv6会稍微快一点。 https://nextcl.asilante.xyz/s/3pX326jHfrcwJKY
我用该文件做了测试,是可以正常播放的
<img width="405" height="619" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/9cf89b94-b4fd-4283-a44a-af4d63d836fe" />
以上是测试的版本
音响是L05B
如果音响不支持音频格式需要jellyfin做转码,可以将插件升级到1.0.0.2版本。在插件设置中做一下配置:
<img width="956" height="878" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/59d14057-46f7-4a34-9f20-99dde6a803ac" />
以上设置示例,是会将*.m4a后缀的文件以mp3格式进行输出
---
[Issue 链接](https://github.com/hanxi/xiaomusic/issues/637)
================================================
FILE: docs/issues/688.md
================================================
---
title: 使用cookie登陆
---
# 使用cookie登陆
> 使用 cookie 登录时不要写填账号密码
## 步骤
1. 在电脑上使用 Chrome 浏览器登录小米账号官网 👉 <https://account.xiaomi.com/>
2. 按 `F12` 打开网络工具,点击 `Network` 标签页。
3. 点击左上角小圆点使其变红色。
4. 按 `F5` 刷新页面。
5. 在过滤框里输入 `account?` 过滤出下面的连接,点击连接查看 `cookie` ,复制完整的 `cookie` 值。需要确保复制的 `cookie` 中有 `userId` 和 `passToken` 两个值。
6. 粘贴到设置页面。
## 步骤截图

<img width="758" height="166" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/b53de6d9-d4fd-4cfc-8dd3-6f8f4f63de69" />
## 评论
### 评论 1 - seryte
0.4.12版本测试了使用cookie也无效,是不是cookie还未正确读取?
<img width="1017" height="374" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/8556270f-b2f3-4b07-a951-656a9b76743c" />
<img width="1263" height="309" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/9bd7be41-df3d-4870-a879-66e2a3ce9575" />
---
### 评论 2 - WGKER
局域网内电脑获取完整cookie填入,并且也填写账户密码,仍然无法获取设备。
可行的话还是换成跳转小米账户授权登录的方式吧,类似小米官方HA插件,目前太折腾了。
---
### 评论 3 - gdzjy
0.4.12登录无效,找不到音箱设备。
<img width="1875" height="697" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/4d5c8bd6-fe00-4dca-b4bc-f9539f16790b" />
---
### 评论 4 - swxk521
0.4.12一样不行
---
### 评论 5 - gdzjy
发现回到旧版本也找不到设备了,不知是不是小米有ban账号的机制,日志如下:
<img width="1621" height="523" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/848b635d-34d2-458b-b9b8-3d382ef8a52c" />
<img width="3029" height="1256" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/06cecd23-58ca-426b-86f3-465b04515b9e" />
---
### 评论 6 - hanxi
文档更新了,截图还没更新,可以先看。
---
### 评论 7 - hanxi
下个版本可以不用填账号密码,目前0.4.12版本有点bug需要填。
---
### 评论 8 - seryte
> 文档更新了,截图还没更新,可以先看。
也还是不行 😂
<img width="1896" height="529" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/b3eb56bd-a22f-4664-848d-95ae87262528" />
<img width="983" height="769" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/1669bdf3-f5b5-4285-a5c0-d8b9d9c21cff" />
---
### 评论 9 - hanxi
需要填账号,或者用测试版本。
---
### 评论 10 - seryte
> 需要填账号,或者用测试版本。
`hanxi/xiaomusic main d1d275ae0e98 About an hour ago 341MB`
测试版本能发现设备,但是还是无法播放本地歌曲,播放歌曲时会再次登录失败, 然后没过2分钟,设备再次消失,日志出现需要验证码的信息
<img width="580" height="625" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/d8f4bf43-7333-4146-98b6-0e5a2335b225" />
<img width="1894" height="947" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/c075364e-9624-4a04-9486-208e246d00ac" />
<img width="1539" height="127" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/3ca27f19-f317-4f69-b723-7ea624db78ff" />
---
### 评论 11 - sam0773
填写cookies时候要加上_utm_data={""}吗?直接复制进去保存时候会弹出[object object]
---
### 评论 12 - figsong

0.4.13版单用cookies、cookies加上账号密码依然不能发现设备
---
### 评论 13 - hanxi
需要确保复制的 cookie 中有 userId 和 passToken 两个值。
---
### 评论 14 - hanxi
需要确保复制的 cookie 中有 userId 和 passToken 两个值。
---
### 评论 15 - dyz312
仅使用cookie找的设备了,感谢!
---
### 评论 16 - xpeagle
4.14也是显示不能找到设备,不知道咋回事,cookie也是填了的, cookie 中有 userId 和 passToken 两个值的,但是还是不能找到设备哦
<img width="1045" height="702" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/c4cf7f46-3391-44b6-bd6b-cc6b24948857" />
---
### 评论 17 - xpeagle
> 仅使用cookie找的设备了,感谢!
我也是试了下仅用cookie,但是还是找不到设备,郁闷了
---
### 评论 18 - zzfca
使用仅cookie登陆,终于链接到小爱音箱了! 谢谢谢谢
---
### 评论 19 - TangTang0812
有个问题大概播放几首歌后,会触发账号安全机制,控制台会提示“验证码输入错误”,然后就需要再次去官网过下人机验证,再到xiaomusic中重新配置cookie,是不是cookie容易被Mi ban 导致生命周期挂了,总的来说还是不错,能正常识别设备了;感谢大佬的倾情奉献 ❤
---------------------------------
。。。。。。。。。
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] device_player.py:226: play. names:['告白气球周杰伦'] 1
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] device_player.py:912: cancel_group_next_timer {'981327057': <xiaomusic.device_player.XiaoMusicDevice object at 0xffff8f736780>}
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] device_player.py:897: cancel_next_timer did: 981327057
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] device_player.py:907: 下一曲定时器不见了 did: 981327057
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] device_player.py:357: cur_music 告白气球周杰伦
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] music_url.py:60: get_music_url name:告白气球周杰伦
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] music_library.py:586: try get_filename. filename:music/download/告白气球周杰伦.mp3
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] music_url.py:139: _get_local_music_url local music. name:告白气球周杰伦, filename:music/download/告白气球周杰伦.mp3
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] music_url.py:162: _get_file_url filepath:music/download/告白气球周杰伦.mp3, filename:download/告白气球周杰伦.mp3
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] music_url.py:53: get_music_sec_url. name:告白气球周杰伦 url:http://192.168.6.167:58090/music/download/%E5%91%8A%E7%99%BD%E6%B0%94%E7%90%83%E5%91%A8%E6%9D%B0%E4%BC%A6.mp3 origin_url:None
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:46] [0.4.12] [INFO] device_player.py:872: group_force_stop_xiaoai Xiaomi 智能音箱 Pro ['70ba747b-af44-4e41-bc49-b9da586cf770']
1Panel-xiaomusic-1n74 | Exception on login 2340782246: {'code': 87001, 'desc': '验证码输入错误', 'captchaUrl': '/pass/getCode?icodeType=login&0.754211913663798', 'type': 'manMachine'}
1Panel-xiaomusic-1n74 | Traceback (most recent call last):
1Panel-xiaomusic-1n74 | File "/app/.venv/lib/python3.14/site-packages/miservice/miaccount.py", line 72, in login
1Panel-xiaomusic-1n74 | raise Exception(resp)
1Panel-xiaomusic-1n74 | Exception: {'code': 87001, 'desc': '验证码输入错误', 'captchaUrl': '/pass/getCode?icodeType=login&0.754211913663798', 'type': 'manMachine'}
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:47] [0.4.12] [WARNING] device_player.py:424: Execption Error https://api2.mina.mi.com/remote/ubus: Login failed
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:47] [0.4.12] [INFO] device_player.py:875: group_force_stop_xiaoai ['70ba747b-af44-4e41-bc49-b9da586cf770'] [None]
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:47] [0.4.12] [INFO] device_player.py:362: 播放 http://192.168.6.167:58090/music/download/%E5%91%8A%E7%99%BD%E6%B0%94%E7%90%83%E5%91%A8%E6%9D%B0%E4%BC%A6.mp31Panel-xiaomusic-1n74 | Exception on login 2340782246: {'code': 87001, 'desc': '验证码输入错误', 'captchaUrl': '/pass/getCode?icodeType=login&0.38010189998178023', 'type': 'manMachine'}
1Panel-xiaomusic-1n74 | Traceback (most recent call last):
1Panel-xiaomusic-1n74 | File "/app/.venv/lib/python3.14/site-packages/miservice/miaccount.py", line 72, in login
1Panel-xiaomusic-1n74 | raise Exception(resp)
1Panel-xiaomusic-1n74 | Exception: {'code': 87001, 'desc': '验证码输入错误', 'captchaUrl': '/pass/getCode?icodeType=login&0.38010189998178023', 'type': 'manMachine'}
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:47] [0.4.12] [ERROR] device_player.py:730: Execption Error https://api2.mina.mi.com/remote/ubus: Login failed
1Panel-xiaomusic-1n74 | Traceback (most recent call last):
1Panel-xiaomusic-1n74 | File "/app/xiaomusic/device_player.py", line 718, in play_one_url
1Panel-xiaomusic-1n74 | ret = await self.auth_manager.mina_service.play_by_music_url(
1Panel-xiaomusic-1n74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1Panel-xiaomusic-1n74 | device_id, url, audio_id=audio_id
1Panel-xiaomusic-1n74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1Panel-xiaomusic-1n74 | )
1Panel-xiaomusic-1n74 | ^
1Panel-xiaomusic-1n74 | File "/app/.venv/lib/python3.14/site-packages/miservice/minaservice.py", line 206, in play_by_music_url
1Panel-xiaomusic-1n74 | return await self.ubus_request(
1Panel-xiaomusic-1n74 | ^^^^^^^^^^^^^^^^^^^^^^^^
1Panel-xiaomusic-1n74 | ...<4 lines>...
1Panel-xiaomusic-1n74 | )
1Panel-xiaomusic-1n74 | ^
1Panel-xiaomusic-1n74 | File "/app/.venv/lib/python3.14/site-packages/miservice/minaservice.py", line 47, in ubus_request
1Panel-xiaomusic-1n74 | result = await self.mina_request(
1Panel-xiaomusic-1n74 | ^^^^^^^^^^^^^^^^^^^^^^^^
1Panel-xiaomusic-1n74 | ...<2 lines>...
1Panel-xiaomusic-1n74 | )
1Panel-xiaomusic-1n74 | ^
1Panel-xiaomusic-1n74 | File "/app/.venv/lib/python3.14/site-packages/miservice/minaservice.py", line 37, in mina_request
1Panel-xiaomusic-1n74 | return await self.account.mi_request(
1Panel-xiaomusic-1n74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1Panel-xiaomusic-1n74 | "micoapi", "https://api2.mina.mi.com" + uri, data, headers
1Panel-xiaomusic-1n74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1Panel-xiaomusic-1n74 | )
1Panel-xiaomusic-1n74 | ^
1Panel-xiaomusic-1n74 | File "/app/.venv/lib/python3.14/site-packages/miservice/miaccount.py", line 155, in mi_request
1Panel-xiaomusic-1n74 | raise Exception(f"Error {url}: {resp}")
1Panel-xiaomusic-1n74 | Exception: Error https://api2.mina.mi.com/remote/ubus: Login failed
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:47] [0.4.12] [INFO] device_player.py:700: group_player_play http://192.168.6.167:58090/music/download/%E5%91%8A%E7%99%BD%E6%B0%94%E7%90%83%E5%91%A8%E6%9D%B0%E4%BC%A6.mp3 ['70ba747b-af44-4e41-bc49-b9da586cf770'] [None]
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:47] [0.4.12] [INFO] device_player.py:366: 播放 告白气球周杰伦 失败. 失败次数: 10
1Panel-xiaomusic-1n74 | [2026-01-17 13:06:48] [0.4.12] [INFO] 192.168.6.205:2582 - "POST /playmusiclist HTTP/1.1" 200
。。。。。。。。
---------------------------------
<img width="1070" height="1737" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/dc119eb8-f988-4cf8-a322-452c5c6cf8ee" />
<img width="499" height="808" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/49123fe0-9a42-4595-8c6c-cf6cc06bb032" />
<img width="1357" height="1291" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/9d930d4c-36bb-45f3-8ac3-a11033027962" />
---
### 评论 20 - hanxi
@TangTang0812 把对话记录关掉会好一点。
---
### 评论 21 - ideajoker
<img width="2260" height="1173" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/275debe0-9058-48ad-8c6a-a3ba3eaf5105" />
<img width="1236" height="701" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/80f152fa-1b1c-4130-8a4e-c40e8cbe2ef6" />
0.4.16版本还是无法识别设备。
按照/issues/99.html里面的方法试了
1、官网重新登陆过了
2、小米音响APP也重新登录过了
3、cookie也填入了
群辉NAS里面不知道怎么把网络由bridge改为host,这个还没试。 有没有类似的情况?
---
### 评论 22 - zmz163
试了很多次都不能查找设备,局域网内电脑获取完整cookie填入,并且也填写账户密码,仍然无法获取设备。
[xiaomusic (1).txt](https://github.com/user-attachments/files/24761966/xiaomusic.1.txt)
---
### 评论 23 - sam0773
如果用cookies就不要填写账号和密码
---原始邮件---
发件人: ***@***.***>
发送时间: 2026年1月21日(周三) 下午5:13
收件人: ***@***.***>;
抄送: ***@***.******@***.***>;
主题: Re: [hanxi/xiaomusic] 使用cookie登陆 (Issue #688)
zmz163 left a comment (hanxi/xiaomusic#688)
试了很多次都不能查找设备,局域网内电脑获取完整cookie填入,并且也填写账户密码,仍然无法获取设备。
xiaomusic (1).txt
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
---
### 评论 24 - zmz163
> 如果用cookies就不要填写账号和密码
> […](#)
只用cookie登录也不行
---
### 评论 25 - w0000dy
> > 如果用cookies就不要填写账号和密码
> > […](#)
>
> 只用cookie登录也不行
填账号密码 账号别用手机号,我一开始手机号不行 使用ID号 然后去网页登录验证 刷新就有了,可以的话 先去你docker配置的config文件夹下东西都删除 登陆账号后再重新配置下
---
### 评论 26 - zmz163
> > > 如果用cookies就不要填写账号和密码
> > > […](#)
> >
> >
> > 只用cookie登录也不行
>
> 填账号密码 账号别用手机号,我一开始手机号不行 使用ID号 然后去网页登录验证 刷新就有了,可以的话 先去你docker配置的config文件夹下东西都删除 登陆账号后再重新配置下
试了很多次,还是不行
---
### 评论 27 - ideajoker
<img width="1600" height="896" alt="Image" src="https://gproxy.hanxi.cc/proxy/user-attachments/assets/69ff2cb0-f09f-4390-8aee-9743ee608f6f" />
2026年1月22日12:30测试反馈:
群辉NAS,把项目docker配置目录config文件夹下的内容全都删除,
重新拉取最新版本构建一下,再登录后台,可以先Ctrl+F5一下刷新出新版后台。
账号登录页面删掉手机号,换成ID,密码留空。
然后使用cookie登录,保存之后,就能识别到设备了。
---
### 评论 28 - yangjianxun36
大佬现在有个新问题,不知道是我的个例还是设备问题。家里有两台小米设备,一台米家音箱,一台触屏音箱,都是初版。米家音箱各项功能都正常,包括用风花雪月app控制播放暂停。但是触屏音箱有问题,在网页后台控制,点击播放暂停都可以正常用,但是停止的时候音箱没有停止语音反馈(米家音箱是有的),但是用着也还是没问题,用风花雪月app就有问题了,点播放可以放歌,但是一点就没反应,而且点了以后再网页后台也无法控制了,播放时间会开始一直跳,后台两个音箱都无法被发现,只能重启容器后正常。但是一通过风花雪月控制就会重复出现上面的问题。只能再次重启容器
---
### 评论 29 - b2522
用这个网址的token可以登录, 里面的userid换成自己的
https://account.xiaomi.com/fe/service/account?userId=11111&_locale=zh_CN
---
### 评论 30 - chenxixian
我用的pip instal方式安装和更新xiaomusic在Mac上的,之前帐号密码也是时不时有问题,改用cookie登录会正常,但Mac的IP有时会变,所以又加了个启动脚本,IP没变直接启动xiaomusic,IP变了修改好conf/setting.json后,再启动xiaomusic。
start_xiaomusic.sh
```
#!/bin/bash
# xiaomusic启动脚本
# 功能:检查IP配置,如不一致则更新,然后启动xiaomusic
# 设置脚本目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SETTING_FILE="$SCRIPT_DIR/conf/setting.json"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查配置文件是否存在
if [ ! -f "$SETTING_FILE" ]; then
log_error "配置文件不存在: $SETTING_FILE"
exit 1
fi
# 获取当前无线网卡IP (en0)
get_current_ip() {
# 尝试获取en0的IPv4地址
local ip=$(ifconfig en0 2>/dev/null | grep "inet " | awk '{print $2}' | head -1)
if [ -z "$ip" ]; then
# 如果en0没有IP,尝试其他网络接口
ip=$(ifconfig | grep "inet " | grep -v "127.0.0.1" | awk '{print $2}' | head -1)
fi
echo "$ip"
}
# 获取配置文件中的hostname IP
get_config_ip() {
local ip=$(grep '"hostname"' "$SETTING_FILE" | sed 's/.*"hostname": *"http:\/\///' | sed 's/".*//' | tr -d ' ')
echo "$ip"
}
# 更新配置文件中的IP
update_config_ip() {
local new_ip="$1"
log_info "更新配置文件中的IP为: $new_ip"
# 使用Python来更新JSON文件,更可靠
python3 -c "
import json
import sys
try:
with open('$SETTING_FILE', 'r', encoding='utf-8') as f:
data = json.load(f)
data['hostname'] = 'http://$new_ip'
with open('$SETTING_FILE', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print('更新成功')
except Exception as e:
print(f'更新失败: {e}')
sys.exit(1)
"
if [ $? -eq 0 ]; then
log_info "IP配置更新成功"
return 0
else
log_error "IP配置更新失败"
return 1
fi
}
# 启动xiaomusic
start_xiaomusic() {
log_info "启动xiaomusic..."
# 检查xiaomusic命令是否存在
if ! command -v xiaomusic &> /dev/null; then
log_error "未找到xiaomusic命令,请确保已正确安装"
exit 1
fi
# 获取xiaomusic命令路径
local xiaomusic_cmd=$(command -v xiaomusic)
log_info "xiaomusic命令: $xiaomusic_cmd"
log_info "配置文件: $SETTING_FILE"
# 切换到脚本目录并启动
cd "$SCRIPT_DIR" || exit 1
# 执行xiaomusic命令
exec xiaomusic
}
# 主程序
main() {
log_info "=== xiaomusic启动脚本 ==="
# 获取当前IP
current_ip=$(get_current_ip)
if [ -z "$current_ip" ]; then
log_error "无法获取当前IP地址"
exit 1
fi
log_info "当前IP地址: $current_ip"
# 获取配置文件中的IP
config_ip=$(get_config_ip)
if [ -z "$config_ip" ]; then
log_error "无法从配置文件中获取hostname IP"
exit 1
fi
log_info "配置文件IP: $config_ip"
# 比较IP
if [ "$current_ip" = "$config_ip" ]; then
log_info "IP配置一致,直接启动xiaomusic"
else
log_warn "IP配置不一致,需要更新配置"
if ! update_config_ip "$current_ip"; then
log_error "更新IP配置失败"
exit 1
fi
fi
# 启动xiaomusic
start_xiaomusic
}
# 捕获中断信号
trap 'log_info "脚本被中断"; exit 130' INT TERM
# 执行主程序
main "$@"
```
---
### 评论 31 - zhagn4
> 用这个网址的token可以登录, 里面的userid换成自己的 https://account.xiaomi.com/fe/service/account?userId=11111&_locale=zh_CN
亲测可用,谢谢
---
### 评论 32 - zhagn4
> > 用这个网址的token可以登录, 里面的userid换成自己的 https://account.xiaomi.com/fe/service/account?userId=11111&_locale=zh_CN
>
> 亲测可用,谢谢
可用一天后,失效
---
### 评论 33 - b2522
> > > 用这个网址的t
gitextract_prp6ch69/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── ci.yml │ ├── dockerhub-description.yml │ ├── fmt.yml │ └── static.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── check_plugins.py ├── config-example.json ├── docs/ │ ├── .vitepress/ │ │ ├── config.mts │ │ └── vitepress-plugin-github-issues.mts │ ├── CNAME │ ├── index.md │ ├── issues/ │ │ ├── 101.md │ │ ├── 105.md │ │ ├── 182.md │ │ ├── 19.md │ │ ├── 210.md │ │ ├── 211.md │ │ ├── 212.md │ │ ├── 235.md │ │ ├── 269.md │ │ ├── 285.md │ │ ├── 294.md │ │ ├── 297.md │ │ ├── 312.md │ │ ├── 333.md │ │ ├── 350.md │ │ ├── 360.md │ │ ├── 365.md │ │ ├── 366.md │ │ ├── 389.md │ │ ├── 398.md │ │ ├── 417.md │ │ ├── 520.md │ │ ├── 533.md │ │ ├── 595.md │ │ ├── 600.md │ │ ├── 637.md │ │ ├── 688.md │ │ ├── 764.md │ │ ├── 767.md │ │ ├── 78.md │ │ ├── 86.md │ │ ├── 88.md │ │ ├── 94.md │ │ ├── 96.md │ │ ├── 99.md │ │ ├── changelog.md │ │ └── index.md │ └── package.json ├── get_release.py ├── holiday/ │ ├── 2007.json │ ├── 2008.json │ ├── 2009.json │ ├── 2010.json │ ├── 2011.json │ ├── 2012.json │ ├── 2013.json │ ├── 2014.json │ ├── 2015.json │ ├── 2016.json │ ├── 2017.json │ ├── 2018.json │ ├── 2019.json │ ├── 2020.json │ ├── 2021.json │ ├── 2022.json │ ├── 2023.json │ ├── 2024.json │ ├── 2025.json │ ├── 2026.json │ ├── 2027.json │ ├── renovate.json │ └── schema.json ├── install_dependencies.sh ├── newpatch.sh ├── newversion.sh ├── package.json ├── plugins/ │ ├── __init__.py │ ├── code1.py │ ├── httpget.py │ └── httppost.py ├── pyproject.toml ├── test/ │ ├── test_difflib.py │ ├── test_music_duration.py │ ├── test_music_tags.py │ ├── test_remove_common_prefix.py │ └── test_update.py ├── update-holiday.sh ├── update-static-version.py ├── xiaomusic/ │ ├── __init__.py │ ├── analytics.py │ ├── api/ │ │ ├── __init__.py │ │ ├── app.py │ │ ├── dependencies.py │ │ ├── models.py │ │ ├── routers/ │ │ │ ├── __init__.py │ │ │ ├── device.py │ │ │ ├── file.py │ │ │ ├── music.py │ │ │ ├── playlist.py │ │ │ ├── plugin.py │ │ │ └── system.py │ │ └── websocket.py │ ├── auth.py │ ├── cli.py │ ├── command_handler.py │ ├── config.py │ ├── config_manager.py │ ├── const.py │ ├── conversation.py │ ├── crontab.py │ ├── device_manager.py │ ├── device_player.py │ ├── events.py │ ├── file_watcher.py │ ├── holiday.py │ ├── js_adapter.py │ ├── js_plugin_manager.py │ ├── js_plugin_runner.js │ ├── music_library.py │ ├── online_music.py │ ├── plugin.py │ ├── plugins-config-example.json │ ├── qrcode_login.py │ ├── static/ │ │ ├── default/ │ │ │ ├── debug.html │ │ │ ├── downloadtool.html │ │ │ ├── index.html │ │ │ ├── m3u.html │ │ │ ├── main.css │ │ │ ├── md.js │ │ │ ├── merge/ │ │ │ │ ├── index.html │ │ │ │ ├── main.js │ │ │ │ └── tailwind.css │ │ │ ├── setting.css │ │ │ ├── setting.html │ │ │ └── setting.js │ │ ├── index.html │ │ ├── iwebplayer/ │ │ │ └── iwebplayer.html │ │ ├── manifest.json │ │ ├── onlineSearch/ │ │ │ ├── config.js │ │ │ ├── index.html │ │ │ └── setting.html │ │ ├── pure/ │ │ │ ├── assets/ │ │ │ │ ├── DownloadTool-BWMSO0_N.css │ │ │ │ ├── DownloadTool-bty5M9I6.js │ │ │ │ ├── M3u2Json-ButJ7G_D.css │ │ │ │ ├── M3u2Json-DeAtFyPF.js │ │ │ │ ├── index-BAPaOAUA.js │ │ │ │ └── index-CfMOqlRg.css │ │ │ └── index.html │ │ ├── soundSpace/ │ │ │ ├── assets/ │ │ │ │ ├── features-animation-DOC4MC0a.js │ │ │ │ ├── index-KGjtlaO8.js │ │ │ │ ├── index-ckWJnWZz.js │ │ │ │ ├── index-fie2kaim.js │ │ │ │ ├── index-qfFWjqIn.css │ │ │ │ └── src-UW24ZMRV-DgU5LBZm.js │ │ │ └── index.html │ │ ├── sw.js │ │ ├── tailwind/ │ │ │ ├── api.js │ │ │ ├── debug.html │ │ │ ├── downloadtool.html │ │ │ ├── index.html │ │ │ ├── libs/ │ │ │ │ ├── daisyui@4.12.23.css │ │ │ │ ├── jquery-3.6.0.js │ │ │ │ ├── tailwind.js │ │ │ │ └── vue@3.5.13.js │ │ │ ├── m3u.html │ │ │ ├── main.css │ │ │ ├── md.js │ │ │ ├── now_playing.html │ │ │ ├── now_playing.js │ │ │ ├── setting.html │ │ │ ├── setting.js │ │ │ ├── tailwind.js │ │ │ └── theme.js │ │ ├── weapp/ │ │ │ └── qrcode.html │ │ └── xplayer/ │ │ ├── assets/ │ │ │ ├── index-2Kb1oK2G.css │ │ │ └── index-ESKkJcHu.js │ │ └── index.html │ ├── utils/ │ │ ├── __init__.py │ │ ├── file_utils.py │ │ ├── music_utils.py │ │ ├── network_utils.py │ │ ├── openai_utils.py │ │ ├── system_utils.py │ │ └── text_utils.py │ └── xiaomusic.py └── xiaomusic.py
Showing preview only (756K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (8105 symbols across 66 files)
FILE: check_plugins.py
function check_all_plugins (line 14) | def check_all_plugins():
FILE: get_release.py
function fetch_releases (line 12) | def fetch_releases():
function extract_tar_gz_files (line 27) | def extract_tar_gz_files(releases):
function save_to_json (line 44) | def save_to_json(data, filename="versions.json"):
function main (line 53) | def main():
FILE: plugins/code1.py
function code1 (line 1) | async def code1(arg1):
FILE: plugins/httpget.py
function httpget (line 4) | def httpget(url):
FILE: plugins/httppost.py
function httppost (line 6) | def httppost(data, url=target):
FILE: test/test_music_duration.py
function test_one_music (line 8) | async def test_one_music(filename):
function main (line 15) | async def main(directory):
FILE: test/test_music_tags.py
function test_one_music (line 16) | async def test_one_music(filename):
function main (line 26) | async def main(directory):
FILE: test/test_remove_common_prefix.py
function removepre (line 4) | def removepre(filename):
FILE: update-static-version.py
function get_html_files (line 7) | def get_html_files(directory):
function update_html_version (line 17) | def update_html_version(html_files, version):
FILE: xiaomusic/analytics.py
class Analytics (line 15) | class Analytics:
method __init__ (line 16) | def __init__(self, log, config):
method init (line 23) | def init(self):
method send_startup_event (line 37) | async def send_startup_event(self):
method send_daily_event (line 42) | async def send_daily_event(self):
method send_play_event (line 53) | async def send_play_event(self, name, sec, hardware):
method _send (line 62) | async def _send(self, event):
method _google_send (line 69) | def _google_send(self, events):
method run_with_cancel (line 75) | async def run_with_cancel(self, func, *args, **kwargs):
method post_to_umami (line 83) | async def post_to_umami(self, event):
method _get_user_agent (line 116) | def _get_user_agent(self):
FILE: xiaomusic/api/app.py
function app_lifespan (line 26) | async def app_lifespan(app):
function HttpInit (line 72) | def HttpInit(_xiaomusic: "XiaoMusic"):
FILE: xiaomusic/api/dependencies.py
class _AppStateProxy (line 31) | class _AppStateProxy:
method __init__ (line 38) | def __init__(self):
method initialize (line 43) | def initialize(self, xiaomusic_instance: "XiaoMusic"):
method is_initialized (line 53) | def is_initialized(self) -> bool:
class _LazyProxy (line 62) | class _LazyProxy:
method __init__ (line 65) | def __init__(self, attr_name: str):
method __getattr__ (line 68) | def __getattr__(self, name):
method __call__ (line 77) | def __call__(self, *args, **kwargs):
method __bool__ (line 86) | def __bool__(self):
method __repr__ (line 91) | def __repr__(self):
function verification (line 103) | def verification(
function no_verification (line 126) | def no_verification():
function access_key_verification (line 131) | def access_key_verification(file_path: str, key: str, code: str) -> bool:
class AuthStaticFiles (line 164) | class AuthStaticFiles(StaticFiles):
method __init__ (line 167) | def __init__(self, *args, **kwargs) -> None:
method __call__ (line 170) | async def __call__(self, scope, receive, send) -> None:
function reset_http_server (line 177) | def reset_http_server(app):
FILE: xiaomusic/api/models.py
class Did (line 6) | class Did(BaseModel):
class DidVolume (line 10) | class DidVolume(BaseModel):
class DidCmd (line 15) | class DidCmd(BaseModel):
class MusicInfoObj (line 20) | class MusicInfoObj(BaseModel):
class MusicItem (line 31) | class MusicItem(BaseModel):
class UrlInfo (line 35) | class UrlInfo(BaseModel):
class DidPlayMusic (line 39) | class DidPlayMusic(BaseModel):
class DidPlayMusicList (line 45) | class DidPlayMusicList(BaseModel):
class DownloadPlayList (line 51) | class DownloadPlayList(BaseModel):
class DownloadOneMusic (line 56) | class DownloadOneMusic(BaseModel):
class PlayListObj (line 63) | class PlayListObj(BaseModel):
class PlayListUpdateObj (line 67) | class PlayListUpdateObj(BaseModel):
class PlayListMusicObj (line 72) | class PlayListMusicObj(BaseModel):
FILE: xiaomusic/api/routers/__init__.py
function register_routers (line 14) | def register_routers(app):
FILE: xiaomusic/api/routers/device.py
function device_list (line 26) | async def device_list():
function getvolume (line 33) | async def getvolume(did: str = ""):
function getplayerstatus (line 43) | async def getplayerstatus(did: str = ""):
function setvolume (line 60) | async def setvolume(data: DidVolume):
function do_cmd (line 73) | async def do_cmd(data: DidCmd):
function cmd_status (line 93) | async def cmd_status():
function playurl (line 102) | async def playurl(did: str, url: str):
function playtts (line 112) | async def playtts(did: str, text: str):
function stop (line 123) | async def stop(data: Did):
FILE: xiaomusic/api/routers/file.py
function _process_m3u8_content (line 56) | def _process_m3u8_content(m3u8_content: str, base_url: str, is_radio: bo...
function cleantempdir (line 100) | async def cleantempdir(Verifcation=Depends(verification)):
function downloadjson (line 107) | async def downloadjson(data: UrlInfo, Verifcation=Depends(verification)):
function downloadplaylist (line 125) | async def downloadplaylist(data: DownloadPlayList, Verifcation=Depends(v...
function downloadonemusic (line 168) | async def downloadonemusic(data: DownloadOneMusic, Verifcation=Depends(v...
function upload_yt_dlp_cookie (line 261) | async def upload_yt_dlp_cookie(file: UploadFile = File(...)):
function upload_music (line 273) | async def upload_music(playlist: str = Form(...), file: UploadFile = Fil...
function safe_redirect (line 333) | def safe_redirect(url):
function music_file (line 344) | async def music_file(request: Request, file_path: str, key: str = "", co...
function music_options (line 382) | async def music_options():
function get_picture (line 391) | async def get_picture(request: Request, file_path: str, key: str = "", c...
function _is_bili_cdn (line 410) | def _is_bili_cdn(netloc: str) -> bool:
function _ffmpeg_mp3_stream (line 415) | async def _ffmpeg_mp3_stream(url: str, extra_headers: dict = None):
function _proxy_handler (line 493) | async def _proxy_handler(urlb64: str, is_radio: bool):
function proxy_with_type (line 702) | async def proxy_with_type(type: str, urlb64: str = "", token: str = ""):
function proxy (line 729) | async def proxy(urlb64: str):
FILE: xiaomusic/api/routers/music.py
function searchmusic (line 31) | def searchmusic(name: str = ""):
function search_online_music (line 40) | async def search_online_music(
function get_real_music_url (line 59) | async def get_real_music_url(url: str = Query(..., description="原始url")):
function get_plugin_source_url (line 74) | async def get_plugin_source_url(
function get_media_source (line 102) | async def get_media_source(request: Request):
function get_media_lyric (line 114) | async def get_media_lyric(request: Request):
function device_push_url (line 126) | async def device_push_url(request: Request):
function device_push_list (line 145) | async def device_push_list(request: Request):
function playingmusic (line 165) | def playingmusic(did: str = ""):
function musiclist (line 186) | async def musiclist():
function musicinfo (line 192) | async def musicinfo(name: str, musictag: bool = False):
function musicinfos (line 206) | async def musicinfos(
function setmusictag (line 225) | async def setmusictag(info: MusicInfoObj):
function delmusic (line 232) | async def delmusic(data: MusicItem):
function playmusic (line 240) | async def playmusic(data: DidPlayMusic):
function refreshmusictag (line 254) | async def refreshmusictag(Verifcation=Depends(verification)):
function debug_play_by_music_url (line 263) | async def debug_play_by_music_url(request: Request, Verifcation=Depends(...
function refreshlist (line 275) | async def refreshlist(Verifcation=Depends(verification)):
FILE: xiaomusic/api/routers/playlist.py
function curplaylist (line 24) | async def curplaylist(did: str = ""):
function playmusiclist (line 32) | async def playmusiclist(data: DidPlayMusicList):
function playlistadd (line 46) | async def playlistadd(data: PlayListObj):
function playlistdel (line 55) | async def playlistdel(data: PlayListObj):
function playlistupdatename (line 64) | async def playlistupdatename(data: PlayListUpdateObj):
function getplaylistnames (line 73) | async def getplaylistnames():
function playlistaddmusic (line 84) | async def playlistaddmusic(data: PlayListMusicObj):
function playlistdelmusic (line 93) | async def playlistdelmusic(data: PlayListMusicObj):
function playlistupdatemusic (line 102) | async def playlistupdatemusic(data: PlayListMusicObj):
function getplaylist (line 111) | async def getplaylist(name: str):
FILE: xiaomusic/api/routers/plugin.py
function get_js_plugins (line 25) | def get_js_plugins(
function enable_js_plugin (line 47) | def enable_js_plugin(plugin_name: str):
function disable_js_plugin (line 64) | def disable_js_plugin(plugin_name: str):
function uninstall_js_plugin (line 81) | def uninstall_js_plugin(plugin_name: str):
function upload_js_plugin (line 98) | async def upload_js_plugin(file: UploadFile = File(...)):
function get_openapi_info (line 154) | def get_openapi_info():
function toggle_openapi (line 164) | def toggle_openapi():
function update_openapi_url (line 173) | async def update_openapi_url(request: Request):
function get_plugin_source_info (line 189) | def get_plugin_source_info():
function refresh_plugin_source (line 199) | def refresh_plugin_source():
function update_plugin_source (line 208) | async def update_plugin_source(request: Request):
FILE: xiaomusic/api/routers/system.py
function read_index (line 58) | async def read_index():
function get_qrcode (line 67) | async def get_qrcode():
function get_logint_status (line 108) | async def get_logint_status(lp: str):
function getversion (line 117) | def getversion():
function getsetting (line 124) | async def getsetting(need_device_list: bool = False):
function savesetting (line 138) | async def savesetting(request: Request):
function modifiysetting (line 167) | async def modifiysetting(request: Request):
function downloadlog (line 215) | def downloadlog():
function latest_version (line 247) | async def latest_version():
function updateversion (line 257) | async def updateversion(version: str = "", lite: bool = True):
function get_swagger_documentation (line 270) | async def get_swagger_documentation():
function get_redoc_documentation (line 276) | async def get_redoc_documentation():
function openapi (line 282) | async def openapi():
FILE: xiaomusic/api/websocket.py
function generate_ws_token (line 32) | def generate_ws_token(
function ws_playingmusic (line 52) | async def ws_playingmusic(websocket: WebSocket):
FILE: xiaomusic/auth.py
class AuthManager (line 25) | class AuthManager:
method __init__ (line 31) | def __init__(self, config, log, device_manager):
method init_all_data (line 55) | async def init_all_data(self):
method can_login (line 77) | async def can_login(self):
method need_login (line 87) | async def need_login(self):
method login_miboy (line 107) | async def login_miboy(self):
method try_update_device_id (line 132) | async def try_update_device_id(self):
method set_token (line 167) | def set_token(self, account):
method get_cookie (line 191) | def get_cookie(self):
FILE: xiaomusic/cli.py
function main (line 39) | def main():
FILE: xiaomusic/command_handler.py
class CommandHandler (line 18) | class CommandHandler:
method __init__ (line 24) | def __init__(self, config, log, xiaomusic_instance: "XiaoMusic"):
method do_check_cmd (line 37) | async def do_check_cmd(self, did="", query="", ctrl_panel=True, **kwar...
method match_cmd (line 73) | def match_cmd(self, device, query, ctrl_panel):
method check_full_match_cmd (line 148) | def check_full_match_cmd(self, device, query, ctrl_panel):
FILE: xiaomusic/config.py
function default_key_word_dict (line 21) | def default_key_word_dict():
function default_user_key_word_dict (line 40) | def default_user_key_word_dict():
function default_key_match_order (line 54) | def default_key_match_order():
class Device (line 76) | class Device:
class Config (line 88) | class Config:
method append_keyword (line 234) | def append_keyword(self, keys, action):
method append_user_keyword (line 241) | def append_user_keyword(self):
method init (line 247) | def init(self):
method __post_init__ (line 265) | def __post_init__(self) -> None:
method from_options (line 280) | def from_options(cls, options: argparse.Namespace) -> Config:
method convert_value (line 290) | def convert_value(cls, k, v, type_hints):
method read_from_file (line 310) | def read_from_file(cls, config_path: str) -> dict:
method update_config (line 322) | def update_config(self, data):
method get_active_cmd_arr (line 331) | def get_active_cmd_arr(self):
method get_exclude_dirs_set (line 334) | def get_exclude_dirs_set(self):
method getsettingfile (line 338) | def getsettingfile(self):
method tag_cache_path (line 348) | def tag_cache_path(self):
method picture_cache_path (line 355) | def picture_cache_path(self):
method yt_dlp_cookies_path (line 362) | def yt_dlp_cookies_path(self):
method temp_dir (line 369) | def temp_dir(self):
method get_play_type_tts (line 374) | def get_play_type_tts(self, play_type):
method get_ignore_tag_dirs (line 387) | def get_ignore_tag_dirs(self):
method get_one_device_id (line 395) | def get_one_device_id(self):
method is_http_server_config (line 404) | def is_http_server_config(self, key: str) -> bool:
method get_basic_auth (line 422) | def get_basic_auth(self):
method get_self_netloc (line 427) | def get_self_netloc(self):
FILE: xiaomusic/config_manager.py
class ConfigManager (line 10) | class ConfigManager:
method __init__ (line 20) | def __init__(self, config, log):
method try_init_setting (line 30) | def try_init_setting(self):
method do_saveconfig (line 51) | def do_saveconfig(self, data):
method save_cur_config (line 64) | def save_cur_config(self, devices):
method update_config (line 84) | def update_config(self, data):
method get_config (line 95) | def get_config(self):
method get_setting_filename (line 103) | def get_setting_filename(self):
FILE: xiaomusic/conversation.py
class ConversationPoller (line 19) | class ConversationPoller:
method __init__ (line 27) | def __init__(
method run_conversation_loop (line 55) | async def run_conversation_loop(self, do_check_cmd_callback, reset_tim...
method poll_latest_ask (line 99) | async def poll_latest_ask(self, session):
method get_latest_ask_from_xiaoai (line 155) | async def get_latest_ask_from_xiaoai(self, session, device_id):
method get_latest_ask_by_mina (line 209) | async def get_latest_ask_by_mina(self, device_id):
method _get_last_query (line 246) | def _get_last_query(self, device_id, data):
method _check_last_query (line 272) | def _check_last_query(self, last_record):
FILE: xiaomusic/crontab.py
class CustomCronTrigger (line 11) | class CustomCronTrigger(BaseTrigger):
method __init__ (line 14) | def __init__(self, cron_expression, holiday_checker=None):
method get_next_fire_time (line 33) | def get_next_fire_time(self, previous_fire_time, now):
class Crontab (line 60) | class Crontab:
method __init__ (line 61) | def __init__(self, log):
method start (line 65) | def start(self):
method add_job (line 68) | def add_job(self, expression, job, coalesce=True):
method add_job_stop (line 91) | def add_job_stop(self, expression, xiaomusic, did, **kwargs):
method add_job_play (line 98) | def add_job_play(self, expression, xiaomusic, did, arg1, **kwargs):
method add_job_play_music_list (line 105) | def add_job_play_music_list(self, expression, xiaomusic, did, arg1, **...
method add_job_play_music_tmp_list (line 112) | def add_job_play_music_tmp_list(self, expression, xiaomusic, did, arg1...
method add_job_tts (line 126) | def add_job_tts(self, expression, xiaomusic, did, arg1, **kwargs):
method add_job_refresh_music_list (line 133) | def add_job_refresh_music_list(self, expression, xiaomusic, **kwargs):
method add_job_set_volume (line 140) | def add_job_set_volume(self, expression, xiaomusic, did, arg1, **kwargs):
method add_job_set_play_type (line 147) | def add_job_set_play_type(self, expression, xiaomusic, did, arg1, **kw...
method add_job_set_pull_ask (line 155) | def add_job_set_pull_ask(self, expression, xiaomusic, did, arg1, **kwa...
method add_job_refresh_web_music_list (line 165) | def add_job_refresh_web_music_list(self, expression, xiaomusic, **kwar...
method add_job_reinit (line 173) | def add_job_reinit(self, expression, xiaomusic, did, arg1, **kwargs):
method add_job_cron (line 179) | def add_job_cron(self, xiaomusic, cron):
method clear_jobs (line 197) | def clear_jobs(self):
method reload_config (line 205) | def reload_config(self, xiaomusic):
FILE: xiaomusic/device_manager.py
class DeviceManager (line 18) | class DeviceManager:
method __init__ (line 24) | def __init__(self, config, log, xiaomusic: Optional["XiaoMusic"] = None):
method _update_devices (line 41) | def _update_devices(self):
method get_did (line 66) | def get_did(self, device_id):
method get_hardward (line 77) | def get_hardward(self, device_id):
method get_device_by_device_id (line 91) | def get_device_by_device_id(self, device_id):
method get_group_device_id_list (line 105) | def get_group_device_id_list(self, group_name):
method get_group_devices (line 116) | def get_group_devices(self, group_name):
method update_device_info (line 133) | async def update_device_info(self, auth_manager):
method set_devices (line 144) | def set_devices(self, devices: dict[str, XiaoMusicDevice]):
FILE: xiaomusic/device_player.py
class XiaoMusicDevice (line 34) | class XiaoMusicDevice:
method __init__ (line 46) | def __init__(self, xiaomusic: "XiaoMusic", device: Device, group_name:...
method did (line 86) | def did(self):
method hardware (line 91) | def hardware(self):
method get_cur_music (line 95) | def get_cur_music(self):
method get_offset_duration (line 99) | def get_offset_duration(self):
method auto_add_song (line 108) | async def auto_add_song(self, cur_list_name, sleep_sec=20):
method _add_singer_song (line 129) | async def _add_singer_song(self, list_name, cur_music, sleep_sec):
method _delayed_add_singer_song (line 139) | async def _delayed_add_singer_song(self, list_name, singer_name, sleep...
method cancel_add_song_timer (line 151) | def cancel_add_song_timer(self):
method play_music (line 160) | async def play_music(self, name):
method update_playlist (line 164) | def update_playlist(self):
method play (line 186) | async def play(self, name="", search_key=""):
method _check_and_download_music (line 191) | async def _check_and_download_music(self, name, search_key, allow_down...
method _play_internal (line 224) | async def _play_internal(self, name="", search_key="", allow_download=...
method _play (line 275) | async def _play(self, name="", search_key=""):
method play_next (line 283) | async def play_next(self):
method _play_next (line 287) | async def _play_next(self):
method play_prev (line 308) | async def play_prev(self):
method _play_prev (line 312) | async def _play_prev(self):
method playlocal (line 330) | async def playlocal(self, name=""):
method _playmusic (line 335) | async def _playmusic(self, name):
method do_tts (line 400) | async def do_tts(self, value):
method force_stop_xiaoai (line 416) | async def force_stop_xiaoai(self, device_id):
method get_if_xiaoai_is_playing (line 427) | async def get_if_xiaoai_is_playing(self):
method stop_if_xiaoai_is_playing (line 440) | async def stop_if_xiaoai_is_playing(self, device_id):
method isdownloading (line 450) | def isdownloading(self):
method download (line 464) | async def download(self, search_key, name):
method check_replay (line 508) | async def check_replay(self):
method add_download_music (line 524) | async def add_download_music(self, name):
method get_music (line 535) | def get_music(self, direction="next"):
method get_next_music (line 576) | def get_next_music(self):
method get_prev_music (line 580) | def get_prev_music(self):
method check_play_next (line 584) | def check_play_next(self):
method text_to_speech (line 602) | async def text_to_speech(self, value):
method _text_to_speech_edge_tts (line 628) | async def _text_to_speech_edge_tts(self, value):
method group_player_play (line 690) | async def group_player_play(self, url, name=""):
method play_one_url (line 702) | async def play_one_url(self, device_id, url, name):
method _get_audio_id (line 732) | async def _get_audio_id(self, name):
method reset_timer_when_answer (line 760) | async def reset_timer_when_answer(self, answer_length):
method set_next_music_timeout (line 773) | async def set_next_music_timeout(self, sec):
method set_volume (line 806) | async def set_volume(self, volume: int):
method get_volume (line 816) | async def get_volume(self):
method get_player_status (line 833) | async def get_player_status(self):
method set_play_type (line 846) | async def set_play_type(self, play_type, dotts=True):
method play_music_list (line 857) | async def play_music_list(self, list_name, music_name):
method stop (line 867) | async def stop(self, arg1=""):
method group_force_stop_xiaoai (line 879) | async def group_force_stop_xiaoai(self):
method stop_after_minute (line 890) | async def stop_after_minute(self, minute: int):
method cancel_next_timer (line 907) | async def cancel_next_timer(self):
method cancel_group_next_timer (line 921) | async def cancel_group_next_timer(self):
method get_cur_play_list (line 928) | def get_cur_play_list(self):
method cancel_all_timer (line 932) | def cancel_all_timer(self):
method dict_clear (line 951) | def dict_clear(cls, d):
method find_cur_playlist (line 957) | def find_cur_playlist(self, name):
FILE: xiaomusic/events.py
class EventBus (line 13) | class EventBus:
method __init__ (line 19) | def __init__(self):
method subscribe (line 23) | def subscribe(self, event_type: str, callback: Callable) -> None:
method unsubscribe (line 35) | def unsubscribe(self, event_type: str, callback: Callable) -> None:
method publish (line 46) | def publish(self, event_type: str, **kwargs) -> None:
FILE: xiaomusic/file_watcher.py
class XiaoMusicPathWatch (line 19) | class XiaoMusicPathWatch(FileSystemEventHandler):
method __init__ (line 30) | def __init__(self, callback, debounce_delay, loop):
method on_any_event (line 43) | def on_any_event(self, event):
method schedule_callback (line 70) | def schedule_callback(self):
class FileWatcherManager (line 87) | class FileWatcherManager:
method __init__ (line 93) | def __init__(self, config, log, on_change_callback):
method start (line 107) | def start(self, loop):
method stop (line 142) | def stop(self):
FILE: xiaomusic/holiday.py
function load_year_data (line 12) | def load_year_data(year):
function is_valid_date (line 39) | def is_valid_date(year, month, day):
function is_weekend (line 48) | def is_weekend(year, month, day):
function is_off_day (line 54) | def is_off_day(year, month, day):
function is_working_day (line 76) | def is_working_day(year, month, day):
FILE: xiaomusic/js_adapter.py
class JSAdapter (line 10) | class JSAdapter:
method __init__ (line 13) | def __init__(self, xiaomusic):
method format_search_results (line 17) | def format_search_results(
method format_media_source_result (line 54) | def format_media_source_result(
method format_lyric_result (line 71) | def format_lyric_result(self, lyric_result: dict) -> str:
method format_album_info_result (line 87) | def format_album_info_result(self, album_info_result: dict) -> dict:
method format_music_sheet_info_result (line 108) | def format_music_sheet_info_result(self, music_sheet_result: dict) -> ...
method format_artist_works_result (line 127) | def format_artist_works_result(self, artist_works_result: dict) -> dict:
method format_top_lists_result (line 141) | def format_top_lists_result(self, top_lists_result: list[dict]) -> lis...
method format_top_list_detail_result (line 163) | def format_top_list_detail_result(self, top_list_detail_result: dict) ...
method _generate_music_id (line 178) | def _generate_music_id(
method _extract_artists (line 188) | def _extract_artists(self, item: dict) -> str:
method convert_music_item_for_plugin (line 215) | def convert_music_item_for_plugin(self, music_item: dict) -> dict:
FILE: xiaomusic/js_plugin_manager.py
class JSPluginManager (line 17) | class JSPluginManager:
method __init__ (line 20) | def __init__(self, xiaomusic):
method _start_node_process (line 57) | def _start_node_process(self):
method _monitor_node_process (line 82) | def _monitor_node_process(self):
method _attempt_restart_node_process (line 92) | def _attempt_restart_node_process(self):
method _start_message_handler (line 126) | def _start_message_handler(self):
method _send_message (line 163) | def _send_message(
method _wait_for_response (line 209) | def _wait_for_response(self, message_id: str, timeout: int) -> dict[st...
method _handle_response (line 221) | def _handle_response(self, response: dict[str, Any]):
method get_aiapi_info (line 264) | def get_aiapi_info(self) -> dict[str, Any]:
method get_openapi_info (line 280) | def get_openapi_info(self) -> dict[str, Any]:
method toggle_openapi (line 297) | def toggle_openapi(self) -> dict[str, Any]:
method update_openapi_url (line 320) | def update_openapi_url(self, openapi_url: str) -> dict[str, Any]:
method get_plugin_source (line 343) | def get_plugin_source(self) -> dict[str, Any]:
method refresh_plugin_source (line 360) | def refresh_plugin_source(self) -> dict[str, Any]:
method download_and_save_plugin (line 396) | def download_and_save_plugin(self, plugins_array: list) -> bool:
method download_single_plugin (line 437) | def download_single_plugin(self, plugin_name: str, plugin_url: str) ->...
method update_plugin_source_url (line 496) | def update_plugin_source_url(self, source_url: str) -> dict[str, Any]:
method _get_config_data (line 521) | def _get_config_data(self):
method _invalidate_config_cache (line 543) | def _invalidate_config_cache(self):
method _load_plugins (line 548) | def _load_plugins(self):
method load_plugin (line 622) | def load_plugin(self, plugin_name: str) -> bool:
method refresh_plugin_list (line 655) | def refresh_plugin_list(self) -> list[dict[str, Any]]:
method get_plugin_list (line 663) | def get_plugin_list(self) -> list[dict[str, Any]]:
method get_enabled_plugins (line 695) | def get_enabled_plugins(self) -> list[str]:
method get_auto_add_song (line 714) | def get_auto_add_song(self) -> bool:
method search (line 727) | def search(self, plugin_name: str, keyword: str, page: int = 1, limit:...
method openapi_search (line 776) | async def openapi_search(
method optimize_search_results (line 881) | def optimize_search_results(
method get_media_source (line 965) | def get_media_source(self, plugin_name: str, music_item: dict[str, Any...
method get_lyric (line 994) | def get_lyric(self, plugin_name: str, music_item: dict[str, Any]):
method get_music_info (line 1014) | def get_music_info(self, plugin_name: str, music_item: dict[str, Any]):
method get_album_info (line 1038) | def get_album_info(
method get_music_sheet_info (line 1064) | def get_music_sheet_info(
method get_artist_works (line 1090) | def get_artist_works(
method import_music_item (line 1122) | def import_music_item(self, plugin_name: str, url_like: str):
method import_music_sheet (line 1146) | def import_music_sheet(self, plugin_name: str, url_like: str):
method get_top_lists (line 1170) | def get_top_lists(self, plugin_name: str):
method get_top_list_detail (line 1188) | def get_top_list_detail(
method enable_plugin (line 1216) | def enable_plugin(self, plugin_name: str) -> bool:
method disable_plugin (line 1263) | def disable_plugin(self, plugin_name: str) -> bool:
method uninstall_plugin (line 1306) | def uninstall_plugin(self, plugin_name: str) -> bool:
method reload_plugins (line 1359) | def reload_plugins(self):
method update_plugin_config (line 1368) | def update_plugin_config(self, plugin_name: str, plugin_file: str):
method reset_restart_limit (line 1414) | def reset_restart_limit(self):
method get_restart_status (line 1420) | def get_restart_status(self) -> dict[str, Any]:
method shutdown (line 1438) | def shutdown(self):
FILE: xiaomusic/js_plugin_runner.js
class PluginRunner (line 15) | class PluginRunner {
method constructor (line 16) | constructor() {
method setupMessageHandler (line 23) | setupMessageHandler() {
method handleMessage (line 50) | async handleMessage(message) {
method sendResponse (line 113) | sendResponse(id, response) {
method loadPlugin (line 118) | loadPlugin(name, code) {
method createSandbox (line 171) | createSandbox() {
method detectCapabilities (line 253) | detectCapabilities(plugin) {
method search (line 263) | async search(pluginName, params) {
method getMediaSource (line 327) | async getMediaSource(pluginName, musicItem, quality) {
method getLyric (line 356) | async getLyric(pluginName, songId) {
method getAlbum (line 385) | async getAlbum(pluginName, albumInfo) {
method getPlaylist (line 415) | async getPlaylist(pluginName, playlistInfo) {
method getMusicInfo (line 445) | async getMusicInfo(pluginName, musicItem) {
method getArtistWorks (line 474) | async getArtistWorks(pluginName, artistItem, page = 1, type = 'music') {
method importMusicItem (line 503) | async importMusicItem(pluginName, urlLike) {
method importMusicSheet (line 532) | async importMusicSheet(pluginName, urlLike) {
method getTopLists (line 561) | async getTopLists(pluginName) {
method getTopListDetail (line 590) | async getTopListDetail(pluginName, topListItem, page = 1) {
method unloadPlugin (line 619) | unloadPlugin(name) {
method proxyFetch (line 625) | async proxyFetch(url, options) {
FILE: xiaomusic/music_library.py
function set_proxy_token (line 37) | def set_proxy_token(token: str, origin_url: str, is_radio: bool) -> None:
function get_proxy_token (line 42) | def get_proxy_token(token: str):
class MusicLibrary (line 47) | class MusicLibrary:
method __init__ (line 57) | def __init__(
method gen_all_music_list (line 95) | def gen_all_music_list(self):
method _append_music_list (line 193) | def _append_music_list(self):
method refresh_custom_play_list (line 235) | def refresh_custom_play_list(self):
method _is_reserved_playlist_name (line 259) | def _is_reserved_playlist_name(self, name):
method _build_custom_conflict_name (line 263) | def _build_custom_conflict_name(self, base_name, existed_names):
method _normalize_custom_playlist_conflicts (line 277) | def _normalize_custom_playlist_conflicts(self, custom_play_list):
method get_custom_play_list (line 301) | def get_custom_play_list(self):
method save_custom_play_list (line 313) | def save_custom_play_list(self):
method play_list_add (line 326) | def play_list_add(self, name):
method play_list_del (line 345) | def play_list_del(self, name):
method play_list_update_name (line 361) | def play_list_update_name(self, oldname, newname):
method get_play_list_names (line 388) | def get_play_list_names(self):
method play_list_musics (line 397) | def play_list_musics(self, name):
method play_list_update_music (line 412) | def play_list_update_music(self, name, music_list):
method update_music_list_json (line 438) | def update_music_list_json(self, list_name, update_list, append=False):
method _resolve_play_list (line 494) | def _resolve_play_list(self, name, create_if_missing=False):
method play_list_add_music (line 520) | def play_list_add_music(self, name, music_list):
method play_list_del_music (line 542) | def play_list_del_music(self, name, music_list):
method find_real_music_name (line 566) | def find_real_music_name(self, name, n):
method find_real_music_list_name (line 608) | def find_real_music_list_name(self, list_name):
method searchmusic (line 637) | def searchmusic(self, name):
method get_filename (line 653) | def get_filename(self, name):
method is_music_exist (line 673) | def is_music_exist(self, name):
method is_web_radio_music (line 691) | def is_web_radio_music(self, name):
method is_online_music (line 704) | def is_online_music(cur_playlist):
method is_web_music (line 708) | def is_web_music(self, name):
method is_need_use_play_music_api (line 722) | def is_need_use_play_music_api(self, name):
method get_music_tags (line 735) | async def get_music_tags(self, name):
method set_music_tag (line 769) | def set_music_tag(self, name, info):
method get_music_duration (line 804) | async def get_music_duration(self, name: str) -> float:
method refresh_music_tag (line 882) | def refresh_music_tag(self):
method try_load_from_tag_cache (line 903) | def try_load_from_tag_cache(self):
method try_save_tag_cache (line 927) | def try_save_tag_cache(self):
method ensure_single_thread_for_tag (line 937) | def ensure_single_thread_for_tag(self):
method try_gen_all_music_tag (line 947) | def try_gen_all_music_tag(self, only_items=None):
method _gen_all_music_tag (line 963) | async def _gen_all_music_tag(self, only_items=None):
method get_music_list (line 1021) | def get_music_list(self):
method get_all_music (line 1029) | def get_all_music(self):
method get_web_music_api (line 1037) | def get_web_music_api(self):
method get_all_radio (line 1045) | def get_all_radio(self):
method clear_web_music_duration_cache (line 1053) | def clear_web_music_duration_cache(self):
method get_music_url (line 1063) | async def get_music_url(self, name):
method _get_web_music_url (line 1077) | async def _get_web_music_url(self, name):
method _get_url_from_api (line 1105) | async def _get_url_from_api(self, name, url):
method _get_proxy_url (line 1121) | def _get_proxy_url(self, origin_url, is_radio=None):
method _get_local_music_url (line 1152) | def _get_local_music_url(self, name):
method _get_file_url (line 1167) | def _get_file_url(self, filepath):
method get_play_url (line 1193) | async def get_play_url(proxy_url):
method expand_self_url (line 1209) | def expand_self_url(self, origin_url):
FILE: xiaomusic/online_music.py
function _build_keyword (line 18) | def _build_keyword(song_name, artist):
function _parse_keyword_by_dash (line 38) | def _parse_keyword_by_dash(keyword):
class OnlineMusicService (line 45) | class OnlineMusicService:
method __init__ (line 51) | def __init__(self, log, js_plugin_manager, xiaomusic_instance=None):
method get_music_list_online (line 62) | async def get_music_list_online(
method _parse_keyword_and_artist (line 101) | async def _parse_keyword_and_artist(self, keyword):
method _execute_concurrent_searches (line 108) | async def _execute_concurrent_searches(
method _handle_search_exception (line 151) | def _handle_search_exception(self, result, source_name):
method _execute_openapi_search (line 158) | async def _execute_openapi_search(self, openapi_info, keyword, artist):
method _execute_plugin_search (line 174) | async def _execute_plugin_search(self, plugin, keyword, artist, page, ...
method _merge_search_results (line 182) | def _merge_search_results(
method get_music_list_mf (line 233) | async def get_music_list_mf(
method search_singer_play (line 267) | async def search_singer_play(self, did, search_key, name):
method add_singer_song (line 301) | async def add_singer_song(self, list_name, name):
method _parse_keyword_with_ai (line 315) | async def _parse_keyword_with_ai(self, keyword):
method _handle_music_list (line 358) | def _handle_music_list(
method online_play (line 392) | async def online_play(self, did="", arg1="", **kwargs):
method singer_play (line 404) | async def singer_play(self, did="", arg1="", **kwargs):
method push_music_list_play (line 416) | async def push_music_list_play(
method search_top_one_play (line 460) | async def search_top_one_play(self, did, search_key, name):
method default_url (line 484) | def default_url(self):
method _before_play (line 494) | async def _before_play(self):
method _convert_song_list_to_music_items (line 499) | def _convert_song_list_to_music_items(self, song_list):
method _get_plugin_proxy_url (line 530) | def _get_plugin_proxy_url(self, origin_data):
method _search_all_plugins (line 538) | async def _search_all_plugins(self, keyword, artist, page, limit):
method _search_specific_plugin (line 617) | async def _search_specific_plugin(self, plugin, keyword, artist, page,...
method _search_plugin_task (line 652) | async def _search_plugin_task(self, plugin_name, keyword, page, limit):
method get_media_source_url (line 661) | async def get_media_source_url(self, music_item, quality: str = "stand...
method get_media_lyric (line 680) | async def get_media_lyric(self, music_item):
method _deduplicate_song_list (line 697) | def _deduplicate_song_list(self, song_list):
method _search_top_one (line 726) | async def _search_top_one(self, music_items, search_key, name):
method _call_plugin_method (line 796) | async def _call_plugin_method(
method _make_request_with_validation (line 856) | async def _make_request_with_validation(
FILE: xiaomusic/plugin.py
class PluginManager (line 10) | class PluginManager:
method __init__ (line 11) | def __init__(self, xiaomusic: "XiaoMusic", plugin_dir="plugins"):
method _load_plugins (line 17) | def _load_plugins(self, plugin_dir):
method get_func (line 41) | def get_func(self, plugin_name):
method get_local_namespace (line 45) | def get_local_namespace(self):
method execute_plugin (line 49) | async def execute_plugin(self, code):
FILE: xiaomusic/qrcode_login.py
function gen_nonce (line 20) | def gen_nonce():
function get_signed_nonce (line 28) | def get_signed_nonce(ssecret, nonce):
function gen_enc_signature (line 36) | def gen_enc_signature(uri, method, signed_nonce, params):
function generate_enc_params (line 52) | def generate_enc_params(uri, method, signed_nonce, nonce, params, ssecur...
function encrypt_rc4 (line 68) | def encrypt_rc4(password, payload):
function decrypt_rc4 (line 74) | def decrypt_rc4(password, payload):
function decrypt (line 80) | def decrypt(ssecurity, nonce, payload):
class MiJiaAPI (line 89) | class MiJiaAPI:
method __init__ (line 90) | def __init__(self, auth_data_path: str | None = None):
method _init_session (line 115) | def _init_session(self):
method available (line 140) | def available(self) -> bool:
method pass_o (line 166) | def pass_o(self) -> str:
method user_agent (line 173) | def user_agent(self) -> str:
method device_id (line 188) | def device_id(self) -> str:
method _parse_service_ret (line 198) | def _parse_service_ret(self, service_ret: requests.Response) -> dict:
method _handle_ret (line 203) | def _handle_ret(
method _print_qr (line 218) | def _print_qr(loginurl: str, box_size: int = 10):
method _save_auth_data (line 229) | def _save_auth_data(self):
method _get_location (line 237) | def _get_location(self) -> dict:
method _refresh_token (line 263) | def _refresh_token(self) -> dict:
method get_qrcode (line 279) | def get_qrcode(self):
method get_logint_status (line 311) | def get_logint_status(self, status_url):
method qr_login (line 352) | def qr_login(self) -> dict:
method _request (line 436) | def _request(self, uri: str, data: dict, refresh_token: bool = True) -...
method check_new_msg (line 458) | def check_new_msg(
FILE: xiaomusic/static/default/md.js
function announceToScreenReader (line 35) | function announceToScreenReader(message) {
function fillSelectOptions (line 46) | function fillSelectOptions(
function openDialog (line 85) | function openDialog(dialogId) {
function closeDialog (line 115) | function closeDialog(dialogId) {
function closeAllDialogs (line 145) | function closeAllDialogs() {
function updateProgressAria (line 151) | function updateProgressAria(currentTime, totalTime) {
function updateVolumeAria (line 170) | function updateVolumeAria(volume) {
function updateFavoriteAria (line 178) | function updateFavoriteAria(isFavorited) {
function updatePullAskAria (line 189) | function updatePullAskAria(isEnabled) {
function loadAndPlayMusic (line 301) | function loadAndPlayMusic(musicName) {
function webPlay (line 345) | function webPlay() {
function play (line 370) | function play() {
function playOnDevice (line 379) | function playOnDevice() {
function stopPlay (line 399) | function stopPlay() {
function prevTrack (line 431) | function prevTrack() {
function nextTrack (line 443) | function nextTrack() {
function webPlayPrevious (line 456) | function webPlayPrevious() {
function webPlayNext (line 494) | function webPlayNext() {
function togglePlayMode (line 549) | function togglePlayMode(isSend = true) {
function playlistAddMusic (line 594) | function playlistAddMusic(playlistName, musicName) {
function playlistDelMusic (line 604) | function playlistDelMusic(playlistName, musicName) {
function addToFavorites (line 613) | function addToFavorites() {
function openSettings (line 649) | function openSettings() {
function toggleVolume (line 653) | function toggleVolume() {
function toggleSearch (line 662) | function toggleSearch() {
function toggleTimer (line 671) | function toggleTimer() {
function togglePlayLink (line 680) | function togglePlayLink() {
function toggleLocalPlay (line 689) | function toggleLocalPlay() {
function toggleWarning (line 693) | function toggleWarning() {
function toggleDelete (line 702) | function toggleDelete() {
function confirmDelete (line 712) | function confirmDelete() {
function formatTime (line 730) | function formatTime(seconds) {
function compareVersion (line 865) | function compareVersion(version1, version2) {
function _refresh_music_list (line 896) | function _refresh_music_list(callback) {
function refresh_music_list (line 1027) | function refresh_music_list() {
function do_play_music_list (line 1052) | function do_play_music_list(listname, musicname) {
function playUrl (line 1086) | function playUrl() {
function playProxyUrl (line 1094) | function playProxyUrl() {
function playTts (line 1107) | function playTts() {
function sendCustomCmd (line 1114) | function sendCustomCmd() {
function do_play_music (line 1136) | function do_play_music(musicname, searchkey) {
function triggerUpload (line 1156) | function triggerUpload() {
function check_status_refresh_music_list (line 1236) | function check_status_refresh_music_list(retries) {
function refreshlist (line 1248) | function refreshlist() {
function sendcmd (line 1263) | function sendcmd(cmd) {
function debounce (line 1288) | function debounce(func, delay) {
function handleSearch (line 1298) | function handleSearch() {
function selectSearchResult (line 1369) | function selectSearchResult(element) {
function formatTime (line 1393) | function formatTime(seconds) {
function validHost (line 1418) | function validHost(url) {
function nowarning (line 1437) | function nowarning() {
function timedShutDown (line 1442) | function timedShutDown(cmd) {
function confirmSearch (line 1450) | function confirmSearch() {
function cleanupWebSocket (line 1480) | function cleanupWebSocket() {
function connectWebSocket (line 1511) | function connectWebSocket(did) {
function startWebSocket (line 1541) | function startWebSocket(did, token) {
function updateProgressUI (line 1612) | function updateProgressUI() {
function togglePullAsk (line 1630) | function togglePullAsk() {
function updatePullAskUI (line 1656) | function updatePullAskUI(enabled) {
function updateWebPlayingUI (line 1710) | function updateWebPlayingUI() {
function updateWebFavoriteButton (line 1723) | function updateWebFavoriteButton() {
function initWebPlayer (line 1743) | function initWebPlayer() {
FILE: xiaomusic/static/default/merge/main.js
function d (line 1) | function d(K){if(K===null||typeof K!=="object")return null;var E=zZ&&K[z...
function x0 (line 1) | function x0(K){S0=K}
function q0 (line 1) | function q0(K){{for(var E=arguments.length,I=new Array(E>1?E-1:0),b=1;b<...
function FZ (line 1) | function FZ(K){{for(var E=arguments.length,I=new Array(E>1?E-1:0),b=1;b<...
function C0 (line 1) | function C0(K,E,I){{var b=yZ.ReactDebugCurrentFrame,u=b.getStackAddendum...
function j7 (line 1) | function j7(K,E){{var I=K.constructor,b=I&&(I.displayName||I.name)||"Rea...
function K4 (line 1) | function K4(K,E,I){this.props=K,this.context=E,this.refs=P4,this.updater...
function N7 (line 1) | function N7(){}
function u0 (line 1) | function u0(K,E,I){this.props=K,this.context=E,this.refs=P4,this.updater...
function z4 (line 1) | function z4(){var K={current:null};return Object.seal(K),K}
function w0 (line 1) | function w0(K){return X7(K)}
function h4 (line 1) | function h4(K){{var E=typeof Symbol==="function"&&Symbol.toStringTag,I=E...
function J7 (line 1) | function J7(K){try{return k0(K),!1}catch(E){return!0}}
function k0 (line 1) | function k0(K){return""+K}
function j0 (line 1) | function j0(K){if(J7(K))return FZ("The provided key is an unsupported ty...
function S4 (line 1) | function S4(K,E,I){var b=K.displayName;if(b)return b;var u=E.displayName...
function L7 (line 1) | function L7(K){return K.displayName||"Context"}
function l0 (line 1) | function l0(K){if(K==null)return null;if(typeof K.tag==="number")FZ("Rec...
function Y7 (line 1) | function Y7(K){if(G7.call(K,"ref")){var E=Object.getOwnPropertyDescripto...
function _4 (line 1) | function _4(K){if(G7.call(K,"key")){var E=Object.getOwnPropertyDescripto...
function t0 (line 1) | function t0(K,E){var I=function(){if(!m4)m4=!0,FZ("%s: `key` is not a pr...
function d7 (line 1) | function d7(K,E){var I=function(){if(!d4)d4=!0,FZ("%s: `ref` is not a pr...
function p7 (line 1) | function p7(K){if(typeof K.ref==="string"&&MZ.current&&K.__self&&MZ.curr...
function p4 (line 1) | function p4(K,E,I){var b,u={},_Z=null,XZ=null,jZ=null,kZ=null;if(E!=null...
function a (line 1) | function a(K,E){var I=D7(K.type,E,K.ref,K._self,K._source,K._owner,K.pro...
function QZ (line 1) | function QZ(K,E,I){if(K===null||K===void 0)throw new Error("React.cloneE...
function AZ (line 1) | function AZ(K){return typeof K==="object"&&K!==null&&K.$$typeof===O}
function b0 (line 1) | function b0(K){var E=/[=:]/g,I={"=":"=0",":":"=2"},b=K.replace(E,functio...
function RZ (line 1) | function RZ(K){return K.replace(O4,"$&/")}
function uZ (line 1) | function uZ(K,E){if(typeof K==="object"&&K!==null&&K.key!=null)return j0...
function U8 (line 1) | function U8(K,E,I,b,u){var _Z=typeof K;if(_Z==="undefined"||_Z==="boolea...
function Q7 (line 1) | function Q7(K,E,I){if(K==null)return K;var b=[],u=0;return U8(K,b,"","",...
function f8 (line 1) | function f8(K){var E=0;return Q7(K,function(){E++}),E}
function z1 (line 1) | function z1(K,E,I){Q7(K,function(){E.apply(this,arguments)},I)}
function YX (line 1) | function YX(K){return Q7(K,function(E){return E})||[]}
function QX (line 1) | function QX(K){if(!AZ(K))throw new Error("React.Children.only expected t...
function E9 (line 1) | function E9(K){var E={$$typeof:C,_currentValue:K,_currentValue2:K,_threa...
function F7 (line 1) | function F7(K){if(K._status===u7){var E=K._result,I=E();if(I.then(functi...
function l7 (line 9) | function l7(K){var E={_status:u7,_result:K},I={$$typeof:l,_payload:E,_in...
function N (line 9) | function N(K){{if(K!=null&&K.$$typeof===k)FZ("forwardRef requires a rend...
function s (line 9) | function s(K){if(typeof K==="string"||typeof K==="function")return!0;if(...
function BZ (line 9) | function BZ(K,E){if(!s(K))FZ("memo: The first argument must be a compone...
function WZ (line 9) | function WZ(){var K=g.current;if(K===null)FZ(`Invalid hook call. Hooks c...
function mZ (line 13) | function mZ(K){var E=WZ();if(K._context!==void 0){var I=K._context;if(I....
function CZ (line 13) | function CZ(K){var E=WZ();return E.useState(K)}
function qZ (line 13) | function qZ(K,E,I){var b=WZ();return b.useReducer(K,E,I)}
function lZ (line 13) | function lZ(K){var E=WZ();return E.useRef(K)}
function U0 (line 13) | function U0(K,E){var I=WZ();return I.useEffect(K,E)}
function O0 (line 13) | function O0(K,E){var I=WZ();return I.useInsertionEffect(K,E)}
function e0 (line 13) | function e0(K,E){var I=WZ();return I.useLayoutEffect(K,E)}
function c7 (line 13) | function c7(K,E){var I=WZ();return I.useCallback(K,E)}
function q7 (line 13) | function q7(K,E){var I=WZ();return I.useMemo(K,E)}
function c0 (line 13) | function c0(K,E,I){var b=WZ();return b.useImperativeHandle(K,E,I)}
function h8 (line 13) | function h8(K,E){{var I=WZ();return I.useDebugValue(K,E)}}
function _1 (line 13) | function _1(){var K=WZ();return K.useTransition()}
function w7 (line 13) | function w7(K){var E=WZ();return E.useDeferredValue(K)}
function xZ (line 13) | function xZ(){var K=WZ();return K.useId()}
function N9 (line 13) | function N9(K,E,I){var b=WZ();return b.useSyncExternalStore(K,E,I)}
function U6 (line 13) | function U6(){}
function bY (line 13) | function bY(){{if(m8===0){O1=console.log,H1=console.info,j1=console.warn...
function A1 (line 13) | function A1(){{if(m8--,m8===0){var K={configurable:!0,enumerable:!0,writ...
function d8 (line 13) | function d8(K,E,I){{if($7===void 0)try{throw Error()}catch(u){var b=u.st...
function B6 (line 14) | function B6(K,E){if(!K||p8)return"";{var I=$X.get(K);if(I!==void 0)retur...
function q1 (line 17) | function q1(K,E,I){return B6(K,!1)}
function RY (line 17) | function RY(K){var E=K.prototype;return!!(E&&E.isReactComponent)}
function u8 (line 17) | function u8(K,E,I){if(K==null)return"";if(typeof K==="function")return B...
function WX (line 17) | function WX(K){if(K){var E=K._owner,I=u8(K.type,K._source,E?E.type:null)...
function z6 (line 17) | function z6(K,E,I,b,u){{var _Z=Function.call.bind(G7);for(var XZ in K)if...
function hZ (line 17) | function hZ(K){if(K){var E=K._owner,I=u8(K.type,K._source,E?E.type:null)...
function T1 (line 17) | function T1(){if(MZ.current){var K=l0(MZ.current.type);if(K)return`
function wZ (line 19) | function wZ(K){if(K!==void 0){var E=K.fileName.replace(/^.*[\\\/]/,""),I...
function O6 (line 21) | function O6(K){if(K!==null&&K!==void 0)return wZ(K.__source);return""}
function D9 (line 21) | function D9(K){var E=T1();if(!E){var I=typeof K==="string"?K:K.displayNa...
function l8 (line 23) | function l8(K,E){if(!K._store||K._store.validated||K.key!=null)return;K....
function H6 (line 23) | function H6(K,E){if(typeof K!=="object")return;if(w0(K))for(var I=0;I<K....
function i0 (line 23) | function i0(K){{var E=K.type;if(E===null||E===void 0||typeof E==="string...
function E0 (line 23) | function E0(K){{var E=Object.keys(K.props);for(var I=0;I<E.length;I++){v...
function j6 (line 23) | function j6(K,E,I){var b=s(K);if(!b){var u="";if(K===void 0||typeof K===...
function x4 (line 23) | function x4(K){var E=j6.bind(null,K);E.type=K;{if(!u4)u4=!0,q0("React.cr...
function i7 (line 23) | function i7(K,E,I){var b=QZ.apply(this,arguments);for(var u=2;u<argument...
function fY (line 23) | function fY(K,E){var I=p.transition;p.transition={};var b=p.transition;p...
function E6 (line 23) | function E6(K){if(A9===null)try{var E=("require"+Math.random()).slice(0,...
function L6 (line 23) | function L6(K){{var E=c8;if(c8++,i.current===null)i.current=[];var I=i.i...
function B8 (line 23) | function B8(K){{if(K!==c8-1)FZ("You seem to have overlapping act() calls...
function UX (line 23) | function UX(K,E,I){{var b=i.current;if(b!==null)try{BX(b),E6(function(){...
function BX (line 23) | function BX(K){if(!i8){i8=!0;var E=0;try{for(;E<K.length;E++){var I=K[E]...
function j (line 23) | function j(a,QZ){var AZ=a.length;a.push(QZ),A(a,QZ,AZ)}
function U (line 23) | function U(a){return a.length===0?null:a[0]}
function F (line 23) | function F(a){if(a.length===0)return null;var QZ=a[0],AZ=a.pop();if(AZ!=...
function A (line 23) | function A(a,QZ,AZ){var nZ=AZ;while(nZ>0){var X0=nZ-1>>>1,b0=a[X0];if(S(...
function C (line 23) | function C(a,QZ,AZ){var nZ=AZ,X0=a.length,b0=X0>>>1;while(nZ<b0){var V0=...
function S (line 23) | function S(a,QZ){var AZ=a.sortIndex-QZ.sortIndex;return AZ!==0?AZ:a.id-Q...
function zZ (line 23) | function zZ(a,QZ){}
function P4 (line 23) | function P4(a){var QZ=U(Q0);while(QZ!==null){if(QZ.callback===null)F(Q0)...
function K4 (line 23) | function K4(a){if(C0=!1,P4(a),!FZ)if(U(IZ)!==null)FZ=!0,t0(Z7);else{var ...
function Z7 (line 23) | function Z7(a,QZ){if(FZ=!1,C0)C0=!1,p7();q0=!0;var AZ=yZ;try{if(O)try{re...
function m7 (line 23) | function m7(a,QZ){var AZ=QZ;P4(AZ),gZ=U(IZ);while(gZ!==null&&!B){if(gZ.e...
function E7 (line 23) | function E7(a,QZ){switch(a){case q:case x:case k:case l:case GZ:break;de...
function N7 (line 23) | function N7(a){var QZ;switch(yZ){case q:case x:case k:QZ=k;break;default...
function u0 (line 23) | function u0(a){var QZ=yZ;return function(){var AZ=yZ;yZ=QZ;try{return a....
function y4 (line 23) | function y4(a,QZ,AZ){var nZ=fF.unstable_now(),X0;if(typeof AZ==="object"...
function z4 (line 23) | function z4(){}
function X7 (line 23) | function X7(){if(!FZ&&!q0)FZ=!0,t0(Z7)}
function w0 (line 23) | function w0(){return U(IZ)}
function h4 (line 23) | function h4(a){a.callback=null}
function J7 (line 23) | function J7(){return yZ}
function G7 (line 23) | function G7(){var a=fF.unstable_now()-l0;if(a<L7)return!1;return!0}
function R8 (line 23) | function R8(){}
function m4 (line 23) | function m4(a){if(a<0||a>125){console.error("forceFrameRate takes a posi...
function t0 (line 23) | function t0(a){if(j0=a,!k0)k0=!0,v0()}
function d7 (line 23) | function d7(a,QZ){S4=H7(function(){a(fF.unstable_now())},QZ)}
function p7 (line 23) | function p7(){j7(S4),S4=-1}
function H (line 23) | function H(Z){O=Z}
function j (line 23) | function j(Z){if(!O){for(var X=arguments.length,J=new Array(X>1?X-1:0),G...
function U (line 23) | function U(Z){if(!O){for(var X=arguments.length,J=new Array(X>1?X-1:0),G...
function F (line 23) | function F(Z,X,J){{var G=B.ReactDebugCurrentFrame,Y=G.getStackAddendum()...
function z4 (line 23) | function z4(Z,X){X7(Z,X),X7(Z+"Capture",X)}
function X7 (line 23) | function X7(Z,X){if(u0[Z])U("EventRegistry: More than one plugin attempt...
function J7 (line 23) | function J7(Z){{var X=typeof Symbol==="function"&&Symbol.toStringTag,J=X...
function k0 (line 23) | function k0(Z){try{return j0(Z),!1}catch(X){return!0}}
function j0 (line 23) | function j0(Z){return""+Z}
function S4 (line 23) | function S4(Z,X){if(k0(Z))return U("The provided `%s` attribute is an un...
function L7 (line 23) | function L7(Z){if(k0(Z))return U("The provided key is an unsupported typ...
function l0 (line 23) | function l0(Z,X){if(k0(Z))return U("The provided `%s` prop is an unsuppo...
function G7 (line 23) | function G7(Z,X){if(k0(Z))return U("The provided `%s` CSS property is an...
function R8 (line 23) | function R8(Z){if(k0(Z))return U("The provided HTML markup uses a value ...
function m4 (line 23) | function m4(Z){if(k0(Z))return U("Form field values (value, checked, def...
function nZ (line 23) | function nZ(Z){if(h4.call(AZ,Z))return!0;if(h4.call(QZ,Z))return!1;if(a....
function X0 (line 23) | function X0(Z,X,J){if(X!==null)return X.type===d4;if(J)return!1;if(Z.len...
function b0 (line 23) | function b0(Z,X,J,G){if(J!==null&&J.type===d4)return!1;switch(typeof X){...
function V0 (line 23) | function V0(Z,X,J,G){if(X===null||typeof X==="undefined")return!0;if(b0(...
function O4 (line 23) | function O4(Z){return uZ.hasOwnProperty(Z)?uZ[Z]:null}
function RZ (line 23) | function RZ(Z,X,J,G,Y,Q,$){this.acceptsBooleans=X===Y7||X===_4||X===t0,t...
function E9 (line 23) | function E9(Z){if(!QX&&YX.test(Z))QX=!0,U("A future version of React wil...
function u7 (line 23) | function u7(Z,X,J,G){if(G.mustUseProperty){var Y=G.propertyName;return Z...
function g8 (line 23) | function g8(Z,X,J,G){{if(!nZ(X))return;if(!Z.hasAttribute(X))return J===...
function y8 (line 23) | function y8(Z,X,J,G){var Y=O4(X);if(X0(X,Y,G))return;if(V0(X,J,Y,G))J=nu...
function w7 (line 23) | function w7(Z){if(Z===null||typeof Z!=="object")return null;var X=h8&&Z[...
function D1 (line 23) | function D1(){}
function U6 (line 23) | function U6(){{if(N9===0){m8=console.log,O1=console.info,H1=console.warn...
function bY (line 23) | function bY(){{if(N9--,N9===0){var Z={configurable:!0,enumerable:!0,writ...
function $7 (line 23) | function $7(Z,X,J){{if(L9===void 0)try{throw Error()}catch(Y){var G=Y.st...
function F1 (line 24) | function F1(Z,X){if(!Z||d8)return"";{var J=p8.get(Z);if(J!==void 0)retur...
function B6 (line 27) | function B6(Z,X,J){return F1(Z,!0)}
function q1 (line 27) | function q1(Z,X,J){return F1(Z,!1)}
function RY (line 27) | function RY(Z){var X=Z.prototype;return!!(X&&X.isReactComponent)}
function u8 (line 27) | function u8(Z,X,J){if(Z==null)return"";if(typeof Z==="function")return F...
function K6 (line 27) | function K6(Z){var X=Z._debugOwner?Z._debugOwner.type:null,J=Z._debugSou...
function w1 (line 27) | function w1(Z){try{var X="",J=Z;do X+=K6(J),J=J.return;while(J);return X...
function WX (line 29) | function WX(Z,X,J){var G=Z.displayName;if(G)return G;var Y=X.displayName...
function z6 (line 29) | function z6(Z){return Z.displayName||"Context"}
function hZ (line 29) | function hZ(Z){if(Z==null)return null;if(typeof Z.tag==="number")U("Rece...
function _6 (line 29) | function _6(Z,X,J){var G=X.displayName||X.name||"";return Z.displayName|...
function T1 (line 29) | function T1(Z){return Z.displayName||"Context"}
function wZ (line 29) | function wZ(Z){var{tag:X,type:J}=Z;switch(X){case yZ:return"Cache";case ...
function l8 (line 29) | function l8(){{if(H4===null)return null;var Z=H4._debugOwner;if(Z!==null...
function H6 (line 29) | function H6(){{if(H4===null)return"";return w1(H4)}}
function i0 (line 29) | function i0(){O6.getCurrentStack=null,H4=null,D9=!1}
function E0 (line 29) | function E0(Z){O6.getCurrentStack=Z===null?null:H6,H4=Z,D9=!1}
function j6 (line 29) | function j6(){return H4}
function u4 (line 29) | function u4(Z){D9=Z}
function x4 (line 29) | function x4(Z){return""+Z}
function i7 (line 29) | function i7(Z){switch(typeof Z){case"boolean":case"number":case"string":...
function VX (line 29) | function VX(Z,X){{if(!(fY[X.type]||X.onChange||X.onInput||X.readOnly||X....
function A9 (line 29) | function A9(Z){var{type:X,nodeName:J}=Z;return J&&J.toLowerCase()==="inp...
function E6 (line 29) | function E6(Z){return Z._valueTracker}
function c8 (line 29) | function c8(Z){Z._valueTracker=null}
function N6 (line 29) | function N6(Z){var X="";if(!Z)return X;if(A9(Z))X=Z.checked?"true":"fals...
function L6 (line 29) | function L6(Z){var X=A9(Z)?"checked":"value",J=Object.getOwnPropertyDesc...
function B8 (line 29) | function B8(Z){if(E6(Z))return;Z._valueTracker=L6(Z)}
function UX (line 29) | function UX(Z){if(!Z)return!1;var X=E6(Z);if(!X)return!0;var J=X.getValu...
function i8 (line 29) | function i8(Z){if(Z=Z||(typeof document!=="undefined"?document:void 0),t...
function q6 (line 29) | function q6(Z){var X=Z.type==="checkbox"||Z.type==="radio";return X?Z.ch...
function K (line 29) | function K(Z,X){var J=Z,G=X.checked,Y=xZ({},X,{defaultChecked:void 0,def...
function E (line 29) | function E(Z,X){{if(VX("input",X),X.checked!==void 0&&X.defaultChecked!=...
function I (line 29) | function I(Z,X){var J=Z,G=X.checked;if(G!=null)y8(J,"checked",G,!1)}
function b (line 29) | function b(Z,X){var J=Z;{var G=q6(X);if(!J._wrapperState.controlled&&G&&...
function u (line 29) | function u(Z,X,J){var G=Z;if(X.hasOwnProperty("value")||X.hasOwnProperty...
function _Z (line 29) | function _Z(Z,X){var J=Z;b(J,X),XZ(J,X)}
function XZ (line 29) | function XZ(Z,X){var J=X.name;if(X.type==="radio"&&J!=null){var G=Z;whil...
function jZ (line 29) | function jZ(Z,X,J){if(X!=="number"||i8(Z.ownerDocument)!==Z){if(J==null)...
function G0 (line 29) | function G0(Z,X){{if(X.value==null){if(typeof X.children==="object"&&X.c...
function B0 (line 29) | function B0(Z,X){if(X.value!=null)Z.setAttribute("value",x4(i7(X.value)))}
function cZ (line 29) | function cZ(Z){return D0(Z)}
function KX (line 29) | function KX(){var Z=l8();if(Z)return`
function gY (line 31) | function gY(Z){{VX("select",Z);for(var X=0;X<C1.length;X++){var J=C1[X];...
function K8 (line 31) | function K8(Z,X,J,G){var Y=Z.options;if(X){var Q=J,$={};for(var W=0;W<Q....
function I1 (line 31) | function I1(Z,X){return xZ({},X,{value:void 0})}
function M1 (line 31) | function M1(Z,X){var J=Z;if(gY(X),J._wrapperState={wasMultiple:!!X.multi...
function yY (line 31) | function yY(Z,X){var J=Z;J.multiple=!!X.multiple;var G=X.value;if(G!=nul...
function K_ (line 31) | function K_(Z,X){var J=Z,G=J._wrapperState.wasMultiple;J._wrapperState.w...
function z_ (line 31) | function z_(Z,X){var J=Z,G=X.value;if(G!=null)K8(J,!!X.multiple,G,!1)}
function hY (line 31) | function hY(Z,X){var J=Z;if(X.dangerouslySetInnerHTML!=null)throw new Er...
function BW (line 31) | function BW(Z,X){var J=Z;if(VX("textarea",X),X.value!==void 0&&X.default...
function KW (line 31) | function KW(Z,X){var J=Z,G=i7(X.value),Y=i7(X.defaultValue);if(G!=null){...
function zW (line 31) | function zW(Z,X){var J=Z,G=J.textContent;if(G===J._wrapperState.initialV...
function __ (line 31) | function __(Z,X){KW(Z,X)}
function dY (line 31) | function dY(Z){switch(Z){case"svg":return mY;case"math":return O_;defaul...
function pY (line 31) | function pY(Z,X){if(Z==null||Z===z8)return dY(X);if(Z===mY&&X==="foreign...
function E_ (line 31) | function E_(Z,X){return Z+X.charAt(0).toUpperCase()+X.substring(1)}
function lY (line 31) | function lY(Z,X,J){var G=X==null||typeof X==="boolean"||X==="";if(G)retu...
function A_ (line 31) | function A_(Z){return Z.replace(L_,"-$1").toLowerCase().replace(D_,"-ms-")}
function k_ (line 31) | function k_(Z){{var X="",J="";for(var G in Z){if(!Z.hasOwnProperty(G))co...
function NW (line 31) | function NW(Z,X){var J=Z.style;for(var G in X){if(!X.hasOwnProperty(G))c...
function v_ (line 31) | function v_(Z){return Z==null||typeof Z==="boolean"||Z===""}
function LW (line 31) | function LW(Z){var X={};for(var J in Z){var G=j_[J]||[J];for(var Y=0;Y<G...
function b_ (line 31) | function b_(Z,X){{if(!X)return;var J=LW(Z),G=LW(X),Y={};for(var Q in J){...
function iY (line 31) | function iY(Z,X){if(!X)return;if(f_[Z]){if(X.children!=null||X.dangerous...
function q9 (line 31) | function q9(Z,X){if(Z.indexOf("-")===-1)return typeof X.is==="string";sw...
function m_ (line 31) | function m_(Z,X){{if(h4.call(_X,X)&&_X[X])return!0;if(h_.test(X)){var J=...
function d_ (line 31) | function d_(Z,X){{var J=[];for(var G in X){var Y=m_(Z,G);if(!Y)J.push(G)...
function p_ (line 31) | function p_(Z,X){if(q9(Z,X))return;d_(Z,X)}
function u_ (line 31) | function u_(Z,X){{if(Z!=="input"&&Z!=="textarea"&&Z!=="select")return;if...
function s_ (line 31) | function s_(Z,X,J){if(q9(Z,X))return;n_(Z,X,J)}
function a_ (line 31) | function a_(Z){if(x1!==null)U("Expected currently replaying event to be ...
function r_ (line 31) | function r_(){if(x1===null)U("Expected currently replaying event to not ...
function t_ (line 31) | function t_(Z){return Z===x1}
function sY (line 31) | function sY(Z){var X=Z.target||Z.srcElement||window;if(X.correspondingUs...
function TW (line 31) | function TW(Z){var X=Z9(Z);if(!X)return;if(typeof oY!=="function")throw ...
function e_ (line 31) | function e_(Z){oY=Z}
function CW (line 31) | function CW(Z){if(OX)if(HX)HX.push(Z);else HX=[Z];else OX=Z}
function ZO (line 31) | function ZO(){return OX!==null||HX!==null}
function IW (line 31) | function IW(){if(!OX)return;var Z=OX,X=HX;if(OX=null,HX=null,TW(Z),X)for...
function XO (line 31) | function XO(){var Z=ZO();if(Z)PW(),IW()}
function SW (line 31) | function SW(Z,X,J){if(aY)return Z(X,J);aY=!0;try{return MW(Z,X,J)}finall...
function JO (line 31) | function JO(Z,X,J){MW=Z,PW=J}
function GO (line 31) | function GO(Z){return Z==="button"||Z==="input"||Z==="select"||Z==="text...
function YO (line 31) | function YO(Z,X,J){switch(Z){case"onClick":case"onClickCapture":case"onD...
function k1 (line 31) | function k1(Z,X){var J=Z.stateNode;if(J===null)return null;var G=BG(J);i...
function xW (line 31) | function xW(Z,X,J,G,Y,Q,$,W,V){var z=Array.prototype.slice.call(argument...
function v (line 31) | function v(){if(tY.removeEventListener(R,YZ,!1),typeof window.event!=="u...
function YZ (line 31) | function YZ(){D=!0,v(),J.apply(G,n),L=!1}
function w (line 31) | function w(T){if(JZ=T.error,PZ=!0,JZ===null&&T.colno===0&&T.lineno===0)S...
function Z5 (line 31) | function Z5(Z,X,J,G,Y,Q,$,W,V){jX=!1,I6=null,QO.apply($O,arguments)}
function WO (line 31) | function WO(Z,X,J,G,Y,Q,$,W,V){if(Z5.apply(this,arguments),jX){var z=X5(...
function VO (line 31) | function VO(){if(M6){var Z=eY;throw M6=!1,eY=null,Z}}
function UO (line 31) | function UO(){return jX}
function X5 (line 31) | function X5(){if(jX){var Z=I6;return jX=!1,I6=null,Z}else throw new Erro...
function EX (line 31) | function EX(Z){return Z._reactInternals}
function BO (line 31) | function BO(Z){return Z._reactInternals!==void 0}
function KO (line 31) | function KO(Z,X){Z._reactInternals=X}
function M9 (line 31) | function M9(Z){var X=Z,J=Z;if(!Z.alternate){var G=X;do{if(X=G,(X.flags&(...
function bW (line 31) | function bW(Z){if(Z.tag===i){var X=Z.memoizedState;if(X===null){var J=Z....
function RW (line 31) | function RW(Z){return Z.tag===q?Z.stateNode.containerInfo:null}
function HO (line 31) | function HO(Z){return M9(Z)===Z}
function jO (line 31) | function jO(Z){{var X=OO.current;if(X!==null&&X.tag===C){var J=X,G=J.sta...
function fW (line 31) | function fW(Z){if(M9(Z)!==Z)throw new Error("Unable to find node on an u...
function gW (line 31) | function gW(Z){var X=Z.alternate;if(!X){var J=M9(Z);if(J===null)throw ne...
function yW (line 31) | function yW(Z){var X=gW(Z);return X!==null?hW(X):null}
function hW (line 31) | function hW(Z){if(Z.tag===k||Z.tag===l)return Z;var X=Z.child;while(X!==...
function EO (line 31) | function EO(Z){var X=gW(Z);return X!==null?mW(X):null}
function mW (line 31) | function mW(Z){if(Z.tag===k||Z.tag===l)return Z;var X=Z.child;while(X!==...
function TO (line 31) | function TO(Z){if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__==="undefined")re...
function CO (line 31) | function CO(Z,X){if(Z4&&typeof Z4.onScheduleFiberRoot==="function")try{Z...
function IO (line 31) | function IO(Z,X){if(Z4&&typeof Z4.onCommitFiberRoot==="function")try{var...
function MO (line 31) | function MO(Z){if(Z4&&typeof Z4.onPostCommitFiberRoot==="function")try{Z...
function PO (line 31) | function PO(Z){if(Z4&&typeof Z4.onCommitFiberUnmount==="function")try{Z4...
function s0 (line 31) | function s0(Z){{if(typeof qO==="function")wO(Z),H(Z);if(Z4&&typeof Z4.se...
function SO (line 31) | function SO(Z){r=Z}
function xO (line 31) | function xO(){{var Z=new Map,X=1;for(var J=0;J<K5;J++){var G=rO(X);Z.set...
function kO (line 31) | function kO(Z){if(r!==null&&typeof r.markCommitStarted==="function")r.ma...
function pW (line 31) | function pW(){if(r!==null&&typeof r.markCommitStopped==="function")r.mar...
function g1 (line 31) | function g1(Z){if(r!==null&&typeof r.markComponentRenderStarted==="funct...
function DX (line 31) | function DX(){if(r!==null&&typeof r.markComponentRenderStopped==="functi...
function vO (line 31) | function vO(Z){if(r!==null&&typeof r.markComponentPassiveEffectMountStar...
function bO (line 31) | function bO(){if(r!==null&&typeof r.markComponentPassiveEffectMountStopp...
function RO (line 31) | function RO(Z){if(r!==null&&typeof r.markComponentPassiveEffectUnmountSt...
function fO (line 31) | function fO(){if(r!==null&&typeof r.markComponentPassiveEffectUnmountSto...
function gO (line 31) | function gO(Z){if(r!==null&&typeof r.markComponentLayoutEffectMountStart...
function yO (line 31) | function yO(){if(r!==null&&typeof r.markComponentLayoutEffectMountStoppe...
function uW (line 31) | function uW(Z){if(r!==null&&typeof r.markComponentLayoutEffectUnmountSta...
function lW (line 31) | function lW(){if(r!==null&&typeof r.markComponentLayoutEffectUnmountStop...
function hO (line 31) | function hO(Z,X,J){if(r!==null&&typeof r.markComponentErrored==="functio...
function mO (line 31) | function mO(Z,X,J){if(r!==null&&typeof r.markComponentSuspended==="funct...
function dO (line 31) | function dO(Z){if(r!==null&&typeof r.markLayoutEffectsStarted==="functio...
function pO (line 31) | function pO(){if(r!==null&&typeof r.markLayoutEffectsStopped==="function...
function uO (line 31) | function uO(Z){if(r!==null&&typeof r.markPassiveEffectsStarted==="functi...
function lO (line 31) | function lO(){if(r!==null&&typeof r.markPassiveEffectsStopped==="functio...
function cW (line 31) | function cW(Z){if(r!==null&&typeof r.markRenderStarted==="function")r.ma...
function cO (line 31) | function cO(){if(r!==null&&typeof r.markRenderYielded==="function")r.mar...
function iW (line 31) | function iW(){if(r!==null&&typeof r.markRenderStopped==="function")r.mar...
function iO (line 31) | function iO(Z){if(r!==null&&typeof r.markRenderScheduled==="function")r....
function nO (line 31) | function nO(Z,X){if(r!==null&&typeof r.markForceUpdateScheduled==="funct...
function B5 (line 31) | function B5(Z,X){if(r!==null&&typeof r.markStateUpdateScheduled==="funct...
function aO (line 31) | function aO(Z){var X=Z>>>0;if(X===0)return 32;return 31-(sO(X)/oO|0)|0}
function rO (line 31) | function rO(Z){{if(Z&EZ)return"Sync";if(Z&AX)return"InputContinuousHydra...
function p1 (line 31) | function p1(Z){switch(v9(Z)){case EZ:return EZ;case AX:return AX;case L8...
function R6 (line 31) | function R6(Z,X){var J=Z.pendingLanes;if(J===y)return y;var G=y,Y=Z.susp...
function tO (line 31) | function tO(Z,X){var J=Z.eventTimes,G=$0;while(X>0){var Y=b9(X),Q=1<<Y,$...
function eO (line 31) | function eO(Z,X){switch(Z){case EZ:case AX:case L8:return X+250;case x9:...
function ZH (line 31) | function ZH(Z,X){var{pendingLanes:J,suspendedLanes:G,pingedLanes:Y,expir...
function XH (line 31) | function XH(Z){return p1(Z.pendingLanes)}
function x5 (line 31) | function x5(Z){var X=Z.pendingLanes&~l4;if(X!==y)return X;if(X&l4)return...
function JH (line 31) | function JH(Z){return(Z&EZ)!==y}
function k5 (line 31) | function k5(Z){return(Z&oW)!==y}
function aW (line 31) | function aW(Z){return(Z&k6)===Z}
function GH (line 31) | function GH(Z){var X=EZ|L8|o7;return(Z&X)===y}
function YH (line 31) | function YH(Z){return(Z&FX)===Z}
function f6 (line 31) | function f6(Z,X){var J=AX|L8|x9|o7;return(X&J)!==y}
function QH (line 31) | function QH(Z,X){return(X&Z.expiredLanes)!==y}
function rW (line 31) | function rW(Z){return(Z&FX)!==y}
function tW (line 31) | function tW(){var Z=v6;if(v6<<=1,(v6&FX)===y)v6=h1;return Z}
function $H (line 31) | function $H(){var Z=b6;if(b6<<=1,(b6&k6)===y)b6=qX;return Z}
function v9 (line 31) | function v9(Z){return Z&-Z}
function u1 (line 31) | function u1(Z){return v9(Z)}
function b9 (line 31) | function b9(Z){return 31-nW(Z)}
function v5 (line 31) | function v5(Z){return b9(Z)}
function c4 (line 31) | function c4(Z,X){return(Z&X)!==y}
function wX (line 31) | function wX(Z,X){return(Z&X)===X}
function TZ (line 31) | function TZ(Z,X){return Z|X}
function g6 (line 31) | function g6(Z,X){return Z&~X}
function eW (line 31) | function eW(Z,X){return Z&X}
function y6 (line 31) | function y6(Z){return Z}
function WH (line 31) | function WH(Z,X){return Z!==o0&&Z<X?Z:X}
function b5 (line 31) | function b5(Z){var X=[];for(var J=0;J<K5;J++)X.push(Z);return X}
function l1 (line 31) | function l1(Z,X,J){if(Z.pendingLanes|=X,X!==k9)Z.suspendedLanes=y,Z.ping...
function VH (line 31) | function VH(Z,X){Z.suspendedLanes|=X,Z.pingedLanes&=~X;var J=Z.expiratio...
function ZV (line 31) | function ZV(Z,X,J){Z.pingedLanes|=Z.suspendedLanes&X}
function UH (line 31) | function UH(Z,X){var J=Z.pendingLanes&~X;Z.pendingLanes=X,Z.suspendedLan...
function R5 (line 31) | function R5(Z,X){var J=Z.entangledLanes|=X,G=Z.entanglements,Y=J;while(Y...
function BH (line 31) | function BH(Z,X){var J=v9(X),G;switch(J){case L8:G=AX;break;case o7:G=x9...
function XV (line 31) | function XV(Z,X,J){if(!C7)return;var G=Z.pendingUpdatersLaneMap;while(J>...
function JV (line 31) | function JV(Z,X){if(!C7)return;var{pendingUpdatersLaneMap:J,memoizedUpda...
function GV (line 31) | function GV(Z,X){return null}
function I7 (line 31) | function I7(){return c1}
function a0 (line 31) | function a0(Z){c1=Z}
function KH (line 31) | function KH(Z,X){var J=c1;try{return c1=Z,X()}finally{c1=J}}
function zH (line 31) | function zH(Z,X){return Z!==0&&Z<X?Z:X}
function _H (line 31) | function _H(Z,X){return Z===0||Z>X?Z:X}
function f5 (line 31) | function f5(Z,X){return Z!==0&&Z<X}
function YV (line 31) | function YV(Z){var X=v9(Z);if(!f5(i4,X))return i4;if(!f5(D8,X))return D8...
function m6 (line 31) | function m6(Z){var X=Z.current.memoizedState;return X.isDehydrated}
function OH (line 31) | function OH(Z){QV=Z}
function HH (line 31) | function HH(Z){QV(Z)}
function jH (line 31) | function jH(Z){g5=Z}
function EH (line 31) | function EH(Z){$V=Z}
function NH (line 31) | function NH(Z){WV=Z}
function LH (line 31) | function LH(Z){VV=Z}
function AH (line 31) | function AH(Z){return DH.indexOf(Z)>-1}
function FH (line 31) | function FH(Z,X,J,G,Y){return{blockedOn:Z,domEventName:X,eventSystemFlag...
function UV (line 31) | function UV(Z,X){switch(Z){case"focusin":case"focusout":s8=null;break;ca...
function s1 (line 31) | function s1(Z,X,J,G,Y,Q){if(Z===null||Z.nativeEvent!==Q){var $=FH(X,J,G,...
function qH (line 31) | function qH(Z,X,J,G,Y){switch(X){case"focusin":{var Q=Y;return s8=s1(s8,...
function BV (line 31) | function BV(Z){var X=g9(Z.target);if(X!==null){var J=M9(X);if(J!==null){...
function wH (line 31) | function wH(Z){var X=WV(),J={blockedOn:null,target:Z,priority:X},G=0;for...
function p6 (line 31) | function p6(Z){if(Z.blockedOn!==null)return!1;var X=Z.targetContainers;w...
function KV (line 31) | function KV(Z,X,J){if(p6(Z))J.delete(X)}
function TH (line 31) | function TH(){if(y5=!1,s8!==null&&p6(s8))s8=null;if(o8!==null&&p6(o8))o8...
function o1 (line 31) | function o1(Z,X){if(Z.blockedOn===X){if(Z.blockedOn=null,!y5)y5=!0,Z0.un...
function a1 (line 31) | function a1(Z){if(d6.length>0){o1(d6[0],Z);for(var X=1;X<d6.length;X++){...
function zV (line 31) | function zV(Z){h5=!!Z}
function CH (line 31) | function CH(){return h5}
function IH (line 31) | function IH(Z,X,J){var G=_V(X),Y;switch(G){case i4:Y=MH;break;case D8:Y=...
function MH (line 31) | function MH(Z,X,J,G){var Y=I7(),Q=TX.transition;TX.transition=null;try{a...
function PH (line 31) | function PH(Z,X,J,G){var Y=I7(),Q=TX.transition;TX.transition=null;try{a...
function m5 (line 31) | function m5(Z,X,J,G){if(!h5)return;SH(Z,X,J,G)}
function SH (line 31) | function SH(Z,X,J,G){var Y=d5(Z,X,J,G);if(Y===null){XQ(Z,X,G,u6,J),UV(Z,...
function d5 (line 31) | function d5(Z,X,J,G){u6=null;var Y=sY(G),Q=g9(Y);if(Q!==null){var $=M9(Q...
function _V (line 31) | function _V(Z){switch(Z){case"cancel":case"click":case"close":case"conte...
function xH (line 31) | function xH(Z,X,J){return Z.addEventListener(X,J,!1),J}
function kH (line 31) | function kH(Z,X,J){return Z.addEventListener(X,J,!0),J}
function vH (line 31) | function vH(Z,X,J,G){return Z.addEventListener(X,J,{capture:!0,passive:G...
function bH (line 31) | function bH(Z,X,J,G){return Z.addEventListener(X,J,{passive:G}),J}
function RH (line 31) | function RH(Z){return r1=Z,p5=HV(),!0}
function fH (line 31) | function fH(){r1=null,p5=null,t1=null}
function OV (line 31) | function OV(){if(t1)return t1;var Z,X=p5,J=X.length,G,Y=HV(),Q=Y.length;...
function HV (line 31) | function HV(){if("value"in r1)return r1.value;return r1.textContent}
function l6 (line 31) | function l6(Z){var X,J=Z.keyCode;if("charCode"in Z){if(X=Z.charCode,X===...
function c6 (line 31) | function c6(){return!0}
function jV (line 31) | function jV(){return!1}
function n4 (line 31) | function n4(Z){function X(J,G,Y,Q,$){this._reactName=J,this._targetInst=...
function yH (line 31) | function yH(Z){if(Z!==ZJ){if(ZJ&&Z.type==="mousemove")l5=Z.screenX-ZJ.sc...
function aH (line 31) | function aH(Z){if(Z.key){var X=sH[Z.key]||Z.key;if(X!=="Unidentified")re...
function tH (line 31) | function tH(Z){var X=this,J=X.nativeEvent;if(J.getModifierState)return J...
function n5 (line 31) | function n5(Z){return tH}
function Bj (line 31) | function Bj(){z4("onBeforeInput",["compositionend","keypress","textInput...
function Kj (line 31) | function Kj(Z){return(Z.ctrlKey||Z.altKey||Z.metaKey)&&!(Z.ctrlKey&&Z.al...
function zj (line 31) | function zj(Z){switch(Z){case"compositionstart":return"onCompositionStar...
function _j (line 31) | function _j(Z,X){return Z==="keydown"&&X.keyCode===DV}
function TV (line 31) | function TV(Z,X){switch(Z){case"keyup":return Vj.indexOf(X.keyCode)!==-1...
function CV (line 31) | function CV(Z){var X=Z.detail;if(typeof X==="object"&&"data"in X)return ...
function IV (line 31) | function IV(Z){return Z.locale==="ko"}
function Oj (line 31) | function Oj(Z,X,J,G,Y){var Q,$;if(s5)Q=zj(X);else if(!IX){if(_j(X,G))Q="...
function Hj (line 31) | function Hj(Z,X){switch(Z){case"compositionend":return CV(X);case"keypre...
function jj (line 31) | function jj(Z,X){if(IX){if(Z==="compositionend"||!s5&&TV(Z,X)){var J=OV(...
function Ej (line 31) | function Ej(Z,X,J,G,Y){var Q;if(Uj)Q=Hj(X,G);else Q=jj(X,G);if(!Q)return...
function Nj (line 31) | function Nj(Z,X,J,G,Y,Q,$){Oj(Z,X,J,G,Y),Ej(Z,X,J,G,Y)}
function MV (line 31) | function MV(Z){var X=Z&&Z.nodeName&&Z.nodeName.toLowerCase();if(X==="inp...
function Dj (line 31) | function Dj(Z){if(!w0)return!1;var X="on"+Z,J=X in document;if(!J){var G...
function Aj (line 31) | function Aj(){z4("onChange",["change","click","focusin","focusout","inpu...
function PV (line 31) | function PV(Z,X,J,G){CW(G);var Y=r6(X,"onChange");if(Y.length>0){var Q=n...
function Fj (line 31) | function Fj(Z){var X=Z.nodeName&&Z.nodeName.toLowerCase();return X==="se...
function qj (line 31) | function qj(Z){var X=[];PV(X,GJ,Z,sY(Z)),SW(wj,X)}
function wj (line 31) | function wj(Z){nV(Z,0)}
function n6 (line 31) | function n6(Z){var X=vX(Z);if(UX(X))return Z}
function Tj (line 31) | function Tj(Z,X){if(Z==="change")return X}
function Cj (line 31) | function Cj(Z,X){JJ=Z,GJ=X,JJ.attachEvent("onpropertychange",kV)}
function xV (line 31) | function xV(){if(!JJ)return;JJ.detachEvent("onpropertychange",kV),JJ=nul...
function kV (line 31) | function kV(Z){if(Z.propertyName!=="value")return;if(n6(GJ))qj(Z)}
function Ij (line 31) | function Ij(Z,X,J){if(Z==="focusin")xV(),Cj(X,J);else if(Z==="focusout")...
function Mj (line 31) | function Mj(Z,X){if(Z==="selectionchange"||Z==="keyup"||Z==="keydown")re...
function Pj (line 31) | function Pj(Z){var X=Z.nodeName;return X&&X.toLowerCase()==="input"&&(Z....
function Sj (line 31) | function Sj(Z,X){if(Z==="click")return n6(X)}
function xj (line 31) | function xj(Z,X){if(Z==="input"||Z==="change")return n6(X)}
function kj (line 31) | function kj(Z){var X=Z._wrapperState;if(!X||!X.controlled||Z.type!=="num...
function vj (line 31) | function vj(Z,X,J,G,Y,Q,$){var W=J?vX(J):window,V,z;if(Fj(W))V=Tj;else i...
function bj (line 31) | function bj(){X7("onMouseEnter",["mouseout","mouseover"]),X7("onMouseLea...
function Rj (line 31) | function Rj(Z,X,J,G,Y,Q,$){var W=X==="mouseover"||X==="pointerover",V=X=...
function fj (line 31) | function fj(Z,X){return Z===X&&(Z!==0||1/Z===1/X)||Z!==Z&&X!==X}
function YJ (line 31) | function YJ(Z,X){if(s4(Z,X))return!0;if(typeof Z!=="object"||Z===null||t...
function vV (line 31) | function vV(Z){while(Z&&Z.firstChild)Z=Z.firstChild;return Z}
function gj (line 31) | function gj(Z){while(Z){if(Z.nextSibling)return Z.nextSibling;Z=Z.parent...
function bV (line 31) | function bV(Z,X){var J=vV(Z),G=0,Y=0;while(J){if(J.nodeType===_8){if(Y=G...
function yj (line 31) | function yj(Z){var X=Z.ownerDocument,J=X&&X.defaultView||window,G=J.getS...
function hj (line 31) | function hj(Z,X,J,G,Y){var Q=0,$=-1,W=-1,V=0,z=0,_=Z,D=null;Z:while(!0){...
function mj (line 31) | function mj(Z,X){var J=Z.ownerDocument||document,G=J&&J.defaultView||win...
function RV (line 31) | function RV(Z){return Z&&Z.nodeType===_8}
function fV (line 31) | function fV(Z,X){if(!Z||!X)return!1;else if(Z===X)return!0;else if(RV(Z)...
function dj (line 31) | function dj(Z){return Z&&Z.ownerDocument&&fV(Z.ownerDocument.documentEle...
function pj (line 31) | function pj(Z){try{return typeof Z.contentWindow.location.href==="string...
function gV (line 31) | function gV(){var Z=window,X=i8();while(X instanceof Z.HTMLIFrameElement...
function o5 (line 31) | function o5(Z){var X=Z&&Z.nodeName&&Z.nodeName.toLowerCase();return X&&(...
function uj (line 31) | function uj(){var Z=gV();return{focusedElem:Z,selectionRange:o5(Z)?cj(Z)...
function lj (line 31) | function lj(Z){var X=gV(),J=Z.focusedElem,G=Z.selectionRange;if(X!==J&&d...
function cj (line 31) | function cj(Z){var X;if("selectionStart"in Z)X={start:Z.selectionStart,e...
function ij (line 31) | function ij(Z,X){var{start:J,end:G}=X;if(G===void 0)G=J;if("selectionSta...
function sj (line 31) | function sj(){z4("onSelect",["focusout","contextmenu","dragend","focusin...
function oj (line 31) | function oj(Z){if("selectionStart"in Z&&o5(Z))return{start:Z.selectionSt...
function aj (line 31) | function aj(Z){return Z.window===Z?Z.document:Z.nodeType===O8?Z:Z.ownerD...
function yV (line 31) | function yV(Z,X,J){var G=aj(J);if(r5||MX==null||MX!==i8(G))return;var Y=...
function rj (line 31) | function rj(Z,X,J,G,Y,Q,$){var W=J?vX(J):window;switch(X){case"focusin":...
function s6 (line 31) | function s6(Z,X){var J={};return J[Z.toLowerCase()]=X.toLowerCase(),J["W...
function o6 (line 31) | function o6(Z){if(t5[Z])return t5[Z];else if(!PX[Z])return Z;var X=PX[Z]...
function t8 (line 31) | function t8(Z,X){lV.set(Z,X),z4(X,[Z])}
function tj (line 31) | function tj(){for(var Z=0;Z<cV.length;Z++){var X=cV[Z],J=X.toLowerCase()...
function ej (line 31) | function ej(Z,X,J,G,Y,Q,$){var W=lV.get(X);if(W===void 0)return;var V=u5...
function ZE (line 31) | function ZE(Z,X,J,G,Y,Q,$){ej(Z,X,J,G,Y,Q);var W=(Q&o_)===0;if(W)Rj(Z,X,...
function iV (line 31) | function iV(Z,X,J){var G=Z.type||"unknown-event";Z.currentTarget=J,WO(G,...
function XE (line 31) | function XE(Z,X,J){var G;if(J)for(var Y=X.length-1;Y>=0;Y--){var Q=X[Y],...
function nV (line 31) | function nV(Z,X){var J=(X&S1)!==0;for(var G=0;G<Z.length;G++){var Y=Z[G]...
function JE (line 31) | function JE(Z,X,J,G,Y){var Q=sY(J),$=[];ZE($,Z,G,J,Q,X),nV($,X)}
function K0 (line 31) | function K0(Z,X){if(!e5.has(Z))U('Did not expect a listenToNonDelegatedE...
function ZQ (line 31) | function ZQ(Z,X,J){if(e5.has(Z)&&!X)U('Did not expect a listenToNativeEv...
function WJ (line 31) | function WJ(Z){if(!Z[a6]){Z[a6]=!0,N7.forEach(function(J){if(J!=="select...
function sV (line 31) | function sV(Z,X,J,G,Y){var Q=IH(Z,X,J),$=void 0;if(rY){if(X==="touchstar...
function oV (line 31) | function oV(Z,X){return Z===X||Z.nodeType===I0&&Z.parentNode===X}
function XQ (line 31) | function XQ(Z,X,J,G,Y){var Q=G;if((X&wW)===0&&(X&nY)===0){var $=Y;if(G!=...
function VJ (line 31) | function VJ(Z,X,J){return{instance:Z,listener:X,currentTarget:J}}
function GE (line 31) | function GE(Z,X,J,G,Y,Q){var $=X!==null?X+"Capture":null,W=G?$:X,V=[],z=...
function r6 (line 31) | function r6(Z,X){var J=X+"Capture",G=[],Y=Z;while(Y!==null){var Q=Y,$=Q....
function SX (line 31) | function SX(Z){if(Z===null)return null;do Z=Z.return;while(Z&&Z.tag!==k)...
function YE (line 31) | function YE(Z,X){var J=Z,G=X,Y=0;for(var Q=J;Q;Q=SX(Q))Y++;var $=0;for(v...
function aV (line 31) | function aV(Z,X,J,G,Y){var Q=X._reactName,$=[],W=J;while(W!==null){if(W=...
function QE (line 31) | function QE(Z,X,J,G,Y){var Q=G&&Y?YE(G,Y):null;if(G!==null)aV(Z,X,G,Q,!1...
function $E (line 31) | function $E(Z,X){return Z+"__"+(X?"capture":"bubble")}
function JG (line 31) | function JG(Z){R8(Z);var X=typeof Z==="string"?Z:""+Z;return X.replace(WE,`
function GG (line 32) | function GG(Z,X,J,G){var Y=JG(X),Q=JG(Z);if(Q===Y)return;if(G){if(!v4)v4...
function XU (line 32) | function XU(Z){return Z.nodeType===O8?Z:Z.ownerDocument}
function UE (line 32) | function UE(){}
function YG (line 32) | function YG(Z){Z.onclick=UE}
function BE (line 32) | function BE(Z,X,J,G,Y){for(var Q in G){if(!G.hasOwnProperty(Q))continue;...
function KE (line 32) | function KE(Z,X,J,G){for(var Y=0;Y<X.length;Y+=2){var Q=X[Y],$=X[Y+1];if...
function zE (line 32) | function zE(Z,X,J,G){var Y,Q=XU(J),$,W=G;if(W===z8)W=dY(Z);if(W===z8){if...
function _E (line 32) | function _E(Z,X){return XU(X).createTextNode(Z)}
function OE (line 32) | function OE(Z,X,J,G){var Y=q9(X,J);ZG(X,J);var Q;switch(X){case"dialog":...
function HE (line 32) | function HE(Z,X,J,G,Y){ZG(X,G);var Q=null,$,W;switch(X){case"input":$=K(...
function jE (line 32) | function jE(Z,X,J,G,Y){if(J==="input"&&Y.type==="radio"&&Y.name!=null)I(...
function EE (line 32) | function EE(Z){{var X=Z.toLowerCase();if(!C6.hasOwnProperty(X))return nu...
function NE (line 32) | function NE(Z,X,J,G,Y,Q,$){var W,V;switch(W=q9(X,J),ZG(X,J),X){case"dial...
function LE (line 32) | function LE(Z,X,J){var G=Z.nodeValue!==X;return G}
function GQ (line 32) | function GQ(Z,X){{if(v4)return;v4=!0,U("Did not expect server HTML to co...
function YQ (line 32) | function YQ(Z,X){{if(v4)return;v4=!0,U('Did not expect server HTML to co...
function QQ (line 32) | function QQ(Z,X,J){{if(v4)return;v4=!0,U("Expected server HTML to contai...
function $Q (line 32) | function $Q(Z,X){{if(X==="")return;if(v4)return;v4=!0,U('Expected server...
function DE (line 32) | function DE(Z,X,J){switch(X){case"input":_Z(Z,J);return;case"textarea":_...
function IE (line 32) | function IE(Z){var X,J,G=Z.nodeType;switch(G){case O8:case uY:{X=G===O8?...
function ME (line 32) | function ME(Z,X,J){{var G=Z,Y=pY(G.namespace,X),Q=zJ(G.ancestorInfo,X);r...
function UQ (line 32) | function UQ(Z){return Z}
function PE (line 32) | function PE(Z){WQ=CH(),VQ=uj();var X=null;return zV(!1),X}
function SE (line 32) | function SE(Z){lj(VQ),zV(WQ),WQ=null,VQ=null}
function xE (line 32) | function xE(Z,X,J,G,Y){var Q;{var $=G;if(KJ(Z,null,$.ancestorInfo),typeo...
function kE (line 32) | function kE(Z,X){Z.appendChild(X)}
function vE (line 32) | function vE(Z,X,J,G,Y){switch(OE(Z,X,J,G),X){case"button":case"input":ca...
function bE (line 32) | function bE(Z,X,J,G,Y,Q){{var $=Q;if(typeof G.children!==typeof J.childr...
function BQ (line 32) | function BQ(Z,X){return Z==="textarea"||Z==="noscript"||typeof X.childre...
function RE (line 32) | function RE(Z,X,J,G){{var Y=J;KJ(null,Z,Y.ancestorInfo)}var Q=_E(Z,X);re...
function fE (line 32) | function fE(){var Z=window.event;if(Z===void 0)return A8;return _V(Z.type)}
function hE (line 32) | function hE(Z){setTimeout(function(){throw Z})}
function mE (line 32) | function mE(Z,X,J,G){switch(X){case"button":case"input":case"select":cas...
function dE (line 32) | function dE(Z,X,J,G,Y,Q){jE(Z,X,J,G,Y),EQ(Z,Y)}
function $U (line 32) | function $U(Z){T6(Z,"")}
function pE (line 32) | function pE(Z,X,J){Z.nodeValue=J}
function uE (line 32) | function uE(Z,X){Z.appendChild(X)}
function lE (line 32) | function lE(Z,X){var J;if(Z.nodeType===I0)J=Z.parentNode,J.insertBefore(...
function cE (line 32) | function cE(Z,X,J){Z.insertBefore(X,J)}
function iE (line 32) | function iE(Z,X,J){if(Z.nodeType===I0)Z.parentNode.insertBefore(X,J);els...
function nE (line 32) | function nE(Z,X){Z.removeChild(X)}
function sE (line 32) | function sE(Z,X){if(Z.nodeType===I0)Z.parentNode.removeChild(X);else Z.r...
function _Q (line 32) | function _Q(Z,X){var J=X,G=0;do{var Y=J.nextSibling;if(Z.removeChild(J),...
function oE (line 32) | function oE(Z,X){if(Z.nodeType===I0)_Q(Z.parentNode,X);else if(Z.nodeTyp...
function aE (line 32) | function aE(Z){Z=Z;var X=Z.style;if(typeof X.setProperty==="function")X....
function rE (line 32) | function rE(Z){Z.nodeValue=""}
function tE (line 32) | function tE(Z,X){Z=Z;var J=X[CE],G=J!==void 0&&J!==null&&J.hasOwnPropert...
function eE (line 32) | function eE(Z,X){Z.nodeValue=X}
function ZN (line 32) | function ZN(Z){if(Z.nodeType===k4)Z.textContent="";else if(Z.nodeType===...
function XN (line 32) | function XN(Z,X,J){if(Z.nodeType!==k4||X.toLowerCase()!==Z.nodeName.toLo...
function JN (line 32) | function JN(Z,X){if(X===""||Z.nodeType!==_8)return null;return Z}
function GN (line 32) | function GN(Z){if(Z.nodeType!==I0)return null;return Z}
function WU (line 32) | function WU(Z){return Z.data===_J}
function OQ (line 32) | function OQ(Z){return Z.data===OJ}
function YN (line 32) | function YN(Z){var X=Z.nextSibling&&Z.nextSibling.dataset,J,G,Y;if(X)J=X...
function QN (line 32) | function QN(Z,X){Z._reactRetry=X}
function VG (line 32) | function VG(Z){for(;Z!=null;Z=Z.nextSibling){var X=Z.nodeType;if(X===k4|...
function HJ (line 32) | function HJ(Z){return VG(Z.nextSibling)}
function $N (line 32) | function $N(Z){return VG(Z.firstChild)}
function WN (line 32) | function WN(Z){return VG(Z.firstChild)}
function VN (line 32) | function VN(Z){return VG(Z.nextSibling)}
function UN (line 32) | function UN(Z,X,J,G,Y,Q,$){EJ(Q,Z),EQ(Z,J);var W;{var V=Y;W=V.namespace}...
function BN (line 32) | function BN(Z,X,J,G){EJ(J,Z);var Y=(J.mode&vZ)!==VZ;return LE(Z,X)}
function KN (line 32) | function KN(Z,X){EJ(X,Z)}
function zN (line 32) | function zN(Z){var X=Z.nextSibling,J=0;while(X){if(X.nodeType===I0){var ...
function VU (line 32) | function VU(Z){var X=Z.previousSibling,J=0;while(X){if(X.nodeType===I0){...
function _N (line 32) | function _N(Z){a1(Z)}
function ON (line 32) | function ON(Z){a1(Z)}
function HN (line 32) | function HN(Z){return Z!=="head"&&Z!=="body"}
function jN (line 32) | function jN(Z,X,J,G){var Y=!0;GG(X.nodeValue,J,G,Y)}
function EN (line 32) | function EN(Z,X,J,G,Y,Q){if(X[QG]!==!0){var $=!0;GG(G.nodeValue,Y,Q,$)}}
function NN (line 32) | function NN(Z,X){if(X.nodeType===k4)GQ(Z,X);else if(X.nodeType===I0);els...
function LN (line 32) | function LN(Z,X){{var J=Z.parentNode;if(J!==null)if(X.nodeType===k4)GQ(J...
function DN (line 32) | function DN(Z,X,J,G,Y){if(Y||X[QG]!==!0)if(G.nodeType===k4)GQ(J,G);else ...
function AN (line 32) | function AN(Z,X,J){QQ(Z,X)}
function FN (line 32) | function FN(Z,X){$Q(Z,X)}
function qN (line 32) | function qN(Z,X,J){{var G=Z.parentNode;if(G!==null)QQ(G,X)}}
function wN (line 32) | function wN(Z,X){{var J=Z.parentNode;if(J!==null)$Q(J,X)}}
function TN (line 32) | function TN(Z,X,J,G,Y,Q){if(Q||X[QG]!==!0)QQ(J,G)}
function CN (line 32) | function CN(Z,X,J,G,Y){if(Y||X[QG]!==!0)$Q(J,G)}
function IN (line 32) | function IN(Z){U("An error occurred during hydration. The server HTML wa...
function MN (line 32) | function MN(Z){WJ(Z)}
function xN (line 32) | function xN(Z){delete Z[kX],delete Z[HQ],delete Z[jQ],delete Z[PN],delet...
function EJ (line 32) | function EJ(Z,X){X[kX]=Z}
function UG (line 32) | function UG(Z,X){X[jJ]=Z}
function UU (line 32) | function UU(Z){Z[jJ]=null}
function NJ (line 32) | function NJ(Z){return!!Z[jJ]}
function g9 (line 32) | function g9(Z){var X=Z[kX];if(X)return X;var J=Z.parentNode;while(J){if(...
function Z9 (line 32) | function Z9(Z){var X=Z[kX]||Z[jJ];if(X)if(X.tag===k||X.tag===l||X.tag===...
function vX (line 32) | function vX(Z){if(Z.tag===k||Z.tag===l)return Z.stateNode;throw new Erro...
function BG (line 32) | function BG(Z){return Z[HQ]||null}
function EQ (line 32) | function EQ(Z,X){Z[HQ]=X}
function kN (line 32) | function kN(Z){var X=Z[jQ];if(X===void 0)X=Z[jQ]=new Set;return X}
function KG (line 32) | function KG(Z){if(Z){var X=Z._owner,J=u8(Z.type,Z._source,X?X.type:null)...
function M7 (line 32) | function M7(Z,X,J,G,Y){{var Q=Function.call.bind(h4);for(var $ in Z)if(Q...
function X9 (line 32) | function X9(Z){return{current:Z}}
function j4 (line 32) | function j4(Z,X){if(F8<0){U("Unexpected pop.");return}if(X!==zG[F8])U("U...
function E4 (line 32) | function E4(Z,X,J){F8++,NQ[F8]=Z.current,zG[F8]=J,Z.current=X}
function bX (line 32) | function bX(Z,X,J){{if(J&&r7(X))return LQ;return q8.current}}
function _U (line 32) | function _U(Z,X,J){{var G=Z.stateNode;G.__reactInternalMemoizedUnmaskedC...
function RX (line 32) | function RX(Z,X){{var J=Z.type,G=J.contextTypes;if(!G)return o4;var Y=Z....
function _G (line 32) | function _G(){return a7.current}
function r7 (line 32) | function r7(Z){{var X=Z.childContextTypes;return X!==null&&X!==void 0}}
function OG (line 32) | function OG(Z){j4(a7,Z),j4(q8,Z)}
function DQ (line 32) | function DQ(Z){j4(a7,Z),j4(q8,Z)}
function OU (line 32) | function OU(Z,X,J){{if(q8.current!==o4)throw new Error("Unexpected conte...
function HU (line 32) | function HU(Z,X,J){{var G=Z.stateNode,Y=X.childContextTypes;if(typeof G....
function HG (line 32) | function HG(Z){{var X=Z.stateNode,J=X&&X.__reactInternalMemoizedMergedCh...
function jU (line 32) | function jU(Z,X,J){{var G=Z.stateNode;if(!G)throw new Error("Expected to...
function vN (line 32) | function vN(Z){{if(!HO(Z)||Z.tag!==C)throw new Error("Expected subtree p...
function EU (line 32) | function EU(Z){if(w8===null)w8=[Z];else w8.push(Z)}
function bN (line 32) | function bN(Z){AQ=!0,EU(Z)}
function NU (line 32) | function NU(){if(AQ)G9()}
function G9 (line 32) | function G9(){if(!FQ&&w8!==null){FQ=!0;var Z=0,X=I7();try{var J=!0,G=w8;...
function RN (line 32) | function RN(Z){return m9(),(Z.flags&vW)!==KZ}
function fN (line 32) | function fN(Z){return m9(),NG}
function gN (line 32) | function gN(){var Z=C8,X=T8,J=X&~yN(X);return J.toString(32)+Z}
function h9 (line 32) | function h9(Z,X){m9(),fX[gX++]=NG,fX[gX++]=EG,EG=Z,NG=X}
function LU (line 32) | function LU(Z,X,J){m9(),W7[V7++]=T8,W7[V7++]=C8,W7[V7++]=y9,y9=Z;var G=T...
function qQ (line 32) | function qQ(Z){m9();var X=Z.return;if(X!==null){var J=1,G=0;h9(Z,J),LU(Z...
function LG (line 32) | function LG(Z){return 32-nW(Z)}
function yN (line 32) | function yN(Z){return 1<<LG(Z)-1}
function wQ (line 32) | function wQ(Z){while(Z===EG)EG=fX[--gX],fX[gX]=null,NG=fX[--gX],fX[gX]=n...
function hN (line 32) | function hN(){if(m9(),y9!==null)return{id:T8,overflow:C8};else return null}
function mN (line 32) | function mN(Z,X){m9(),W7[V7++]=T8,W7[V7++]=C8,W7[V7++]=y9,T8=X.id,C8=X.o...
function m9 (line 32) | function m9(){if(!J4())U("Expected to be hydrating. This is a bug in Rea...
function dN (line 32) | function dN(){if(P7)U("We should not be hydrating here. This is a bug in...
function DU (line 32) | function DU(){d9=!0}
function pN (line 32) | function pN(){return d9}
function uN (line 32) | function uN(Z){var X=Z.stateNode.containerInfo;return U7=WN(X),X4=Z,P7=!...
function lN (line 32) | function lN(Z,X,J){if(U7=VN(X),X4=Z,P7=!0,Y9=null,d9=!1,J!==null)mN(Z,J)...
function AU (line 32) | function AU(Z,X){switch(Z.tag){case q:{NN(Z.stateNode.containerInfo,X);b...
function FU (line 32) | function FU(Z,X){AU(Z,X);var J=tA();J.stateNode=X,J.return=Z;var G=Z.del...
function TQ (line 32) | function TQ(Z,X){{if(d9)return;switch(Z.tag){case q:{var J=Z.stateNode.c...
function qU (line 32) | function qU(Z,X){X.flags=X.flags&~j8|M0,TQ(Z,X)}
function wU (line 32) | function wU(Z,X){switch(Z.tag){case k:{var{type:J,pendingProps:G}=Z,Y=XN...
function CQ (line 32) | function CQ(Z){return(Z.mode&vZ)!==VZ&&(Z.flags&iZ)===KZ}
function IQ (line 32) | function IQ(Z){throw new Error("Hydration failed because the initial UI ...
function MQ (line 32) | function MQ(Z){if(!P7)return;var X=U7;if(!X){if(CQ(Z))TQ(X4,Z),IQ();qU(X...
function cN (line 32) | function cN(Z,X,J){var G=Z.stateNode,Y=!d9,Q=UN(G,Z.type,Z.memoizedProps...
function iN (line 32) | function iN(Z){var{stateNode:X,memoizedProps:J}=Z,G=BN(X,J,Z);if(G){var ...
function nN (line 32) | function nN(Z){var X=Z.memoizedState,J=X!==null?X.dehydrated:null;if(!J)...
function sN (line 32) | function sN(Z){var X=Z.memoizedState,J=X!==null?X.dehydrated:null;if(!J)...
function TU (line 32) | function TU(Z){var X=Z.return;while(X!==null&&X.tag!==k&&X.tag!==q&&X.ta...
function DG (line 32) | function DG(Z){if(Z!==X4)return!1;if(!P7)return TU(Z),P7=!0,!1;if(Z.tag!...
function oN (line 32) | function oN(){return P7&&U7!==null}
function CU (line 32) | function CU(Z){var X=U7;while(X)AU(Z,X),X=HJ(X)}
function yX (line 32) | function yX(){X4=null,U7=null,P7=!1,d9=!1}
function IU (line 32) | function IU(){if(Y9!==null)AK(Y9),Y9=null}
function J4 (line 32) | function J4(){return P7}
function PQ (line 32) | function PQ(Z){if(Y9===null)Y9=[Z];else Y9.push(Z)}
function tN (line 32) | function tN(){return aN.transition}
function ZL (line 67) | function ZL(Z){return Z.prototype&&Z.prototype.isReactComponent}
function TJ (line 67) | function TJ(Z,X,J){var G=J.ref;if(G!==null&&typeof G!=="function"&&typeo...
function FG (line 71) | function FG(Z,X){var J=Object.prototype.toString.call(X);throw new Error...
function qG (line 71) | function qG(Z){{var X=wZ(Z)||"Component";if(bQ[X])return;bQ[X]=!0,U("Fun...
function SU (line 71) | function SU(Z){var{_payload:X,_init:J}=Z;return J(X)}
function xU (line 71) | function xU(Z){function X(w,R){if(!Z)return;var T=w.deletions;if(T===nul...
function XL (line 71) | function XL(Z,X){if(Z!==null&&X.child!==Z.child)throw new Error("Resumin...
function JL (line 71) | function JL(Z,X){var J=Z.child;while(J!==null)nA(J,X),J=J.sibling}
function CG (line 71) | function CG(){wG=null,mX=null,gQ=null,TG=!1}
function vU (line 71) | function vU(){TG=!0}
function bU (line 71) | function bU(){TG=!1}
function RU (line 71) | function RU(Z,X,J){{E4(RQ,X._currentValue,Z),X._currentValue=J;{if(X._cu...
function yQ (line 71) | function yQ(Z,X){var J=RQ.current;j4(RQ,X),Z._currentValue=J}
function hQ (line 71) | function hQ(Z,X,J){var G=Z;while(G!==null){var Y=G.alternate;if(!wX(G.ch...
function GL (line 71) | function GL(Z,X,J){YL(Z,X,J)}
function YL (line 71) | function YL(Z,X,J){var G=Z.child;if(G!==null)G.return=Z;while(G!==null){...
function dX (line 71) | function dX(Z,X){wG=Z,mX=null,gQ=null;var J=Z.dependencies;if(J!==null){...
function P0 (line 71) | function P0(Z){if(TG)U("Context can only be read while React is renderin...
function mQ (line 71) | function mQ(Z){if(l9===null)l9=[Z];else l9.push(Z)}
function QL (line 71) | function QL(){if(l9!==null){for(var Z=0;Z<l9.length;Z++){var X=l9[Z],J=X...
function fU (line 71) | function fU(Z,X,J,G){var Y=X.interleaved;if(Y===null)J.next=J,mQ(X);else...
function $L (line 71) | function $L(Z,X,J,G){var Y=X.interleaved;if(Y===null)J.next=J,mQ(X);else...
function WL (line 71) | function WL(Z,X,J,G){var Y=X.interleaved;if(Y===null)J.next=J,mQ(X);else...
function b4 (line 71) | function b4(Z,X){return IG(Z,X)}
function IG (line 71) | function IG(Z,X){Z.lanes=TZ(Z.lanes,X);var J=Z.alternate;if(J!==null)J.l...
function uQ (line 71) | function uQ(Z){var X={baseState:Z.memoizedState,firstBaseUpdate:null,las...
function hU (line 71) | function hU(Z,X){var J=X.updateQueue,G=Z.updateQueue;if(J===G){var Y={ba...
function I8 (line 71) | function I8(Z,X){var J={eventTime:Z,lane:X,tag:gU,payload:null,callback:...
function Q9 (line 71) | function Q9(Z,X,J){var G=Z.updateQueue;if(G===null)return null;var Y=G.s...
function xG (line 71) | function xG(Z,X,J){var G=X.updateQueue;if(G===null)return;var Y=G.shared...
function lQ (line 71) | function lQ(Z,X){var{updateQueue:J,alternate:G}=Z;if(G!==null){var Y=G.u...
function UL (line 71) | function UL(Z,X,J,G,Y,Q){switch(J.tag){case yU:{var $=J.payload;if(typeo...
function kG (line 71) | function kG(Z,X,J,G){var Y=Z.updateQueue;PG=!1,SG=Y.shared;var{firstBase...
function BL (line 71) | function BL(Z,X){if(typeof Z!=="function")throw new Error("Invalid argum...
function mU (line 71) | function mU(){PG=!1}
function vG (line 71) | function vG(){return PG}
function dU (line 71) | function dU(Z,X,J){var G=X.effects;if(X.effects=null,G!==null)for(var Y=...
function RG (line 71) | function RG(Z){if(Z===CJ)throw new Error("Expected host context to exist...
function pU (line 71) | function pU(){var Z=RG(bG.current);return Z}
function cQ (line 71) | function cQ(Z,X){E4(bG,X,Z),E4(IJ,Z,Z),E4($9,CJ,Z);var J=IE(X);j4($9,Z),...
function pX (line 71) | function pX(Z){j4($9,Z),j4(IJ,Z),j4(bG,Z)}
function iQ (line 71) | function iQ(){var Z=RG($9.current);return Z}
function uU (line 71) | function uU(Z){var X=RG(bG.current),J=RG($9.current),G=ME(J,Z.type);if(J...
function nQ (line 71) | function nQ(Z){if(IJ.current!==Z)return;j4($9,Z),j4(IJ,Z)}
function sQ (line 71) | function sQ(Z,X){return(Z&X)!==0}
function uX (line 71) | function uX(Z){return Z&lU}
function oQ (line 71) | function oQ(Z,X){return Z&lU|X}
function zL (line 71) | function zL(Z,X){return Z|X}
function W9 (line 71) | function W9(Z,X){E4(x7,X,Z)}
function lX (line 71) | function lX(Z){j4(x7,Z)}
function _L (line 71) | function _L(Z,X){var J=Z.memoizedState;if(J!==null){if(J.dehydrated!==nu...
function fG (line 71) | function fG(Z){var X=Z;while(X!==null){if(X.tag===i){var J=X.memoizedSta...
function rQ (line 71) | function rQ(){for(var Z=0;Z<aQ.length;Z++){var X=aQ[Z];X._workInProgress...
function OL (line 71) | function OL(Z,X){var J=X._getVersion,G=J(X._source);if(Z.mutableSourceEa...
function sZ (line 71) | function sZ(){{var Z=f;if(B7===null)B7=[Z];else B7.push(Z)}}
function c (line 71) | function c(){{var Z=f;if(B7!==null){if(V9++,B7[V9]!==Z)EL(Z)}}}
function iX (line 71) | function iX(Z){if(Z!==void 0&&Z!==null&&!cZ(Z))U("%s received a final ar...
function EL (line 71) | function EL(Z){{var X=wZ(tZ);if(!tQ.has(X)){if(tQ.add(X),B7!==null){var ...
function N4 (line 77) | function N4(){throw new Error(`Invalid hook call. Hooks can only be call...
function Z2 (line 81) | function Z2(Z,X){if(eQ)return!1;if(X===null)return U("%s received a fina...
function nX (line 84) | function nX(Z,X,J,G,Y,Q){if(c9=Q,tZ=X,B7=Z!==null?Z._debugHookTypes:null...
function sX (line 84) | function sX(){var Z=xJ!==0;return xJ=0,Z}
function iU (line 84) | function iU(Z,X,J){if(X.updateQueue=Z.updateQueue,(X.mode&s7)!==VZ)X.fla...
function nU (line 84) | function nU(){if(t.current=tG,gG){var Z=tZ.memoizedState;while(Z!==null)...
function e7 (line 84) | function e7(){var Z={memoizedState:null,baseState:null,baseQueue:null,qu...
function K7 (line 84) | function K7(){var Z;if(g0===null){var X=tZ.alternate;if(X!==null)Z=X.mem...
function sU (line 84) | function sU(){return{lastEffect:null,stores:null}}
function X2 (line 84) | function X2(Z,X){return typeof X==="function"?X(Z):X}
function J2 (line 84) | function J2(Z,X,J){var G=e7(),Y;if(J!==void 0)Y=J(X);else Y=X;G.memoized...
function G2 (line 84) | function G2(Z,X,J){var G=K7(),Y=G.queue;if(Y===null)throw new Error("Sho...
function Y2 (line 84) | function Y2(Z,X,J){var G=K7(),Y=G.queue;if(Y===null)throw new Error("Sho...
function Q2 (line 84) | function Q2(Z,X,J){return}
function yG (line 84) | function yG(Z,X,J){return}
function $2 (line 84) | function $2(Z,X,J){var G=tZ,Y=e7(),Q,$=J4();if($){if(J===void 0)throw ne...
function hG (line 84) | function hG(Z,X,J){var G=tZ,Y=K7(),Q=X();if(!cX){var $=X();if(!s4(Q,$))U...
function oU (line 84) | function oU(Z,X,J){Z.flags|=P6;var G={getSnapshot:X,value:J},Y=tZ.update...
function aU (line 84) | function aU(Z,X,J,G){if(X.value=J,X.getSnapshot=G,tU(X))eU(Z)}
function rU (line 84) | function rU(Z,X,J){var G=function(){if(tU(X))eU(Z)};return J(G)}
function tU (line 84) | function tU(Z){var{getSnapshot:X,value:J}=Z;try{var G=X();return!s4(J,G)...
function eU (line 84) | function eU(Z){var X=b4(Z,EZ);if(X!==null)p0(X,Z,EZ,$0)}
function mG (line 84) | function mG(Z){var X=e7();if(typeof Z==="function")Z=Z();X.memoizedState...
function W2 (line 84) | function W2(Z){return G2(X2)}
function V2 (line 84) | function V2(Z){return Y2(X2)}
function kJ (line 84) | function kJ(Z,X,J,G){var Y={tag:Z,create:X,destroy:J,deps:G,next:null},Q...
function U2 (line 84) | function U2(Z){var X=e7();{var J={current:Z};return X.memoizedState=J,J}}
function dG (line 84) | function dG(Z){var X=K7();return X.memoizedState}
function vJ (line 84) | function vJ(Z,X,J,G){var Y=e7(),Q=G===void 0?null:G;tZ.flags|=Z,Y.memoiz...
function pG (line 84) | function pG(Z,X,J,G){var Y=K7(),Q=G===void 0?null:G,$=void 0;if(g0!==nul...
function uG (line 84) | function uG(Z,X){if((tZ.mode&s7)!==VZ)return vJ(S6|T7|Q5,G4,Z,X);else re...
function bJ (line 84) | function bJ(Z,X){return pG(T7,G4,Z,X)}
function B2 (line 84) | function B2(Z,X){return vJ(fZ,t7,Z,X)}
function lG (line 84) | function lG(Z,X){return pG(fZ,t7,Z,X)}
function K2 (line 84) | function K2(Z,X){var J=fZ;if(J|=I9,(tZ.mode&s7)!==VZ)J|=E8;return vJ(J,f...
function cG (line 84) | function cG(Z,X){return pG(fZ,f0,Z,X)}
function ZB (line 84) | function ZB(Z,X){if(typeof X==="function"){var J=X,G=Z();return J(G),fun...
function z2 (line 84) | function z2(Z,X,J){if(typeof X!=="function")U("Expected useImperativeHan...
function iG (line 84) | function iG(Z,X,J){if(typeof X!=="function")U("Expected useImperativeHan...
function nG (line 84) | function nG(Z,X){}
function _2 (line 84) | function _2(Z,X){var J=e7(),G=X===void 0?null:X;return J.memoizedState=[...
function oG (line 84) | function oG(Z,X){var J=K7(),G=X===void 0?null:X,Y=J.memoizedState;if(Y!=...
function O2 (line 84) | function O2(Z,X){var J=e7(),G=X===void 0?null:X,Y=Z();return J.memoizedS...
function aG (line 84) | function aG(Z,X){var J=K7(),G=X===void 0?null:X,Y=J.memoizedState;if(Y!=...
function H2 (line 84) | function H2(Z){var X=e7();return X.memoizedState=Z,Z}
function XB (line 84) | function XB(Z){var X=K7(),J=g0,G=J.memoizedState;return GB(X,G,Z)}
function JB (line 84) | function JB(Z){var X=K7();if(g0===null)return X.memoizedState=Z,Z;else{v...
function GB (line 84) | function GB(Z,X,J){var G=!GH(c9);if(G){if(!s4(J,X)){var Y=tW();tZ.lanes=...
function NL (line 84) | function NL(Z,X,J){var G=I7();a0(zH(G,D8)),Z(!0);var Y=PJ.transition;PJ....
function j2 (line 84) | function j2(){var Z=mG(!1),X=Z[0],J=Z[1],G=NL.bind(null,J),Y=e7();return...
function YB (line 84) | function YB(){var Z=W2(),X=Z[0],J=K7(),G=J.memoizedState;return[X,G]}
function QB (line 84) | function QB(){var Z=V2(),X=Z[0],J=K7(),G=J.memoizedState;return[X,G]}
function LL (line 84) | function LL(){return $B}
function E2 (line 84) | function E2(){var Z=e7(),X=EY(),J=X.identifierPrefix,G;if(J4()){var Y=gN...
function rG (line 84) | function rG(){var Z=K7(),X=Z.memoizedState;return X}
function DL (line 84) | function DL(Z,X,J){if(typeof arguments[3]==="function")U("State updates ...
function AL (line 84) | function AL(Z,X,J){if(typeof arguments[3]==="function")U("State updates ...
function WB (line 84) | function WB(Z){var X=Z.alternate;return Z===tZ||X!==null&&X===tZ}
function VB (line 84) | function VB(Z,X){SJ=gG=!0;var J=Z.pending;if(J===null)X.next=X;else X.ne...
function UB (line 84) | function UB(Z,X,J){if(rW(J)){var G=X.lanes;G=eW(G,Z.pendingLanes);var Y=...
function BB (line 84) | function BB(Z,X,J){B5(Z,X)}
function jB (line 84) | function jB(){return L2}
function FL (line 84) | function FL(){JY=!0}
function qL (line 84) | function qL(){L2=!1,JY=!1}
function wL (line 84) | function wL(){L2=JY,JY=!1}
function EB (line 84) | function EB(){return HB}
function NB (line 84) | function NB(){HB=U9()}
function D2 (line 84) | function D2(Z){if(RJ=U9(),Z.actualStartTime<0)Z.actualStartTime=U9()}
function LB (line 84) | function LB(Z){RJ=-1}
function GY (line 84) | function GY(Z,X){if(RJ>=0){var J=U9()-RJ;if(Z.actualDuration+=J,X)Z.self...
function X8 (line 84) | function X8(Z){if(ZY>=0){var X=U9()-ZY;ZY=-1;var J=Z.return;while(J!==nu...
function A2 (line 84) | function A2(Z){if(XY>=0){var X=U9()-XY;XY=-1;var J=Z.return;while(J!==nu...
function J8 (line 84) | function J8(){ZY=U9()}
function F2 (line 84) | function F2(){XY=U9()}
function q2 (line 84) | function q2(Z){var X=Z.child;while(X)Z.actualDuration+=X.actualDuration,...
function v7 (line 84) | function v7(Z,X){if(Z&&Z.defaultProps){var J=xZ({},X),G=Z.defaultProps;f...
function v2 (line 84) | function v2(Z,X,J,G){var Y=Z.memoizedState,Q=J(G,Y);{if(Z.mode&A0){s0(!0...
function FB (line 84) | function FB(Z,X,J,G,Y,Q,$){var W=Z.stateNode;if(typeof W.shouldComponent...
function TL (line 84) | function TL(Z,X,J){var G=Z.stateNode;{var Y=hZ(X)||"Component",Q=G.rende...
function qB (line 88) | function qB(Z,X){X.updater=b2,Z.stateNode=X,KO(X,Z),X._reactInternalInst...
function wB (line 88) | function wB(Z,X,J){var G=!1,Y=o4,Q=o4,$=X.contextType;if("contextType"in...
function CL (line 96) | function CL(Z,X){var J=X.state;if(typeof X.componentWillMount==="functio...
function TB (line 96) | function TB(Z,X,J,G){var Y=X.state;if(typeof X.componentWillReceiveProps...
function R2 (line 96) | function R2(Z,X,J,G){TL(Z,X,J);var Y=Z.stateNode;Y.props=J,Y.state=Z.mem...
function IL (line 96) | function IL(Z,X,J,G){var{stateNode:Y,memoizedProps:Q}=Z;Y.props=Q;var $=...
function ML (line 96) | function ML(Z,X,J,G,Y){var Q=X.stateNode;hU(Z,X);var $=X.memoizedProps,W...
function i9 (line 96) | function i9(Z,X){return{value:Z,source:X,stack:w1(X),digest:null}}
function f2 (line 96) | function f2(Z,X,J){return{value:Z,source:null,stack:J!=null?J:null,diges...
function PL (line 96) | function PL(Z,X){return!0}
function g2 (line 96) | function g2(Z,X){try{var J=PL(Z,X);if(J===!1)return;var{value:G,source:Y...
function CB (line 100) | function CB(Z,X,J){var G=I8($0,J);G.tag=dQ,G.payload={element:null};var ...
function y2 (line 100) | function y2(Z,X,J){var G=I8($0,J);G.tag=dQ;var Y=Z.type.getDerivedStateF...
function IB (line 100) | function IB(Z,X,J){var G=Z.pingCache,Y;if(G===null)G=Z.pingCache=new SL,...
function xL (line 100) | function xL(Z,X,J,G){var Y=Z.updateQueue;if(Y===null){var Q=new Set;Q.ad...
function kL (line 100) | function kL(Z,X){var J=Z.tag;if((Z.mode&vZ)===VZ&&(J===A||J===g||J===HZ)...
function MB (line 100) | function MB(Z){var X=Z;do{if(X.tag===i&&_L(X))return X;X=X.return}while(...
function PB (line 100) | function PB(Z,X,J,G,Y){if((Z.mode&vZ)===VZ){if(Z===X)Z.flags|=q4;else{if...
function vL (line 100) | function vL(Z,X,J,G,Y){if(J.flags|=R1,C7)eJ(Z,Y);if(G!==null&&typeof G==...
function bL (line 100) | function bL(){return null}
function w4 (line 100) | function w4(Z,X,J,G){if(Z===null)X.child=kU(X,null,J,G);else X.child=hX(...
function RL (line 100) | function RL(Z,X,J,G){X.child=hX(X,Z.child,null,G),X.child=hX(X,null,J,G)}
function SB (line 100) | function SB(Z,X,J,G,Y){if(X.type!==X.elementType){var Q=J.propTypes;if(Q...
function xB (line 100) | function xB(Z,X,J,G,Y){if(Z===null){var Q=J.type;if(cA(Q)&&J.compare===n...
function kB (line 100) | function kB(Z,X,J,G,Y){if(X.type!==X.elementType){var Q=X.elementType;if...
function vB (line 100) | function vB(Z,X,J){var G=X.pendingProps,Y=G.children,Q=Z!==null?Z.memoiz...
function fL (line 100) | function fL(Z,X,J){var G=X.pendingProps;return w4(Z,X,G,J),X.child}
function gL (line 100) | function gL(Z,X,J){var G=X.pendingProps.children;return w4(Z,X,G,J),X.ch...
function yL (line 100) | function yL(Z,X,J){{X.flags|=fZ;{var G=X.stateNode;G.effectDuration=0,G....
function bB (line 100) | function bB(Z,X){var J=X.ref;if(Z===null&&J!==null||Z!==null&&Z.ref!==J)...
function l2 (line 100) | function l2(Z,X,J,G,Y){if(X.type!==X.elementType){var Q=J.propTypes;if(Q...
function RB (line 100) | function RB(Z,X,J,G,Y){{switch($F(X)){case!1:{var{stateNode:Q,type:$}=X,...
function c2 (line 100) | function c2(Z,X,J,G,Y,Q){bB(Z,X);var $=(X.flags&iZ)!==KZ;if(!G&&!$){if(Y...
function fB (line 100) | function fB(Z){var X=Z.stateNode;if(X.pendingContext)OU(Z,X.pendingConte...
function hL (line 100) | function hL(Z,X,J){if(fB(X),Z===null)throw new Error("Should have a curr...
function gB (line 100) | function gB(Z,X,J,G,Y){return yX(),PQ(Y),X.flags|=H8,w4(Z,X,J,G),X.child}
function mL (line 100) | function mL(Z,X,J){if(uU(X),Z===null)MQ(X);var{type:G,pendingProps:Y}=X,...
function dL (line 100) | function dL(Z,X){if(Z===null)MQ(X);return null}
function pL (line 100) | function pL(Z,X,J,G){WY(Z,X);var Y=X.pendingProps,Q=J,$=Q._payload,W=Q._...
function uL (line 100) | function uL(Z,X,J,G,Y){WY(Z,X),X.tag=C;var Q;if(r7(J))Q=!0,HG(X);else Q=...
function lL (line 100) | function lL(Z,X,J,G){WY(Z,X);var Y=X.pendingProps,Q;{var $=bX(X,J,!1);Q=...
function i2 (line 100) | function i2(Z,X){{if(X){if(X.childContextTypes)U("%s(...): childContextT...
function s2 (line 102) | function s2(Z){return{baseLanes:Z,cachePool:bL(),transitions:null}}
function cL (line 102) | function cL(Z,X){var J=null;return{baseLanes:TZ(Z.baseLanes,X),cachePool...
function iL (line 102) | function iL(Z,X,J,G){if(X!==null){var Y=X.memoizedState;if(Y===null)retu...
function nL (line 102) | function nL(Z,X){return g6(Z.childLanes,X)}
function yB (line 102) | function yB(Z,X,J){var G=X.pendingProps;if(WF(X))X.flags|=iZ;var Y=x7.cu...
function o2 (line 102) | function o2(Z,X,J){var G=Z.mode,Y={mode:"visible",children:X},Q=a2(Y,G);...
function sL (line 102) | function sL(Z,X,J,G){var{mode:Y,child:Q}=Z,$={mode:"hidden",children:X},...
function a2 (line 102) | function a2(Z,X,J){return mK(Z,X,y,null)}
function hB (line 102) | function hB(Z,X){return t9(Z,X)}
function oL (line 102) | function oL(Z,X,J,G){var Y=Z.child,Q=Y.sibling,$=hB(Y,{mode:"visible",ch...
function aL (line 102) | function aL(Z,X,J,G,Y){var Q=X.mode,$=Z.child,W=$.sibling,V={mode:"hidde...
function $Y (line 102) | function $Y(Z,X,J,G){if(G!==null)PQ(G);hX(X,Z.child,null,J);var Y=X.pend...
function rL (line 102) | function rL(Z,X,J,G,Y){var Q=X.mode,$={mode:"visible",children:J},W=a2($...
function tL (line 102) | function tL(Z,X,J){if((Z.mode&vZ)===VZ)U("Cannot hydrate Suspense in leg...
function eL (line 102) | function eL(Z,X,J,G,Y,Q,$){if(!J){if(dN(),(X.mode&vZ)===VZ)return $Y(Z,X...
function mB (line 102) | function mB(Z,X,J){Z.lanes=TZ(Z.lanes,X);var G=Z.alternate;if(G!==null)G...
function ZD (line 102) | function ZD(Z,X,J){var G=X;while(G!==null){if(G.tag===i){var Y=G.memoize...
function XD (line 102) | function XD(Z){var X=Z,J=null;while(X!==null){var G=X.alternate;if(G!==n...
function JD (line 102) | function JD(Z){if(Z!==void 0&&Z!=="forwards"&&Z!=="backwards"&&Z!=="toge...
function GD (line 102) | function GD(Z,X){if(Z!==void 0&&!QY[Z]){if(Z!=="collapsed"&&Z!=="hidden"...
function dB (line 102) | function dB(Z,X){{var J=cZ(Z),G=!J&&typeof w7(Z)==="function";if(J||G){v...
function YD (line 102) | function YD(Z,X){if((X==="forwards"||X==="backwards")&&Z!==void 0&&Z!==n...
function r2 (line 102) | function r2(Z,X,J,G,Y){var Q=Z.memoizedState;if(Q===null)Z.memoizedState...
function pB (line 102) | function pB(Z,X,J){var G=X.pendingProps,Y=G.revealOrder,Q=G.tail,$=G.chi...
function QD (line 102) | function QD(Z,X,J){cQ(X,X.stateNode.containerInfo);var G=X.pendingProps;...
function $D (line 102) | function $D(Z,X,J){var G=X.type,Y=G._context,Q=X.pendingProps,$=X.memoiz...
function WD (line 102) | function WD(Z,X,J){var G=X.type;if(G._context===void 0){if(G!==G.Consume...
function mJ (line 102) | function mJ(){b7=!0}
function WY (line 102) | function WY(Z,X){if((X.mode&vZ)===VZ){if(Z!==null)Z.alternate=null,X.alt...
function M8 (line 102) | function M8(Z,X,J){if(Z!==null)X.dependencies=Z.dependencies;if(LB(),tJ(...
function VD (line 102) | function VD(Z,X,J){{var G=X.return;if(G===null)throw new Error("Cannot s...
function t2 (line 102) | function t2(Z,X){var J=Z.lanes;if(c4(J,X))return!0;return!1}
function UD (line 102) | function UD(Z,X,J){switch(X.tag){case q:fB(X);var G=X.stateNode;yX();bre...
function cB (line 102) | function cB(Z,X,J){if(X._debugNeedsRemount&&Z!==null)return VD(Z,X,P$(X....
function oX (line 102) | function oX(Z){Z.flags|=fZ}
function iB (line 102) | function iB(Z){Z.flags|=n8,Z.flags|=Y5}
function dJ (line 102) | function dJ(Z,X){if(J4())return;switch(Z.tailMode){case"hidden":{var J=Z...
function Y4 (line 102) | function Y4(Z){var X=Z.alternate!==null&&Z.alternate.child===Z.child,J=y...
function BD (line 102) | function BD(Z,X,J){if(oN()&&(X.mode&vZ)!==VZ&&(X.flags&iZ)===KZ)return C...
function aB (line 102) | function aB(Z,X,J){var G=X.pendingProps;switch(wQ(X),X.tag){case S:case ...
function KD (line 102) | function KD(Z,X,J){switch(wQ(X),X.tag){case C:{var G=X.type;if(r7(G))OG(...
function rB (line 102) | function rB(Z,X,J){switch(wQ(X),X.tag){case C:{var G=X.type.childContext...
function _D (line 102) | function _D(Z){Z5(null,function(){throw Z}),X5()}
function eB (line 102) | function eB(Z,X){try{B9(f0,Z)}catch(J){Y0(Z,X,J)}}
function Z$ (line 102) | function Z$(Z,X,J){try{OD(Z,J)}catch(G){Y0(Z,X,G)}}
function HD (line 102) | function HD(Z,X,J){try{J.componentDidMount()}catch(G){Y0(Z,X,G)}}
function ZK (line 102) | function ZK(Z,X){try{GK(Z)}catch(J){Y0(Z,X,J)}}
function tX (line 102) | function tX(Z,X){var J=Z.ref;if(J!==null)if(typeof J==="function"){var G...
function UY (line 102) | function UY(Z,X,J){try{J()}catch(G){Y0(Z,X,G)}}
function jD (line 102) | function jD(Z,X){XK=PE(Z.containerInfo),ZZ=X,ED();var J=JK;return JK=!1,...
function ED (line 102) | function ED(){while(ZZ!==null){var Z=ZZ,X=Z.child;if((Z.subtreeFlags&$5)...
function ND (line 102) | function ND(){while(ZZ!==null){var Z=ZZ;E0(Z);try{LD(Z)}catch(J){Y0(Z,Z....
function LD (line 102) | function LD(Z){var{alternate:X,flags:J}=Z;if((J&T9)!==KZ){switch(E0(Z),Z...
function R7 (line 102) | function R7(Z,X,J){var G=X.updateQueue,Y=G!==null?G.lastEffect:null;if(Y...
function B9 (line 102) | function B9(Z,X){var J=X.updateQueue,G=J!==null?J.lastEffect:null;if(G!=...
function DD (line 115) | function DD(Z,X){if((X.flags&fZ)!==KZ)switch(X.tag){case p:{var J=X.stat...
function AD (line 115) | function AD(Z,X,J,G){if((J.flags&f1)!==KZ)switch(J.tag){case A:case g:ca...
function FD (line 115) | function FD(Z){switch(Z.tag){case A:case g:case HZ:{if(Z.mode&rZ)try{J8(...
function qD (line 115) | function qD(Z,X){var J=null;{var G=Z;while(!0){if(G.tag===k){if(J===null...
function GK (line 115) | function GK(Z){var X=Z.ref;if(X!==null){var J=Z.stateNode,G;switch(Z.tag...
function wD (line 115) | function wD(Z){var X=Z.alternate;if(X!==null)X.return=null;Z.return=null}
function YK (line 115) | function YK(Z){var X=Z.alternate;if(X!==null)Z.alternate=null,YK(X);{if(...
function TD (line 115) | function TD(Z){var X=Z.return;while(X!==null){if(QK(X))return X;X=X.retu...
function QK (line 115) | function QK(Z){return Z.tag===k||Z.tag===q||Z.tag===x}
function $K (line 115) | function $K(Z){var X=Z;Z:while(!0){while(X.sibling===null){if(X.return==...
function CD (line 115) | function CD(Z){var X=TD(Z);switch(X.tag){case k:{var J=X.stateNode;if(X....
function X$ (line 115) | function X$(Z,X,J){var G=Z.tag,Y=G===k||G===l;if(Y){var Q=Z.stateNode;if...
function J$ (line 115) | function J$(Z,X,J){var G=Z.tag,Y=G===k||G===l;if(Y){var Q=Z.stateNode;if...
function ID (line 115) | function ID(Z,X,J){{var G=X;Z:while(G!==null){switch(G.tag){case k:{$4=G...
function K9 (line 115) | function K9(Z,X,J){var G=J.child;while(G!==null)WK(Z,X,G),G=G.sibling}
function WK (line 115) | function WK(Z,X,J){switch(PO(J),J.tag){case k:if(!Q4)tX(J,X);case l:{{va...
function MD (line 115) | function MD(Z){var X=Z.memoizedState}
function PD (line 115) | function PD(Z,X){var J=X.memoizedState;if(J===null){var G=X.alternate;if...
function VK (line 115) | function VK(Z){var X=Z.updateQueue;if(X!==null){Z.updateQueue=null;var J...
function SD (line 115) | function SD(Z,X,J){aX=J,rX=Z,E0(X),UK(X,Z),E0(X),aX=null,rX=null}
function g7 (line 115) | function g7(Z,X,J){var G=X.deletions;if(G!==null)for(var Y=0;Y<G.length;...
function UK (line 115) | function UK(Z,X,J){var{alternate:G,flags:Y}=Z;switch(Z.tag){case A:case ...
function G8 (line 115) | function G8(Z){var X=Z.flags;if(X&M0){try{CD(Z)}catch(J){Y0(Z,Z.return,J...
function xD (line 115) | function xD(Z,X,J){aX=J,rX=X,ZZ=Z,BK(Z,X,J),aX=null,rX=null}
function BK (line 115) | function BK(Z,X,J){var G=(Z.mode&vZ)!==VZ;while(ZZ!==null){var Y=ZZ,Q=Y....
function G$ (line 115) | function G$(Z,X,J){while(ZZ!==null){var G=ZZ;if((G.flags&f1)!==KZ){var Y...
function kD (line 115) | function kD(Z){while(ZZ!==null){var X=ZZ,J=X.child;switch(X.tag){case A:...
function KK (line 115) | function KK(Z){while(ZZ!==null){var X=ZZ;if(X===Z){ZZ=null;return}var J=...
function vD (line 115) | function vD(Z){while(ZZ!==null){var X=ZZ,J=X.child;if(X.tag===_0){var G=...
function zK (line 115) | function zK(Z){while(ZZ!==null){var X=ZZ;E0(X);try{FD(X)}catch(G){Y0(X,X...
function bD (line 115) | function bD(Z,X,J,G){ZZ=X,RD(X,Z,J,G)}
function RD (line 115) | function RD(Z,X,J,G){while(ZZ!==null){var Y=ZZ,Q=Y.child;if((Y.subtreeFl...
function fD (line 115) | function fD(Z,X,J,G){while(ZZ!==null){var Y=ZZ;if((Y.flags&T7)!==KZ){E0(...
function gD (line 115) | function gD(Z,X,J,G){switch(X.tag){case A:case g:case HZ:{if(X.mode&rZ){...
function yD (line 115) | function yD(Z){ZZ=Z,hD()}
function hD (line 115) | function hD(){while(ZZ!==null){var Z=ZZ,X=Z.child;if((ZZ.flags&w9)!==KZ)...
function mD (line 115) | function mD(){while(ZZ!==null){var Z=ZZ;if((Z.flags&T7)!==KZ)E0(Z),dD(Z)...
function dD (line 115) | function dD(Z){switch(Z.tag){case A:case g:case HZ:{if(Z.mode&rZ)F2(),R7...
function pD (line 115) | function pD(Z,X){while(ZZ!==null){var J=ZZ;E0(J),lD(J,X),i0();var G=J.ch...
function uD (line 115) | function uD(Z){while(ZZ!==null){var X=ZZ,J=X.sibling,G=X.return;if(YK(X)...
function lD (line 115) | function lD(Z,X){switch(Z.tag){case A:case g:case HZ:{if(Z.mode&rZ)F2(),...
function cD (line 115) | function cD(Z){switch(Z.tag){case A:case g:case HZ:{try{B9(f0|R0,Z)}catc...
function iD (line 115) | function iD(Z){switch(Z.tag){case A:case g:case HZ:{try{B9(G4|R0,Z)}catc...
function nD (line 115) | function nD(Z){switch(Z.tag){case A:case g:case HZ:{try{R7(f0|R0,Z,Z.ret...
function sD (line 115) | function sD(Z){switch(Z.tag){case A:case g:case HZ:try{R7(G4|R0,Z,Z.retu...
function XA (line 115) | function XA(){ZA.forEach(function(Z){return Z()})}
function GA (line 115) | function GA(Z){{var X=typeof IS_REACT_ACT_ENVIRONMENT!=="undefined"?IS_R...
function _K (line 115) | function _K(){{var Z=typeof IS_REACT_ACT_ENVIRONMENT!=="undefined"?IS_RE...
function sJ (line 115) | function sJ(){EK=n0()+QA}
function NK (line 115) | function NK(){return EK}
function EY (line 115) | function EY(){return T4}
function C4 (line 115) | function C4(){if((bZ&(V4|z7))!==h0)return n0();if(rJ!==$0)return rJ;retu...
function _9 (line 115) | function _9(Z){var X=Z.mode;if((X&vZ)===VZ)return EZ;else if((bZ&V4)!==h...
function VA (line 115) | function VA(Z){var X=Z.mode;if((X&vZ)===VZ)return EZ;return $H()}
function p0 (line 115) | function p0(Z,X,J,G){if(kA(),LK)U("useInsertionEffect must not schedule ...
function UA (line 115) | function UA(Z,X,J){var G=Z.current;G.lanes=X,l1(Z,X,J),g4(Z,J)}
function BA (line 115) | function BA(Z){return(bZ&V4)!==h0}
function g4 (line 115) | function g4(Z,X){var J=Z.callbackNode;ZH(Z,X);var G=R6(Z,Z===T4?m0:y);if...
function DK (line 115) | function DK(Z,X){if(qL(),rJ=$0,jY=y,(bZ&(V4|z7))!==h0)throw new Error("S...
function H$ (line 115) | function H$(Z,X){var J=nJ;if(m6(Z)){var G=a9(Z,X);G.flags|=H8,IN(Z.conta...
function AK (line 115) | function AK(Z){if(f4===null)f4=Z;else f4.push.apply(f4,Z)}
function KA (line 115) | function KA(Z,X,J){switch(X){case P8:case uJ:throw new Error("Root did n...
function zA (line 115) | function zA(Z){var X=Z;while(!0){if(X.flags&P6){var J=X.updateQueue;if(J...
function O9 (line 115) | function O9(Z,X){X=g6(X,zY),X=g6(X,iJ),VH(Z,X)}
function FK (line 115) | function FK(Z){if(wL(),(bZ&(V4|z7))!==h0)throw new Error("Should not alr...
function _A (line 115) | function _A(Z,X){if(X!==y){if(R5(Z,TZ(X,EZ)),g4(Z,n0()),(bZ&(V4|z7))===h...
function j$ (line 115) | function j$(Z,X){var J=bZ;bZ|=OK;try{return Z(X)}finally{if(bZ=J,bZ===h0...
function OA (line 115) | function OA(Z,X,J,G,Y){var Q=I7(),$=W4.transition;try{return W4.transiti...
function x8 (line 115) | function x8(Z){if(z9!==null&&z9.tag===J9&&(bZ&(V4|z7))===h0)k8();var X=b...
function qK (line 115) | function qK(){return(bZ&(V4|z7))!==h0}
function NY (line 115) | function NY(Z,X){E4(W$,Y8,Z),Y8=TZ(Y8,X),V$=TZ(V$,X)}
function E$ (line 115) | function E$(Z){Y8=W$.current,j4(W$,Z)}
function a9 (line 115) | function a9(Z,X){Z.finishedWork=null,Z.finishedLanes=y;var J=Z.timeoutHa...
function wK (line 115) | function wK(Z,X){do{var J=N0;try{if(CG(),nU(),i0(),Q$.current=null,J===n...
function TK (line 115) | function TK(){var Z=Y$.current;if(Y$.current=tG,Z===null)return tG;else ...
function CK (line 115) | function CK(Z){Y$.current=Z}
function HA (line 115) | function HA(){U$=n0()}
function tJ (line 115) | function tJ(Z){KY=TZ(Z,KY)}
function jA (line 115) | function jA(){if(d0===P8)d0=BY}
function N$ (line 115) | function N$(){if(d0===P8||d0===BY||d0===s9)d0=lJ;if(T4!==null&&(k5(KY)||...
function EA (line 115) | function EA(Z){if(d0!==lJ)d0=s9;if(nJ===null)nJ=[Z];else nJ.push(Z)}
function NA (line 115) | function NA(){return d0===P8}
function LY (line 115) | function LY(Z,X){var J=bZ;bZ|=V4;var G=TK();if(T4!==Z||m0!==X){if(C7){va...
function LA (line 115) | function LA(){while(N0!==null)IK(N0)}
function DA (line 115) | function DA(Z,X){var J=bZ;bZ|=V4;var G=TK();if(T4!==Z||m0!==X){if(C7){va...
function AA (line 115) | function AA(){while(N0!==null&&!LO())IK(N0)}
function IK (line 115) | function IK(Z){var X=Z.alternate;E0(Z);var J;if((Z.mode&rZ)!==VZ)D2(Z),J...
function MK (line 115) | function MK(Z){var X=Z;do{var{alternate:J,return:G}=X;if((X.flags&R1)===...
function r9 (line 115) | function r9(Z,X,J){var G=I7(),Y=W4.transition;try{W4.transition=null,a0(...
function FA (line 115) | function FA(Z,X,J,G){do k8();while(z9!==null);if(vA(),(bZ&(V4|z7))!==h0)...
function k8 (line 115) | function k8(){if(z9!==null){var Z=YV(oJ),X=_H(A8,Z),J=W4.transition,G=I7...
function qA (line 115) | function qA(Z){if(K$.push(Z),!o9)o9=!0,F$(P9,function(){return k8(),null})}
function wA (line 115) | function wA(){if(z9===null)return!1;var Z=z$;z$=null;var X=z9,J=oJ;if(z9...
function PK (line 115) | function PK(Z){return eX!==null&&eX.has(Z)}
function TA (line 115) | function TA(Z){if(eX===null)eX=new Set([Z]);else eX.add(Z)}
function CA (line 115) | function CA(Z){if(!_Y)_Y=!0,B$=Z}
function SK (line 115) | function SK(Z,X,J){var G=i9(J,X),Y=CB(Z,G,EZ),Q=Q9(Z,Y,EZ),$=C4();if(Q!=...
function Y0 (line 115) | function Y0(Z,X,J){if(_D(J),Z6(!1),Z.tag===q){SK(Z,Z,J);return}var G=nul...
function MA (line 119) | function MA(Z,X,J){var G=Z.pingCache;if(G!==null)G.delete(X);var Y=C4();...
function xK (line 119) | function xK(Z,X){if(X===o0)X=VA(Z);var J=C4(),G=b4(Z,X);if(G!==null)l1(G...
function PA (line 119) | function PA(Z){var X=Z.memoizedState,J=o0;if(X!==null)J=X.retryLane;xK(Z...
function SA (line 119) | function SA(Z,X){var J=o0,G;switch(Z.tag){case i:G=Z.stateNode;var Y=Z.m...
function xA (line 119) | function xA(Z){return Z<120?120:Z<480?480:Z<1080?1080:Z<1920?1920:Z<3000...
function kA (line 119) | function kA(){if(aJ>$A)throw aJ=0,_$=null,new Error("Maximum update dept...
function vA (line 119) | function vA(){S7.flushLegacyContextWarning(),S7.flushPendingUnsafeLifecy...
function kK (line 119) | function kK(Z,X){{if(E0(Z),DY(Z,E8,nD),X)DY(Z,S6,sD);if(DY(Z,E8,cD),X)DY...
function DY (line 119) | function DY(Z,X,J){{var G=Z,Y=null;while(G!==null){var Q=G.subtreeFlags&...
function vK (line 119) | function vK(Z){{if((bZ&V4)!==h0)return;if(!(Z.mode&vZ))return;var X=Z.ta...
function RA (line 119) | function RA(Z){if(D9&&!LL())switch(Z.tag){case A:case g:case HZ:{var X=N...
function eJ (line 119) | function eJ(Z,X){if(C7){var J=Z.memoizedUpdaters;J.forEach(function(G){X...
function F$ (line 119) | function F$(Z,X){{var J=y7.current;if(J!==null)return J.push(X),A$;else ...
function RK (line 119) | function RK(Z){if(Z===A$)return;return NO(Z)}
function fK (line 119) | function fK(){return y7.current!==null}
function fA (line 119) | function fA(Z){{if(Z.mode&vZ){if(!_K())return}else{if(!GA())return;if(bZ...
function gA (line 128) | function gA(Z){if(Z.tag!==J9&&_K()&&y7.current===null)U(`A suspended res...
function Z6 (line 137) | function Z6(Z){LK=Z}
function J1 (line 137) | function J1(Z){{if(_7===null)return Z;var X=_7(Z);if(X===void 0)return Z...
function q$ (line 137) | function q$(Z){return J1(Z)}
function w$ (line 137) | function w$(Z){{if(_7===null)return Z;var X=_7(Z);if(X===void 0){if(Z!==...
function gK (line 137) | function gK(Z,X){{if(_7===null)return!1;var J=Z.elementType,G=X.type,Y=!...
function yK (line 137) | function yK(Z){{if(_7===null)return;if(typeof WeakSet!=="function")retur...
function T$ (line 137) | function T$(Z,X,J){{var{alternate:G,child:Y,sibling:Q,tag:$,type:W}=Z,V=...
function C$ (line 137) | function C$(Z,X,J){{var{child:G,sibling:Y,tag:Q,type:$}=Z,W=null;switch(...
function pA (line 137) | function pA(Z,X){{var J=uA(Z,X);if(J)return;var G=Z;while(!0){switch(G.t...
function uA (line 137) | function uA(Z,X){{var J=Z,G=!1;while(!0){if(J.tag===k)G=!0,X.add(J.state...
function lA (line 137) | function lA(Z,X,J,G){if(this.tag=Z,this.key=J,this.elementType=null,this...
function M$ (line 137) | function M$(Z){var X=Z.prototype;return!!(X&&X.isReactComponent)}
function cA (line 137) | function cA(Z){return typeof Z==="function"&&!M$(Z)&&Z.defaultProps===vo...
function iA (line 137) | function iA(Z){if(typeof Z==="function")return M$(Z)?C:A;else if(Z!==voi...
function t9 (line 137) | function t9(Z,X){var J=Z.alternate;if(J===null)J=a4(Z.tag,X,Z.key,Z.mode...
function nA (line 137) | function nA(Z,X){Z.flags&=N8|M0;var J=Z.alternate;if(J===null)Z.childLan...
function sA (line 137) | function sA(Z,X,J){var G;if(Z===jG){if(G=vZ,X===!0)G|=A0,G|=s7}else G=VZ...
function P$ (line 137) | function P$(Z,X,J,G,Y,Q){var $=S,W=Z;if(typeof Z==="function")if(M$(Z))$...
function S$ (line 139) | function S$(Z,X,J){var G=null;G=Z._owner;var{type:Y,key:Q,props:$}=Z,W=P...
function H9 (line 139) | function H9(Z,X,J,G){var Y=a4(GZ,Z,G,X);return Y.lanes=J,Y}
function oA (line 139) | function oA(Z,X,J,G){if(typeof Z.id!=="string")U('Profiler must specify ...
function aA (line 139) | function aA(Z,X,J,G){var Y=a4(i,Z,G,X);return Y.elementType=mZ,Y.lanes=J,Y}
function rA (line 139) | function rA(Z,X,J,G){var Y=a4(IZ,Z,G,X);return Y.elementType=CZ,Y.lanes=...
function mK (line 139) | function mK(Z,X,J,G){var Y=a4(_0,Z,G,X);Y.elementType=e0,Y.lanes=J;var Q...
function x$ (line 139) | function x$(Z,X,J){var G=a4(l,Z,null,X);return G.lanes=J,G}
function tA (line 139) | function tA(){var Z=a4(k,null,null,VZ);return Z.elementType="DELETED",Z}
function eA (line 139) | function eA(Z){var X=a4(r0,null,null,VZ);return X.stateNode=Z,X}
function k$ (line 139) | function k$(Z,X,J){var G=Z.children!==null?Z.children:[],Y=a4(x,G,Z.key,...
function dK (line 139) | function dK(Z,X){if(Z===null)Z=a4(S,null,null,VZ);return Z.tag=X.tag,Z.k...
function ZF (line 139) | function ZF(Z,X,J,G,Y){this.tag=X,this.containerInfo=Z,this.pendingChild...
function pK (line 139) | function pK(Z,X,J,G,Y,Q,$,W,V,z){var _=new ZF(Z,X,J,W,V),D=sA(X,Q);_.cur...
function XF (line 139) | function XF(Z,X,J){var G=arguments.length>3&&arguments[3]!==void 0?argum...
function uK (line 139) | function uK(Z){if(!Z)return o4;var X=EX(Z),J=vN(X);if(X.tag===C){var G=X...
function JF (line 139) | function JF(Z,X){{var J=EX(Z);if(J===void 0)if(typeof Z.render==="functi...
function lK (line 139) | function lK(Z,X,J,G,Y,Q,$,W){var V=!1,z=null;return pK(Z,X,V,z,J,G,Y,Q,$)}
function cK (line 139) | function cK(Z,X,J,G,Y,Q,$,W,V,z){var _=!0,D=pK(J,G,_,Z,Y,Q,$,W,V);D.cont...
function X6 (line 139) | function X6(Z,X,J,G){CO(X,Z);var Y=X.current,Q=C4(),$=_9(Y);iO($);var W=...
function FY (line 141) | function FY(Z){var X=Z.current;if(!X.child)return null;switch(X.child.ta...
function GF (line 141) | function GF(Z){switch(Z.tag){case q:{var X=Z.stateNode;if(m6(X)){var J=X...
function iK (line 141) | function iK(Z,X){var J=Z.memoizedState;if(J!==null&&J.dehydrated!==null)...
function f$ (line 141) | function f$(Z,X){iK(Z,X);var J=Z.alternate;if(J)iK(J,X)}
function YF (line 141) | function YF(Z){if(Z.tag!==i)return;var X=m1,J=b4(Z,X);if(J!==null){var G...
function QF (line 141) | function QF(Z){if(Z.tag!==i)return;var X=_9(Z),J=b4(Z,X);if(J!==null){va...
function nK (line 141) | function nK(Z){var X=EO(Z);if(X===null)return null;return X.stateNode}
function $F (line 141) | function $F(Z){return sK(Z)}
function WF (line 141) | function WF(Z){return oK(Z)}
function VF (line 141) | function VF(Z){var X=yW(Z);if(X===null)return null;return X.stateNode}
function UF (line 141) | function UF(Z){return null}
function BF (line 141) | function BF(){return H4}
function KF (line 141) | function KF(Z){var X=Z.findFiberByHostInstance,J=B.ReactCurrentDispatche...
function y$ (line 141) | function y$(Z){this._internalRoot=Z}
function zF (line 141) | function zF(Z,X){if(!wY(Z))throw new Error("createRoot(...): Target cont...
function qY (line 144) | function qY(Z){this._internalRoot=Z}
function _F (line 144) | function _F(Z){if(Z)wH(Z)}
function OF (line 144) | function OF(Z,X,J){if(!wY(Z))throw new Error("hydrateRoot(...): Target c...
function wY (line 144) | function wY(Z){return!!(Z&&(Z.nodeType===k4||Z.nodeType===O8||Z.nodeType...
function J6 (line 144) | function J6(Z){return!!(Z&&(Z.nodeType===k4||Z.nodeType===O8||Z.nodeType...
function zz (line 144) | function zz(Z){{if(Z.nodeType===k4&&Z.tagName&&Z.tagName.toUpperCase()==...
function h$ (line 144) | function h$(Z){if(!Z)return null;if(Z.nodeType===O8)return Z.documentEle...
function Oz (line 144) | function Oz(){}
function jF (line 144) | function jF(Z,X,J,G,Y){if(Y){if(typeof G==="function"){var Q=G;G=functio...
function EF (line 144) | function EF(Z,X){if(Z!==null&&typeof Z!=="function")U("%s(...): Expected...
function TY (line 144) | function TY(Z,X,J,G,Y){_z(J),EF(Y===void 0?null:Y,"render");var Q=J._rea...
function NF (line 144) | function NF(Z){{if(!Hz)Hz=!0,U("findDOMNode is deprecated and will be re...
function LF (line 144) | function LF(Z,X,J){if(U("ReactDOM.hydrate is no longer supported in Reac...
function DF (line 144) | function DF(Z,X,J){if(U("ReactDOM.render is no longer supported in React...
function AF (line 144) | function AF(Z,X,J,G){if(U("ReactDOM.unstable_renderSubtreeIntoContainer(...
function FF (line 144) | function FF(Z){if(!jz)jz=!0,U("unmountComponentAtNode is deprecated and ...
function qF (line 144) | function qF(Z,X){var J=arguments.length>2&&arguments[2]!==void 0?argumen...
function wF (line 144) | function wF(Z,X,J,G){return AF(Z,X,J,G)}
function TF (line 144) | function TF(Z,X){if(!m$.usingClientEntryPoint)U('You are importing creat...
function CF (line 144) | function CF(Z,X,J){if(!m$.usingClientEntryPoint)U('You are importing hyd...
function IF (line 144) | function IF(Z){if(qK())U("flushSync was called from inside a lifecycle m...
function DZ (line 145) | function DZ(N){if(N===null||typeof N!=="object")return null;var h=GZ&&N[...
function g (line 145) | function g(N){{for(var h=arguments.length,s=new Array(h>1?h-1:0),BZ=1;BZ...
function p (line 145) | function p(N,h,s){{var BZ=d.ReactDebugCurrentFrame,WZ=BZ.getStackAddendu...
function IZ (line 145) | function IZ(N){if(typeof N==="string"||typeof N==="function")return!0;if...
function Q0 (line 145) | function Q0(N,h,s){var BZ=N.displayName;if(BZ)return BZ;var WZ=h.display...
function _0 (line 145) | function _0(N){return N.displayName||"Context"}
function gZ (line 145) | function gZ(N){if(N==null)return null;if(typeof N.tag==="number")g("Rece...
function K4 (line 145) | function K4(){}
function Z7 (line 145) | function Z7(){{if(q0===0){FZ=console.log,C0=console.info,H7=console.warn...
function m7 (line 145) | function m7(){{if(q0--,q0===0){var N={configurable:!0,enumerable:!0,writ...
function u0 (line 145) | function u0(N,h,s){{if(N7===void 0)try{throw Error()}catch(WZ){var BZ=WZ...
function w0 (line 146) | function w0(N,h){if(!N||y4)return"";{var s=z4.get(N);if(s!==void 0)retur...
function h4 (line 149) | function h4(N,h,s){return w0(N,!1)}
function J7 (line 149) | function J7(N){var h=N.prototype;return!!(h&&h.isReactComponent)}
function k0 (line 149) | function k0(N,h,s){if(N==null)return"";if(typeof N==="function")return w...
function l0 (line 149) | function l0(N){if(N){var h=N._owner,s=k0(N.type,N._source,h?h.type:null)...
function G7 (line 149) | function G7(N,h,s,BZ,WZ){{var mZ=Function.call.bind(j0);for(var CZ in N)...
function m4 (line 149) | function m4(N){return R8(N)}
function d4 (line 149) | function d4(N){{var h=typeof Symbol==="function"&&Symbol.toStringTag,s=h...
function v0 (line 149) | function v0(N){try{return Y7(N),!1}catch(h){return!0}}
function Y7 (line 149) | function Y7(N){return""+N}
function _4 (line 149) | function _4(N){if(v0(N))return g("The provided key is an unsupported typ...
function a (line 149) | function a(N){if(j0.call(N,"ref")){var h=Object.getOwnPropertyDescriptor...
function QZ (line 149) | function QZ(N){if(j0.call(N,"key")){var h=Object.getOwnPropertyDescripto...
function AZ (line 149) | function AZ(N,h){if(typeof N.ref==="string"&&t0.current&&h&&t0.current.s...
function nZ (line 149) | function nZ(N,h){{var s=function(){if(!p7)p7=!0,g("%s: `key` is not a pr...
function X0 (line 149) | function X0(N,h){{var s=function(){if(!D7)D7=!0,g("%s: `ref` is not a pr...
function V0 (line 149) | function V0(N,h,s,BZ,WZ){{var mZ,CZ={},qZ=null,lZ=null;if(s!==void 0)_4(...
function uZ (line 149) | function uZ(N){if(N){var h=N._owner,s=k0(N.type,N._source,h?h.type:null)...
function Q7 (line 149) | function Q7(N){return typeof N==="object"&&N!==null&&N.$$typeof===B}
function f8 (line 149) | function f8(){{if(O4.current){var N=gZ(O4.current.type);if(N)return`
function z1 (line 151) | function z1(N){{if(N!==void 0){var h=N.fileName.replace(/^.*[\\\/]/,""),...
function QX (line 153) | function QX(N){{var h=f8();if(!h){var s=typeof N==="string"?N:N.displayN...
function E9 (line 155) | function E9(N,h){{if(!N._store||N._store.validated||N.key!=null)return;N...
function u7 (line 155) | function u7(N,h){{if(typeof N!=="object")return;if(m4(N))for(var s=0;s<N...
function g8 (line 155) | function g8(N){{var h=N.type;if(h===null||h===void 0||typeof h==="string...
function y8 (line 155) | function y8(N){{var h=Object.keys(N.props);for(var s=0;s<h.length;s++){v...
function F7 (line 155) | function F7(N,h,s,BZ,WZ,mZ){{var CZ=IZ(N);if(!CZ){var qZ="";if(N===void ...
function mF (line 160) | function mF(B={}){function O(U,F){let{pathname:A="/",search:C="",hash:S=...
function z0 (line 160) | function z0(B,O){if(B===!1||B===null||typeof B==="undefined")throw new E...
function t4 (line 160) | function t4(B,O){if(!B){if(typeof console!=="undefined")console.warn(O);...
function dF (line 160) | function dF(){return Math.random().toString(36).substring(2,10)}
function Tz (line 160) | function Tz(B,O){return{usr:B.state,key:B.key,idx:O}}
function u$ (line 160) | function u$(B,O,H=null,j){return{pathname:typeof B==="string"?B:B.pathna...
function W1 (line 160) | function W1({pathname:B="/",search:O="",hash:H=""}){if(O&&O!=="?")B+=O.c...
function j9 (line 160) | function j9(B){let O={};if(B){let H=B.indexOf("#");if(H>=0)O.hash=B.subs...
function pF (line 160) | function pF(B,O,H,j={}){let{window:U=document.defaultView,v5Compat:F=!1}...
function uF (line 160) | function uF(B,O=!1){let H="http://localhost";if(typeof window!=="undefin...
function n$ (line 160) | function n$(B,O,H="/"){return cF(B,O,H,!1)}
function cF (line 160) | function cF(B,O,H,j){let U=typeof O==="string"?j9(O):O,F=$8(U.pathname||...
function iF (line 160) | function iF(B,O){let{route:H,pathname:j,params:U}=B;return{id:H.id,pathn...
function Pz (line 160) | function Pz(B,O=[],H=[],j="",U=!1){let F=(A,C,S=U,q)=>{let x={relativePa...
function Sz (line 160) | function Sz(B){let O=B.split("/");if(O.length===0)return[];let[H,...j]=O...
function nF (line 160) | function nF(B){B.sort((O,H)=>O.score!==H.score?H.score-O.score:Xq(O.rout...
function Zq (line 160) | function Zq(B,O){let H=B.split("/"),j=H.length;if(H.some(Cz))j+=eF;if(O)...
function Xq (line 160) | function Xq(B,O){return B.length===O.length&&B.slice(0,-1).every((j,U)=>...
function Jq (line 160) | function Jq(B,O,H=!1){let{routesMeta:j}=B,U={},F="/",A=[];for(let C=0;C<...
function $6 (line 160) | function $6(B,O){if(typeof B==="string")B={path:B,caseSensitive:!1,end:!...
function Gq (line 160) | function Gq(B,O=!1,H=!0){t4(B==="*"||!B.endsWith("*")||B.endsWith("/*"),...
function Yq (line 160) | function Yq(B){try{return B.split("/").map((O)=>decodeURIComponent(O).re...
function $8 (line 160) | function $8(B,O){if(O==="/")return B;if(!B.toLowerCase().startsWith(O.to...
function xz (line 160) | function xz(B,O="/"){let{pathname:H,search:j="",hash:U=""}=typeof B==="s...
function Qq (line 160) | function Qq(B,O){let H=O.replace(/\/+$/,"").split("/");return B.split("/...
function d$ (line 160) | function d$(B,O,H,j){return`Cannot include a '${B}' character in a manua...
function $q (line 160) | function $q(B){return B.filter((O,H)=>H===0||O.route.path&&O.route.path....
function kz (line 160) | function kz(B){let O=$q(B);return O.map((H,j)=>j===O.length-1?H.pathname...
function vz (line 160) | function vz(B,O,H,j=!1){let U;if(typeof B==="string")U=j9(B);else U={......
function bz (line 160) | function bz(B){return B!=null&&typeof B.status==="number"&&typeof B.stat...
function gz (line 160) | function gz(B,{relative:O}={}){z0(B1(),"useHref() may be used only in th...
function B1 (line 160) | function B1(){return $Z.useContext(U1)!=null}
function V8 (line 160) | function V8(){return z0(B1(),"useLocation() may be used only in the cont...
function hz (line 160) | function hz(B){if(!$Z.useContext(e4).static)$Z.useLayoutEffect(B)}
function mz (line 160) | function mz(){let{isDataRoute:B}=$Z.useContext(W8);return B?Aq():zq()}
function zq (line 160) | function zq(){z0(B1(),"useNavigate() may be used only in the context of ...
function K1 (line 160) | function K1(B,{relative:O}={}){let{matches:H}=$Z.useContext(W8),{pathnam...
function dz (line 160) | function dz(B,O){return pz(B,O)}
function pz (line 160) | function pz(B,O,H,j,U){z0(B1(),"useRoutes() may be used only in the cont...
function _q (line 162) | function _q(){let B=lz(),O=bz(B)?`${B.status} ${B.statusText}`:B instanc...
method constructor (line 162) | constructor(B){super(B);this.state={location:B.location,revalidation:B.r...
method getDerivedStateFromError (line 162) | static getDerivedStateFromError(B){return{error:B}}
method getDerivedStateFromProps (line 162) | static getDerivedStateFromProps(B,O){if(O.location!==B.location||O.reval...
method componentDidCatch (line 162) | componentDidCatch(B,O){if(this.props.unstable_onError)this.props.unstabl...
method render (line 162) | render(){return this.state.error!==void 0?$Z.createElement(W8.Provider,{...
function jq (line 162) | function jq({routeContext:B,match:O,children:H}){let j=$Z.useContext(ZX)...
function Eq (line 162) | function Eq(B,O=[],H=null,j=null,U=null){if(B==null){if(!H)return null;i...
function a$ (line 162) | function a$(B){return`${B} must be used within a data router. See https...
function Nq (line 162) | function Nq(B){let O=$Z.useContext(ZX);return z0(O,a$(B)),O}
function r$ (line 162) | function r$(B){let O=$Z.useContext(V1);return z0(O,a$(B)),O}
function Lq (line 162) | function Lq(B){let O=$Z.useContext(W8);return z0(O,a$(B)),O}
function t$ (line 162) | function t$(B){let O=Lq(B),H=O.matches[O.matches.length-1];return z0(H.r...
function Dq (line 162) | function Dq(){return t$("useRouteId")}
function uz (line 162) | function uz(){return r$("useNavigation").navigation}
function e$ (line 162) | function e$(){let{matches:B,loaderData:O}=r$("useMatches");return $Z.use...
function lz (line 162) | function lz(){let B=$Z.useContext(o$),O=r$("useRouteError"),H=t$("useRou...
function Aq (line 162) | function Aq(){let{router:B}=Nq("useNavigate"),O=t$("useNavigate"),H=$Z.u...
function cz (line 162) | function cz(B,O,H){if(!O&&!Iz[B])Iz[B]=!0,t4(!1,H)}
function Fq (line 162) | function Fq({routes:B,future:O,state:H,unstable_onError:j}){return pz(B,...
function kY (line 162) | function kY(B){z0(!1,"A <Route> is only ever to be used as the child of ...
function ZW (line 162) | function ZW({basename:B="/",children:O=null,location:H,navigationType:j=...
function XW (line 162) | function XW({children:B,location:O}){return dz(xY(B),O)}
function xY (line 162) | function xY(B,O=[]){let H=[];return L0.Children.forEach(B,(j,U)=>{if(!L0...
function vY (line 162) | function vY(B){return B!=null&&typeof B.tagName==="string"}
function qq (line 162) | function qq(B){return vY(B)&&B.tagName.toLowerCase()==="button"}
function wq (line 162) | function wq(B){return vY(B)&&B.tagName.toLowerCase()==="form"}
function Tq (line 162) | function Tq(B){return vY(B)&&B.tagName.toLowerCase()==="input"}
function Cq (line 162) | function Cq(B){return!!(B.metaKey||B.altKey||B.ctrlKey||B.shiftKey)}
function Iq (line 162) | function Iq(B,O){return B.button===0&&(!O||O==="_self")&&!Cq(B)}
function Mq (line 162) | function Mq(){if(IY===null)try{new FormData(document.createElement("form...
function p$ (line 162) | function p$(B){if(B!=null&&!Pq.has(B))return t4(!1,`"${B}" is not a vali...
function Sq (line 162) | function Sq(B,O){let H,j,U,F,A;if(wq(B)){let C=B.getAttribute("action");...
function GW (line 162) | function GW(B,O){if(B===!1||B===null||typeof B==="undefined")throw new E...
function kq (line 162) | function kq(B,O,H){let j=typeof B==="string"?new URL(B,typeof window==="...
function vq (line 162) | async function vq(B,O){if(B.id in O)return O[B.id];try{let H=await impor...
function bq (line 162) | function bq(B){return B!=null&&typeof B.page==="string"}
function Rq (line 162) | function Rq(B){if(B==null)return!1;if(B.href==null)return B.rel==="prelo...
function fq (line 162) | async function fq(B,O,H){let j=await Promise.all(B.map(async(U)=>{let F=...
function Mz (line 162) | function Mz(B,O,H,j,U,F){let A=(S,q)=>{if(!H[q])return!0;return S.route....
function gq (line 162) | function gq(B,O,{includeHydrateFallback:H}={}){return yq(B.map((j)=>{let...
function yq (line 162) | function yq(B){return[...new Set(B)]}
function hq (line 162) | function hq(B){let O={},H=Object.keys(B).sort();for(let j of H)O[j]=B[j]...
function mq (line 162) | function mq(B,O){let H=new Set,j=new Set(O);return B.reduce((U,F)=>{if(O...
function nz (line 162) | function nz(){let B=oZ.useContext(ZX);return GW(B,"You must render this ...
function lq (line 162) | function lq(){let B=oZ.useContext(V1);return GW(B,"You must render this ...
function sz (line 162) | function sz(){let B=oZ.useContext(W6);return GW(B,"You must render this ...
function cq (line 162) | function cq(B,O){let H=oZ.useContext(W6),[j,U]=oZ.useState(!1),[F,A]=oZ....
function Q6 (line 162) | function Q6(B,O){return(H)=>{if(B&&B(H),!H.defaultPrevented)O(H)}}
function oz (line 162) | function oz({page:B,...O}){let{router:H}=nz(),j=oZ.useMemo(()=>n$(H.rout...
function iq (line 162) | function iq(B){let{manifest:O,routeModules:H}=sz(),[j,U]=oZ.useState([])...
function nq (line 162) | function nq({page:B,matches:O,...H}){let j=V8(),{manifest:U,routeModules...
function sq (line 162) | function sq(...B){return(O)=>{B.forEach((H)=>{if(typeof H==="function")H...
function YW (line 162) | function YW({basename:B,children:O,window:H}){let j=UZ.useRef();if(j.cur...
function rz (line 162) | function rz({basename:B,children:O,history:H}){let[j,U]=UZ.useState({act...
function x0 (line 162) | function x0(IZ){if(O)O(IZ);if(!IZ.defaultPrevented)S0(IZ)}
function X_ (line 162) | function X_({getKey:B,storageKey:O,...H}){let j=UZ.useContext(W6),{basen...
function J_ (line 162) | function J_(B){return`${B} must be used within a data router. See https...
function QW (line 162) | function QW(B){let O=UZ.useContext(ZX);return z0(O,J_(B)),O}
function oq (line 162) | function oq(B){let O=UZ.useContext(V1);return z0(O,J_(B)),O}
function G_ (line 162) | function G_(B,{target:O,replace:H,state:j,preventScrollReset:U,relative:...
function Y_ (line 162) | function Y_(){let{router:B}=QW("useSubmit"),{basename:O}=UZ.useContext(e...
function Q_ (line 162) | function Q_(B,{relative:O}={}){let{basename:H}=UZ.useContext(e4),j=UZ.us...
function i$ (line 162) | function i$(B,O,H,j){let U=null;if(j)if(H!=="/")U=j({...B,pathname:$8(B....
function $_ (line 162) | function $_({getKey:B,storageKey:O}={}){let{router:H}=QW("useScrollResto...
function tq (line 162) | function tq(B,O){let{capture:H}=O||{};UZ.useEffect(()=>{let j=H!=null?{c...
function W_ (line 162) | function W_(B,{relative:O}={}){let H=UZ.useContext(s$);z0(H!=null,"`useV...
function Zw (line 162) | function Zw(B){if(!B)return"";return B=B.replace(/&/g,"&").replace(/...
function WW (line 162) | function WW(){let[B,O]=JX.useState([]),[H,j]=JX.useState(""),[U,F]=JX.us...
function VW (line 179) | function VW(){return GX.jsxDEV(YW,{children:[GX.jsxDEV("div",{className:...
FILE: xiaomusic/static/default/setting.js
constant DEFAULT_QRCODE_EXPIRE_SECONDS (line 3) | const DEFAULT_QRCODE_EXPIRE_SECONDS = 120;
function stopQRCodeCountdown (line 5) | function stopQRCodeCountdown() {
function startQRCodeCountdown (line 12) | function startQRCodeCountdown($qrcodeStatus, $qrcodeImage, expireSeconds) {
function fetchQRCode (line 38) | function fetchQRCode() {
function updateCheckbox (line 115) | function updateCheckbox(selector, mi_did, device_list, accountPassValid) {
function getSelectedDids (line 156) | function getSelectedDids(containerSelector) {
function fetchDeviceList (line 169) | function fetchDeviceList(callback) {
function switchTab (line 511) | function switchTab(index) {
function updateAdvancedConfigAria (line 566) | function updateAdvancedConfigAria() {
function updateToolsAria (line 601) | function updateToolsAria() {
FILE: xiaomusic/static/pure/assets/DownloadTool-bty5M9I6.js
method setup (line 1) | setup(g){const s=g,o=T("divider"),t=k(()=>o.cssVar({"border-style":s.bor...
method setup (line 1) | setup(g){const s=f({playlistUrl:"https://m.bilibili.com/video/BV1WUsDezE...
FILE: xiaomusic/static/pure/assets/M3u2Json-DeAtFyPF.js
method setup (line 1) | setup(B){const d=i(""),n=i(""),c=i(!1),v=i(null),b=s=>{const e=new FileR...
FILE: xiaomusic/static/pure/assets/index-BAPaOAUA.js
function n (line 2) | function n(r){const s={};return r.integrity&&(s.integrity=r.integrity),r...
function o (line 2) | function o(r){if(r.ep)return;r.ep=!0;const s=n(r);fetch(r.href,s)}
function eu (line 6) | function eu(e,t){const n=new Set(e.split(","));return o=>n.has(o)}
function ze (line 6) | function ze(e){if(ye(e)){const t={};for(let n=0;n<e.length;n++){const o=...
function tg (line 6) | function tg(e){const t={};return e.replace(eg,"").split(Zh).forEach(n=>{...
function z (line 6) | function z(e){let t="";if(ke(e))t=e;else if(ye(e))for(let n=0;n<e.length...
function Tp (line 6) | function Tp(e){return!!e||e===""}
function rg (line 6) | function rg(e,t){if(e.length!==t.length)return!1;let n=!0;for(let o=0;n&...
function Or (line 6) | function Or(e,t){if(e===t)return!0;let n=yc(e),o=yc(t);if(n||o)return n&...
function $p (line 6) | function $p(e,t){return e.findIndex(n=>Or(n,t))}
class sg (line 10) | class sg{constructor(t=!1){this.detached=t,this._active=!0,this.effects=...
method constructor (line 10) | constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this...
method active (line 10) | get active(){return this._active}
method pause (line 10) | pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(...
method resume (line 10) | resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,n;if...
method run (line 10) | run(t){if(this._active){const n=Ht;try{return Ht=this,t()}finally{Ht=n}}}
method on (line 10) | on(){Ht=this}
method off (line 10) | off(){Ht=this.parent}
method stop (line 10) | stop(t){if(this._active){let n,o;for(n=0,o=this.effects.length;n<o;n++...
function ru (line 10) | function ru(){return Ht}
function su (line 10) | function su(e,t=!1){Ht&&Ht.cleanups.push(e)}
class Pp (line 10) | class Pp{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,...
method constructor (line 10) | constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.fl...
method pause (line 10) | pause(){this.flags|=64}
method resume (line 10) | resume(){this.flags&64&&(this.flags&=-65,Bl.has(this)&&(Bl.delete(this...
method notify (line 10) | notify(){this.flags&2&&!(this.flags&32)||this.flags&8||(this.flags|=8,...
method run (line 10) | run(){if(!(this.flags&1))return this.fn();this.flags|=2,wc(this),Mp(th...
method stop (line 10) | stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)iu(t);this.d...
method trigger (line 10) | trigger(){this.flags&64?Bl.add(this):this.scheduler?this.scheduler():t...
method runIfDirty (line 10) | runIfDirty(){gi(this)&&this.run()}
method dirty (line 10) | get dirty(){return gi(this)}
function au (line 10) | function au(){kp++}
function lu (line 10) | function lu(){if(--kp>0)return;let e;for(;fs;){let t=fs;for(fs=void 0;t;...
function Mp (line 10) | function Mp(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveL...
function Ip (line 10) | function Ip(e){let t,n=e.depsTail;for(let o=n;o;o=o.prevDep)o.version===...
function gi (line 10) | function gi(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.versi...
function Ap (line 10) | function Ap(e){if(e.flags&2)return!1;if(e.flags&4&&!(e.flags&16)||(e.fla...
function iu (line 10) | function iu(e){const{dep:t,prevSub:n,nextSub:o}=e;if(n&&(n.nextSub=o,e.p...
function ag (line 10) | function ag(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=vo...
function Bo (line 10) | function Bo(){Vp.push(On),On=!1}
function Fo (line 10) | function Fo(){const e=Vp.pop();On=e===void 0?!0:e}
function wc (line 10) | function wc(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=at;at=v...
class dl (line 10) | class dl{constructor(t){this.computed=t,this.version=0,this.activeLink=v...
method constructor (line 10) | constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,t...
method track (line 10) | track(t){if(!at||!On||at===this.computed)return;let n=this.activeLink;...
method trigger (line 10) | trigger(t){this.version++,Es++,this.notify(t)}
method notify (line 10) | notify(t){au();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()}fi...
function Lp (line 10) | function Lp(e){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for...
function Ft (line 10) | function Ft(e,t,n){if(On&&at){let o=Da.get(e);o||Da.set(e,o=new Map);let...
function so (line 10) | function so(e,t,n,o,r,s){const a=Da.get(e);if(!a){Es++;return}let l=[];i...
function lg (line 10) | function lg(e,t){var n;return(n=Da.get(e))==null?void 0:n.get(t)}
function fr (line 10) | function fr(e){const t=je(e);return t===e?t:(Ft(t,"iterate",Ts),Pn(e)?t:...
function fl (line 10) | function fl(e){return Ft(e=je(e),"iterate",Ts),e}
method [Symbol.iterator] (line 10) | [Symbol.iterator](){return Fl(this,Symbol.iterator,Vt)}
method concat (line 10) | concat(...e){return fr(this).concat(...e.map(t=>ye(t)?fr(t):t))}
method entries (line 10) | entries(){return Fl(this,"entries",e=>(e[1]=Vt(e[1]),e))}
method every (line 10) | every(e,t){return Xn(this,"every",e,t,void 0,arguments)}
method filter (line 10) | filter(e,t){return Xn(this,"filter",e,t,n=>n.map(Vt),arguments)}
method find (line 10) | find(e,t){return Xn(this,"find",e,t,Vt,arguments)}
method findIndex (line 10) | findIndex(e,t){return Xn(this,"findIndex",e,t,void 0,arguments)}
method findLast (line 10) | findLast(e,t){return Xn(this,"findLast",e,t,Vt,arguments)}
method findLastIndex (line 10) | findLastIndex(e,t){return Xn(this,"findLastIndex",e,t,void 0,arguments)}
method forEach (line 10) | forEach(e,t){return Xn(this,"forEach",e,t,void 0,arguments)}
method includes (line 10) | includes(...e){return zl(this,"includes",e)}
method indexOf (line 10) | indexOf(...e){return zl(this,"indexOf",e)}
method join (line 10) | join(e){return fr(this).join(e)}
method lastIndexOf (line 10) | lastIndexOf(...e){return zl(this,"lastIndexOf",e)}
method map (line 10) | map(e,t){return Xn(this,"map",e,t,void 0,arguments)}
method pop (line 10) | pop(){return es(this,"pop")}
method push (line 10) | push(...e){return es(this,"push",e)}
method reduce (line 10) | reduce(e,...t){return Sc(this,"reduce",e,t)}
method reduceRight (line 10) | reduceRight(e,...t){return Sc(this,"reduceRight",e,t)}
method shift (line 10) | shift(){return es(this,"shift")}
method some (line 10) | some(e,t){return Xn(this,"some",e,t,void 0,arguments)}
method splice (line 10) | splice(...e){return es(this,"splice",e)}
method toReversed (line 10) | toReversed(){return fr(this).toReversed()}
method toSorted (line 10) | toSorted(e){return fr(this).toSorted(e)}
method toSpliced (line 10) | toSpliced(...e){return fr(this).toSpliced(...e)}
method unshift (line 10) | unshift(...e){return es(this,"unshift",e)}
method values (line 10) | values(){return Fl(this,"values",Vt)}
function Fl (line 10) | function Fl(e,t,n){const o=fl(e),r=o[t]();return o!==e&&!Pn(e)&&(r._next...
function Xn (line 10) | function Xn(e,t,n,o,r,s){const a=fl(e),l=a!==e&&!Pn(e),i=a[t];if(i!==ug[...
function Sc (line 10) | function Sc(e,t,n,o){const r=fl(e);let s=n;return r!==e&&(Pn(e)?n.length...
function zl (line 10) | function zl(e,t,n){const o=je(e);Ft(o,"iterate",Ts);const r=o[t](...n);r...
function es (line 10) | function es(e,t,n=[]){Bo(),au();const o=je(e)[t].apply(e,n);return lu(),...
function dg (line 10) | function dg(e){Gn(e)||(e=String(e));const t=je(this);return Ft(t,"has",e...
class Np (line 10) | class Np{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get...
method constructor (line 10) | constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}
method get (line 10) | get(t,n,o){const r=this._isReadonly,s=this._isShallow;if(n==="__v_isRe...
class Bp (line 10) | class Bp extends Np{constructor(t=!1){super(!1,t)}set(t,n,o,r){let s=t[n...
method constructor (line 10) | constructor(t=!1){super(!1,t)}
method set (line 10) | set(t,n,o,r){let s=t[n];if(!this._isShallow){const i=nr(s);if(!Pn(o)&&...
method deleteProperty (line 10) | deleteProperty(t,n){const o=Qe(t,n);t[n];const r=Reflect.deletePropert...
method has (line 10) | has(t,n){const o=Reflect.has(t,n);return(!Gn(n)||!Rp.has(n))&&Ft(t,"ha...
method ownKeys (line 10) | ownKeys(t){return Ft(t,"iterate",ye(t)?"length":er),Reflect.ownKeys(t)}
class fg (line 10) | class fg extends Np{constructor(t=!1){super(!0,t)}set(t,n){return!0}dele...
method constructor (line 10) | constructor(t=!1){super(!0,t)}
method set (line 10) | set(t,n){return!0}
method deleteProperty (line 10) | deleteProperty(t,n){return!0}
function aa (line 10) | function aa(e,t,n=!1,o=!1){e=e.__v_raw;const r=je(e),s=je(t);n||(Lo(t,s)...
function la (line 10) | function la(e,t=!1){const n=this.__v_raw,o=je(n),r=je(e);return t||(Lo(e...
function ia (line 10) | function ia(e,t=!1){return e=e.__v_raw,!t&&Ft(je(e),"iterate",er),Reflec...
function Cc (line 10) | function Cc(e,t=!1){!t&&!Pn(e)&&!nr(e)&&(e=je(e));const n=je(this);retur...
function Ec (line 10) | function Ec(e,t,n=!1){!n&&!Pn(t)&&!nr(t)&&(t=je(t));const o=je(this),{ha...
function Tc (line 10) | function Tc(e){const t=je(this),{has:n,get:o}=pl(t);let r=n.call(t,e);r|...
function $c (line 10) | function $c(){const e=je(this),t=e.size!==0,n=e.clear();return t&&so(e,"...
function ua (line 10) | function ua(e,t){return function(o,r){const s=this,a=s.__v_raw,l=je(a),i...
function ca (line 10) | function ca(e,t,n){return function(...o){const r=this.__v_raw,s=je(r),a=...
function yo (line 10) | function yo(e){return function(...t){return e==="delete"?!1:e==="clear"?...
function hg (line 10) | function hg(){const e={get(s){return aa(this,s)},get size(){return ia(th...
function cu (line 10) | function cu(e,t){const n=t?e?_g:yg:e?bg:gg;return(o,r,s)=>r==="__v_isRea...
function Tg (line 10) | function Tg(e){switch(e){case"Object":case"Array":return 1;case"Map":cas...
function $g (line 10) | function $g(e){return e.__v_skip||!Object.isExtensible(e)?0:Tg(Oa(e))}
function ht (line 10) | function ht(e){return nr(e)?e:fu(e,!1,pg,wg,Fp)}
function du (line 10) | function du(e){return fu(e,!1,mg,Sg,zp)}
function ar (line 10) | function ar(e){return fu(e,!0,vg,Cg,Dp)}
function fu (line 10) | function fu(e,t,n,o,r){if(!Fe(e)||e.__v_raw&&!(t&&e.__v_isReactive))retu...
function Sr (line 10) | function Sr(e){return nr(e)?Sr(e.__v_raw):!!(e&&e.__v_isReactive)}
function nr (line 10) | function nr(e){return!!(e&&e.__v_isReadonly)}
function Pn (line 10) | function Pn(e){return!!(e&&e.__v_isShallow)}
function pu (line 10) | function pu(e){return e?!!e.__v_raw:!1}
function je (line 10) | function je(e){const t=e&&e.__v_raw;return t?je(t):e}
function yi (line 10) | function yi(e){return Object.isExtensible(e)&&Cp(e,"__v_skip",!0),e}
function Ue (line 10) | function Ue(e){return e?e.__v_isRef===!0:!1}
function D (line 10) | function D(e){return jp(e,!1)}
function Xt (line 10) | function Xt(e){return jp(e,!0)}
function jp (line 10) | function jp(e,t){return Ue(e)?e:new xg(e,t)}
class xg (line 10) | class xg{constructor(t,n){this.dep=new dl,this.__v_isRef=!0,this.__v_isS...
method constructor (line 10) | constructor(t,n){this.dep=new dl,this.__v_isRef=!0,this.__v_isShallow=...
method value (line 10) | get value(){return this.dep.track(),this._value}
method value (line 10) | set value(t){const n=this._rawValue,o=this.__v_isShallow||Pn(t)||nr(t)...
function d (line 10) | function d(e){return Ue(e)?e.value:e}
function Og (line 10) | function Og(e){return Se(e)?e():d(e)}
function Hp (line 10) | function Hp(e){return Sr(e)?e:new Proxy(e,Pg)}
class kg (line 10) | class kg{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=thi...
method constructor (line 10) | constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=n...
method value (line 10) | get value(){return this._value=this._get()}
method value (line 10) | set value(t){this._set(t)}
function Mg (line 10) | function Mg(e){return new kg(e)}
function hn (line 10) | function hn(e){const t=ye(e)?new Array(e.length):{};for(const n in e)t[n...
class Ig (line 10) | class Ig{constructor(t,n,o){this._object=t,this._key=n,this._defaultValu...
method constructor (line 10) | constructor(t,n,o){this._object=t,this._key=n,this._defaultValue=o,thi...
method value (line 10) | get value(){const t=this._object[this._key];return this._value=t===voi...
method value (line 10) | set value(t){this._object[this._key]=t}
method dep (line 10) | get dep(){return lg(je(this._object),this._key)}
class Ag (line 10) | class Ag{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isRead...
method constructor (line 10) | constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0...
method value (line 10) | get value(){return this._value=this._getter()}
function Jt (line 10) | function Jt(e,t,n){return Ue(e)?e:Se(e)?new Ag(e):Fe(e)&&arguments.lengt...
function Up (line 10) | function Up(e,t,n){const o=e[t];return Ue(o)?o:new Ig(e,t,n)}
class Vg (line 10) | class Vg{constructor(t,n,o){this.fn=t,this.setter=n,this._value=void 0,t...
method constructor (line 10) | constructor(t,n,o){this.fn=t,this.setter=n,this._value=void 0,this.dep...
method notify (line 10) | notify(){at!==this&&(this.flags|=16,this.dep.notify())}
method value (line 10) | get value(){const t=this.dep.track();return Ap(this),t&&(t.version=thi...
method value (line 10) | set value(t){this.setter&&this.setter(t)}
function Lg (line 10) | function Lg(e,t,n=!1){let o,r;return Se(e)?o=e:(o=e.get,r=e.set),new Vg(...
function Rg (line 10) | function Rg(e,t=!1,n=qo){if(n){let o=ja.get(n);o||ja.set(n,o=[]),o.push(...
function Ng (line 10) | function Ng(e,t,n=lt){const{immediate:o,deep:r,once:s,scheduler:a,augmen...
function oo (line 10) | function oo(e,t=1/0,n){if(t<=0||!Fe(e)||e.__v_skip||(n=n||new Set,n.has(...
function Us (line 14) | function Us(e,t,n,o){try{return o?e(...o):e()}catch(r){vl(r,t,n)}}
function kn (line 14) | function kn(e,t,n,o){if(Se(e)){const r=Us(e,t,n,o);return r&&Fa(r)&&r.ca...
function vl (line 14) | function vl(e,t,n,o=!0){const r=t?t.vnode:null,{errorHandler:s,throwUnha...
function Bg (line 14) | function Bg(e,t,n,o=!0,r=!1){if(r)throw e;console.error(e)}
function Be (line 14) | function Be(e){const t=mu||Kp;return e?t.then(this?e.bind(this):e):t}
function Fg (line 14) | function Fg(e){let t=$s?Dn+1:0,n=Ut.length;for(;t<n;){const o=t+n>>>1,r=...
function hu (line 14) | function hu(e){if(!(e.flags&1)){const t=xs(e),n=Ut[Ut.length-1];!n||!(e....
function Wp (line 14) | function Wp(){!$s&&!_i&&(_i=!0,mu=Kp.then(Gp))}
function zg (line 14) | function zg(e){ye(e)?Cr.push(...e):xo&&e.id===-1?xo.splice(gr+1,0,e):e.f...
function xc (line 14) | function xc(e,t,n=$s?Dn+1:0){for(;n<Ut.length;n++){const o=Ut[n];if(o&&o...
function qp (line 14) | function qp(e){if(Cr.length){const t=[...new Set(Cr)].sort((n,o)=>xs(n)-...
function Gp (line 14) | function Gp(e){_i=!1,$s=!0;try{for(Dn=0;Dn<Ut.length;Dn++){const t=Ut[Dn...
function Ha (line 14) | function Ha(e){const t=$t;return $t=e,Yp=e&&e.type.__scopeId||null,t}
function O (line 14) | function O(e,t=$t,n){if(!t||e._n)return e;const o=(...r)=>{o._d&&Fc(-1);...
function tt (line 14) | function tt(e,t){if($t===null)return e;const n=yl($t),o=e.dirs||(e.dirs=...
function Ho (line 14) | function Ho(e,t,n,o){const r=e.dirs,s=t&&t.dirs;for(let a=0;a<r.length;a...
method process (line 14) | process(e,t,n,o,r,s,a,l,i,u){const{mc:c,pc:f,pbc:p,o:{insert:m,querySele...
method remove (line 14) | remove(e,t,n,{um:o,o:{remove:r}},s){const{shapeFlag:a,children:l,anchor:...
function fa (line 14) | function fa(e,t,n,{o:{insert:o},m:r},s=2){s===0&&o(e.targetAnchor,t,n);c...
function Hg (line 14) | function Hg(e,t,n,o,r,s,{o:{nextSibling:a,parentNode:l,querySelector:i,i...
function ka (line 14) | function ka(e){const t=e.ctx;if(t&&t.ut){let n=e.targetStart;for(;n&&n!=...
function Qp (line 14) | function Qp(e,t,n,o){const r=t.targetStart=n(""),s=t.targetAnchor=n("");...
function ev (line 14) | function ev(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leaving...
method setup (line 14) | setup(e,{slots:t}){const n=ot(),o=ev();return()=>{const r=t.default&&gu(...
function ov (line 14) | function ov(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==Lt...
function rv (line 14) | function rv(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||...
function Os (line 14) | function Os(e,t,n,o,r){const{appear:s,mode:a,persisted:l=!1,onBeforeEnte...
function Dl (line 14) | function Dl(e){if(ml(e))return e=io(e),e.children=null,e}
function kc (line 14) | function kc(e){if(!ml(e))return Xp(e.type)&&e.children?ov(e.children):e;...
function or (line 14) | function or(e,t){e.shapeFlag&6&&e.component?(e.transition=t,or(e.compone...
function gu (line 14) | function gu(e,t=!1,n){let o=[],r=0;for(let s=0;s<e.length;s++){let a=e[s...
function q (line 14) | function q(e,t){return Se(e)?_t({name:e.name},t,{setup:e}):e}
function sv (line 14) | function sv(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}
function Si (line 14) | function Si(e,t,n,o,r=!1){if(ye(e)){e.forEach((h,v)=>Si(h,t&&(ye(t)?t[v]...
function av (line 14) | function av(e,t){iv(e,"a",t)}
function lv (line 14) | function lv(e,t){iv(e,"da",t)}
function iv (line 14) | function iv(e,t,n=Ot){const o=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if...
function Wg (line 14) | function Wg(e,t,n,o){const r=hl(t,e,o,!0);Ks(()=>{nu(o[t],r)},n)}
function hl (line 14) | function hl(e,t,n=Ot,o=!1){if(n){const r=n[e]||(n[e]=[]),s=t.__weh||(t._...
function Xg (line 14) | function Xg(e,t=Ot){hl("ec",e,t)}
function Yt (line 14) | function Yt(e,t){return yu(bu,e,!0,t)||e}
function et (line 14) | function et(e){return ke(e)?yu(bu,e,!1)||e:e||cv}
function Qg (line 14) | function Qg(e){return yu(Zg,e)}
function yu (line 14) | function yu(e,t,n=!0,o=!1){const r=$t||Ot;if(r){const s=r.type;if(e===bu...
function Mc (line 14) | function Mc(e,t){return e&&(e[t]||e[mn(t)]||e[Hs(mn(t))])}
function xt (line 14) | function xt(e,t,n,o){let r;const s=n,a=ye(e);if(a||ke(e)){const l=a&&Sr(...
function Ci (line 14) | function Ci(e,t){for(let n=0;n<t.length;n++){const o=t[n];if(ye(o))for(l...
function le (line 14) | function le(e,t,n={},o,r){if($t.ce||$t.parent&&Er($t.parent)&&$t.parent....
function dv (line 14) | function dv(e){return e.some(t=>Mn(t)?!(t.type===Lt||t.type===Le&&!dv(t....
function e1 (line 14) | function e1(e,t){const n={};for(const o in e)n[ds(o)]=e[o];return n}
method get (line 14) | get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:o,data:r,...
method set (line 14) | set({_:e},t,n){const{data:o,setupState:r,ctx:s}=e;return jl(r,t)?(r[t]=n...
method has (line 14) | has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:r,propsOption...
method defineProperty (line 14) | defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Qe(n,"valu...
function Ur (line 14) | function Ur(){return fv().slots}
function n1 (line 14) | function n1(){return fv().attrs}
function fv (line 14) | function fv(){const e=ot();return e.setupContext||(e.setupContext=Iv(e))}
function Ic (line 14) | function Ic(e){return ye(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}
function o1 (line 14) | function o1(e){const t=_u(e),n=e.proxy,o=e.ctx;Ti=!1,t.beforeCreate&&Ac(...
function r1 (line 14) | function r1(e,t,n=Xe){ye(e)&&(e=$i(e));for(const o in e){const r=e[o];le...
function Ac (line 14) | function Ac(e,t,n){kn(ye(e)?e.map(o=>o.bind(t.proxy)):e.bind(t.proxy),t,n)}
function pv (line 14) | function pv(e,t,n,o){let r=o.includes(".")?Tv(n,o):()=>n[o];if(ke(e)){co...
function _u (line 14) | function _u(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCa...
function Ua (line 14) | function Ua(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&Ua(e,s,n,!0),r&&r...
function Vc (line 14) | function Vc(e,t){return t?e?function(){return _t(Se(e)?e.call(this,this)...
function a1 (line 14) | function a1(e,t){return is($i(e),$i(t))}
function $i (line 14) | function $i(e){if(ye(e)){const t={};for(let n=0;n<e.length;n++)t[e[n]]=e...
function jt (line 14) | function jt(e,t){return e?[...new Set([].concat(e,t))]:t}
function is (line 14) | function is(e,t){return e?_t(Object.create(null),e,t):t}
function Lc (line 14) | function Lc(e,t){return e?ye(e)&&ye(t)?[...new Set([...e,...t])]:_t(Obje...
function l1 (line 14) | function l1(e,t){if(!e)return t;if(!t)return e;const n=_t(Object.create(...
function vv (line 14) | function vv(){return{app:null,config:{isNativeTag:qh,performance:!1,glob...
function u1 (line 14) | function u1(e,t){return function(o,r=null){Se(o)||(o=_t({},o)),r!=null&&...
function nt (line 14) | function nt(e,t){if(Ot){let n=Ot.provides;const o=Ot.parent&&Ot.parent.p...
function Pe (line 14) | function Pe(e,t,n=!1){const o=Ot||$t;if(o||Tr){const r=Tr?Tr._context.pr...
function c1 (line 14) | function c1(e,t,n,o=!1){const r={},s=hv();e.propsDefaults=Object.create(...
function d1 (line 14) | function d1(e,t,n,o){const{props:r,attrs:s,vnode:{patchFlag:a}}=e,l=je(r...
function bv (line 14) | function bv(e,t,n,o){const[r,s]=e.propsOptions;let a=!1,l;if(t)for(let i...
function xi (line 14) | function xi(e,t,n,o,r,s){const a=e[n];if(a!=null){const l=Qe(a,"default"...
function yv (line 14) | function yv(e,t,n=!1){const o=n?f1:t.propsCache,r=o.get(e);if(r)return r...
function Rc (line 14) | function Rc(e){return e[0]!=="$"&&!cs(e)}
function h1 (line 14) | function h1(e){return g1(e)}
function g1 (line 14) | function g1(e,t){const n=Ep();n.__VUE__=!0;const{insert:o,remove:r,patch...
function Hl (line 14) | function Hl({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n...
function Uo (line 14) | function Uo({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33...
function b1 (line 14) | function b1(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}
function Su (line 14) | function Su(e,t,n=!1){const o=e.children,r=t.children;if(ye(o)&&ye(r))fo...
function y1 (line 14) | function y1(e){const t=e.slice(),n=[0];let o,r,s,a,l;const i=e.length;fo...
function Ev (line 14) | function Ev(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.as...
function Nc (line 14) | function Nc(e){if(e)for(let t=0;t<e.length;t++)e[t].flags|=8}
function po (line 14) | function po(e,t){return Cu(e,null,t)}
function ve (line 14) | function ve(e,t,n){return Cu(e,t,n)}
function Cu (line 14) | function Cu(e,t,n=lt){const{immediate:o,deep:r,flush:s,once:a}=n,l=_t({}...
function S1 (line 14) | function S1(e,t,n){const o=this.proxy,r=ke(e)?e.includes(".")?Tv(o,e):()...
function Tv (line 14) | function Tv(e,t){const n=t.split(".");return()=>{let o=e;for(let r=0;r<n...
function E1 (line 14) | function E1(e,t,...n){if(e.isUnmounted)return;const o=e.vnode.props||lt;...
function $v (line 14) | function $v(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(r!==void 0)retu...
function gl (line 14) | function gl(e,t){return!e||!il(t)?!1:(t=t.slice(2).replace(/Once$/,""),Q...
function Ul (line 14) | function Ul(e){const{type:t,vnode:n,proxy:o,withProxy:r,propsOptions:[s]...
function x1 (line 14) | function x1(e,t,n){const{props:o,children:r,component:s}=e,{props:a,chil...
function Bc (line 14) | function Bc(e,t,n){const o=Object.keys(t);if(o.length!==Object.keys(e).l...
function O1 (line 14) | function O1({vnode:e,parent:t},n){for(;t;){const o=t.subTree;if(o.suspen...
function P1 (line 14) | function P1(e,t){t&&t.pendingBranch?ye(e)?t.effects.push(...e):t.effects...
function E (line 14) | function E(e=!1){ms.push(an=e?null:[])}
function k1 (line 14) | function k1(){ms.pop(),an=ms[ms.length-1]||null}
function Fc (line 14) | function Fc(e){Ps+=e,e<0&&an&&(an.hasOnce=!0)}
function Ov (line 14) | function Ov(e){return e.dynamicChildren=Ps>0?an||_r:null,k1(),Ps>0&&an&&...
function F (line 14) | function F(e,t,n,o,r,s){return Ov(L(e,t,n,o,r,s,!0))}
function Z (line 14) | function Z(e,t,n,o,r){return Ov(S(e,t,n,o,r,!0))}
function Mn (line 14) | function Mn(e){return e?e.__v_isVNode===!0:!1}
function Go (line 14) | function Go(e,t){return e.type===t.type&&e.key===t.key}
function L (line 14) | function L(e,t=null,n=null,o=0,r=null,s=e===Le?0:1,a=!1,l=!1){const i={_...
function M1 (line 14) | function M1(e,t=null,n=null,o=0,r=null,s=!1){if((!e||e===cv)&&(e=Lt),Mn(...
function I1 (line 14) | function I1(e){return e?pu(e)||gv(e)?_t({},e):e:null}
function io (line 14) | function io(e,t,n=!1,o=!1){const{props:r,ref:s,patchFlag:a,children:l,tr...
function Te (line 14) | function Te(e=" ",t=0){return S(Kr,null,e,t)}
function ee (line 14) | function ee(e="",t=!1){return t?(E(),Z(Lt,null,e)):S(Lt,null,e)}
function Hn (line 14) | function Hn(e){return e==null||typeof e=="boolean"?S(Lt):ye(e)?S(Le,null...
function Po (line 14) | function Po(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:io(e)}
function Eu (line 14) | function Eu(e,t){let n=0;const{shapeFlag:o}=e;if(t==null)t=null;else if(...
function Zt (line 14) | function Zt(...e){const t={};for(let n=0;n<e.length;n++){const o=e[n];fo...
function zn (line 14) | function zn(e,t,n,o=null){kn(e,t,7,[n,o])}
function L1 (line 14) | function L1(e,t,n){const o=e.type,r=(t?t.appContext:e.appContext)||A1,s=...
function kv (line 14) | function kv(e){return e.vnode.shapeFlag&4}
function R1 (line 14) | function R1(e,t=!1,n=!1){t&&Oi(t);const{props:o,children:r}=e.vnode,s=kv...
function N1 (line 14) | function N1(e,t){const n=e.type;e.accessCache=Object.create(null),e.prox...
function Dc (line 14) | function Dc(e,t,n){Se(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render...
function Mv (line 14) | function Mv(e,t,n){const o=e.type;if(!e.render){if(!t&&jc&&!o.render){co...
method get (line 14) | get(e,t){return Ft(e,"get",""),e[t]}
function Iv (line 14) | function Iv(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.att...
function yl (line 14) | function yl(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(...
function F1 (line 14) | function F1(e,t=!0){return Se(e)?e.displayName||e.name:e.name||t&&e.__name}
function z1 (line 14) | function z1(e){return Se(e)&&"__vccOpts"in e}
function Je (line 14) | function Je(e,t,n){const o=arguments.length;return o===2?Fe(t)&&!ye(t)?M...
method setScopeId (line 18) | setScopeId(e,t){e.setAttribute(t,"")}
method insertStaticContent (line 18) | insertStaticContent(e,t,n,o,r,s){const a=n?n.previousSibling:t.lastChild...
function Rv (line 18) | function Rv(e){const t={};for(const P in e)P in Vv||(t[P]=e[P]);if(e.css...
function q1 (line 18) | function q1(e){if(e==null)return null;if(Fe(e))return[Wl(e.enter),Wl(e.l...
function Wl (line 18) | function Wl(e){return Xh(e)}
function eo (line 18) | function eo(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Pr]...
function Co (line 18) | function Co(e,t){t.split(/\s+/).forEach(o=>o&&e.classList.remove(o));con...
function Wc (line 18) | function Wc(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}
function qc (line 18) | function qc(e,t,n,o){const r=e._endId=++G1,s=()=>{r===e._endId&&o()};if(...
function Nv (line 18) | function Nv(e,t){const n=window.getComputedStyle(e),o=h=>(n[h]||"").spli...
function Gc (line 18) | function Gc(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max(....
function Yc (line 18) | function Yc(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",",".")...
function Bv (line 18) | function Bv(){return document.body.offsetHeight}
function Y1 (line 18) | function Y1(e,t,n){const o=e[Pr];o&&(t=(t?[t,...o]:[...o]).join(" ")),t=...
method beforeMount (line 18) | beforeMount(e,{value:t},{transition:n}){e[Wa]=e.style.display==="none"?"...
method mounted (line 18) | mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)}
method updated (line 18) | updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnt...
method beforeUnmount (line 18) | beforeUnmount(e,{value:t}){ns(e,t)}
function ns (line 18) | function ns(e,t){e.style.display=t?e[Wa]:"none",e[Fv]=!t}
function Z1 (line 18) | function Z1(e,t,n){const o=e.style,r=ke(n);let s=!1;if(n&&!r){if(t)if(ke...
function Ia (line 18) | function Ia(e,t,n){if(ye(n))n.forEach(o=>Ia(e,t,o));else if(n==null&&(n=...
function Q1 (line 18) | function Q1(e,t){const n=ql[t];if(n)return n;let o=mn(t);if(o!=="filter"...
function Qc (line 18) | function Qc(e,t,n,o,r,s=og(t)){o&&t.startsWith("xlink:")?n==null?e.remov...
function eb (line 18) | function eb(e,t,n,o){if(t==="innerHTML"||t==="textContent"){n!=null&&(e[...
function Mo (line 18) | function Mo(e,t,n,o){e.addEventListener(t,n,o)}
function tb (line 18) | function tb(e,t,n,o){e.removeEventListener(t,n,o)}
function nb (line 18) | function nb(e,t,n,o,r=null){const s=e[ed]||(e[ed]={}),a=s[t];if(o&&a)a.v...
function ob (line 18) | function ob(e){let t;if(td.test(e)){t={};let o;for(;o=e.match(td);)e=e.s...
function ab (line 18) | function ab(e,t){const n=o=>{if(!o._vts)o._vts=Date.now();else if(o._vts...
function lb (line 18) | function lb(e,t){if(ye(t)){const n=e.stopImmediatePropagation;return e.s...
function ub (line 18) | function ub(e,t,n,o){if(o)return!!(t==="innerHTML"||t==="textContent"||t...
method setup (line 18) | setup(e,{slots:t}){const n=ot(),o=ev();let r,s;return Hr(()=>{if(!r.leng...
function fb (line 18) | function fb(e){const t=e.el;t[qa]&&t[qa](),t[od]&&t[od]()}
function pb (line 18) | function pb(e){Dv.set(e,e.el.getBoundingClientRect())}
function vb (line 18) | function vb(e){const t=zv.get(e),n=Dv.get(e),o=t.left-n.left,r=t.top-n.t...
function mb (line 18) | function mb(e,t,n){const o=e.cloneNode(),r=e[Pr];r&&r.forEach(l=>{l.spli...
function hb (line 18) | function hb(e){e.target.composing=!0}
function rd (line 18) | function rd(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchE...
method created (line 18) | created(e,{modifiers:{lazy:t,trim:n,number:o}},r){e[ao]=kr(r);const s=o|...
method mounted (line 18) | mounted(e,{value:t}){e.value=t??""}
method beforeUpdate (line 18) | beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:o,trim:r,number:s}},a...
method created (line 18) | created(e,t,n){e[ao]=kr(n),Mo(e,"change",()=>{const o=e._modelValue,r=Uv...
method beforeUpdate (line 18) | beforeUpdate(e,t,n){e[ao]=kr(n),sd(e,t,n)}
function sd (line 18) | function sd(e,{value:t,oldValue:n},o){e._modelValue=t;let r;ye(t)?r=$p(t...
method created (line 18) | created(e,{value:t},n){e.checked=Or(t,n.props.value),e[ao]=kr(n),Mo(e,"c...
method beforeUpdate (line 18) | beforeUpdate(e,{value:t,oldValue:n},o){e[ao]=kr(o),t!==n&&(e.checked=Or(...
function Uv (line 18) | function Uv(e){return"_value"in e?e._value:e.value}
function Kv (line 18) | function Kv(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}
function Wv (line 18) | function Wv(){return ad||(ad=h1(wb))}
function Sb (line 18) | function Sb(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLEl...
function Cb (line 18) | function Cb(e){return ke(e)?document.querySelector(e):e}
function cd (line 18) | function cd(e,t){var n;const o=Xt();return po(()=>{o.value=e()},Ab(Ib({}...
function ks (line 18) | function ks(e){return typeof e=="function"?e():d(e)}
function Nb (line 18) | function Nb(e,t){function n(...o){return new Promise((r,s)=>{Promise.res...
function Bb (line 18) | function Bb(e,t={}){let n,o,r=Ja;const s=l=>{clearTimeout(l),r(),r=Ja};r...
function Fb (line 18) | function Fb(e){return e}
function qs (line 18) | function qs(e){return ru()?(su(e),!0):!1}
function zb (line 18) | function zb(e,t=200,n={}){return Nb(Bb(t,n),e)}
function Db (line 18) | function Db(e,t=200,n={}){const o=D(e.value),r=zb(()=>{o.value=e.value},...
function jb (line 18) | function jb(e,t=!0){ot()?Ge(e):t?e():Be(e)}
function Mi (line 18) | function Mi(e,t,n={}){const{immediate:o=!0}=n,r=D(!1);let s=null;functio...
function ro (line 18) | function ro(e){var t;const n=ks(e);return(t=n==null?void 0:n.$el)!=null?...
function Pt (line 18) | function Pt(...e){let t,n,o,r;if(Rb(e[0])||Array.isArray(e[0])?([n,o,r]=...
function Ub (line 18) | function Ub(e,t,n={}){const{window:o=Gs,ignore:r=[],capture:s=!0,detectI...
function Gv (line 18) | function Gv(e,t=!1){const n=D(),o=()=>n.value=!!e();return o(),jb(o,t),n}
function Kb (line 18) | function Kb(e){return JSON.parse(JSON.stringify(e))}
function Wb (line 18) | function Wb({document:e=Hb}={}){if(!e)return D("visible");const t=D(e.vi...
function Kt (line 18) | function Kt(e,t,n={}){const o=n,{window:r=Gs}=o,s=Yb(o,["window"]);let a...
function Qb (line 18) | function Qb(e,t,n={}){const o=n,{window:r=Gs}=o,s=Zb(o,["window"]);let a...
function s2 (line 18) | function s2(e,t,n,o={}){var r,s,a;const{clone:l=!1,passive:i=!1,eventNam...
function a2 (line 18) | function a2({window:e=Gs}={}){if(!e)return D(!1);const t=D(e.document.ha...
function f2 (line 18) | function f2(e){var t=c2.call(e,os),n=e[os];try{e[os]=void 0;var o=!0}cat...
function m2 (line 18) | function m2(e){return v2.call(e)}
function Wr (line 18) | function Wr(e){return e==null?e===void 0?g2:h2:wd&&wd in Object(e)?f2(e)...
function Ro (line 18) | function Ro(e){return e!=null&&typeof e=="object"}
function _l (line 18) | function _l(e){return typeof e=="symbol"||Ro(e)&&Wr(e)==b2}
function y2 (line 18) | function y2(e,t){for(var n=-1,o=e==null?0:e.length,r=Array(o);++n<o;)r[n...
function Xv (line 18) | function Xv(e){if(typeof e=="string")return e;if(bn(e))return y2(e,Xv)+"...
function S2 (line 18) | function S2(e){for(var t=e.length;t--&&w2.test(e.charAt(t)););return t}
function E2 (line 18) | function E2(e){return e&&e.slice(0,S2(e)+1).replace(C2,"")}
function yn (line 18) | function yn(e){var t=typeof e;return e!=null&&(t=="object"||t=="function")}
function Td (line 18) | function Td(e){if(typeof e=="number")return e;if(_l(e))return Ed;if(yn(e...
function Zv (line 18) | function Zv(e){return e}
function Qv (line 18) | function Qv(e){if(!yn(e))return!1;var t=Wr(e);return t==k2||t==M2||t==P2...
function A2 (line 18) | function A2(e){return!!$d&&$d in e}
function lr (line 18) | function lr(e){if(e!=null){try{return L2.call(e)}catch{}try{return e+""}...
function H2 (line 18) | function H2(e){if(!yn(e)||A2(e))return!1;var t=Qv(e)?j2:N2;return t.test...
function U2 (line 18) | function U2(e,t){return e==null?void 0:e[t]}
function ir (line 18) | function ir(e,t){var n=U2(e,t);return H2(n)?n:void 0}
function e (line 18) | function e(){}
function W2 (line 18) | function W2(e,t,n){switch(n.length){case 0:return e.call(t);case 1:retur...
function q2 (line 18) | function q2(e,t){var n=-1,o=e.length;for(t||(t=Array(o));++n<o;)t[n]=e[n...
function X2 (line 18) | function X2(e){var t=0,n=0;return function(){var o=J2(),r=Y2-(o-n);if(n=...
function Z2 (line 18) | function Z2(e){return function(){return e}}
function ty (line 18) | function ty(e,t){for(var n=-1,o=e==null?0:e.length;++n<o&&t(e[n],n,e)!==...
function ny (line 18) | function ny(e,t,n,o){e.length;for(var r=n+1;r--;)if(t(e[r],r,e))return r...
function Tu (line 18) | function Tu(e,t){var n=typeof e;return t=t??oy,!!t&&(n=="number"||n!="sy...
function em (line 18) | function em(e,t,n){t=="__proto__"&&Xa?Xa(e,t,{configurable:!0,enumerable...
function $u (line 18) | function $u(e,t){return e===t||e!==e&&t!==t}
function xu (line 18) | function xu(e,t,n){var o=e[t];(!(ay.call(e,t)&&$u(o,n))||n===void 0&&!(t...
function wl (line 18) | function wl(e,t,n,o){var r=!n;n||(n={});for(var s=-1,a=t.length;++s<a;){...
function ly (line 18) | function ly(e,t,n){return t=Od(t===void 0?e.length-1:t,0),function(){for...
function Ou (line 18) | function Ou(e){return typeof e=="number"&&e>-1&&e%1==0&&e<=iy}
function tm (line 18) | function tm(e){return e!=null&&Ou(e.length)&&!Qv(e)}
function Pu (line 18) | function Pu(e){var t=e&&e.constructor,n=typeof t=="function"&&t.prototyp...
function cy (line 18) | function cy(e,t){for(var n=-1,o=Array(e);++n<e;)o[n]=t(n);return o}
function Pd (line 18) | function Pd(e){return Ro(e)&&Wr(e)==dy}
function vy (line 18) | function vy(){return!1}
function Dy (line 18) | function Dy(e){return Ro(e)&&Ou(e.length)&&!!dt[Wr(e)]}
function Mu (line 18) | function Mu(e){return function(t){return e(t)}}
function am (line 18) | function am(e,t){var n=bn(e),o=!n&&ku(e),r=!n&&!o&&Za(e),s=!n&&!o&&!r&&s...
function lm (line 18) | function lm(e,t){return function(n){return e(t(n))}}
function Gy (line 18) | function Gy(e){if(!Pu(e))return Ky(e);var t=[];for(var n in Object(e))qy...
function Sl (line 18) | function Sl(e){return tm(e)?am(e):Gy(e)}
function Yy (line 18) | function Yy(e){var t=[];if(e!=null)for(var n in Object(e))t.push(n);retu...
function Zy (line 18) | function Zy(e){if(!yn(e))return Yy(e);var t=Pu(e),n=[];for(var o in e)o=...
function Iu (line 18) | function Iu(e){return tm(e)?am(e,!0):Zy(e)}
function Au (line 18) | function Au(e,t){if(bn(e))return!1;var n=typeof e;return n=="number"||n=...
function t4 (line 18) | function t4(){this.__data__=Ms?Ms(null):{},this.size=0}
function n4 (line 18) | function n4(e){var t=this.has(e)&&delete this.__data__[e];return this.si...
function a4 (line 18) | function a4(e){var t=this.__data__;if(Ms){var n=t[e];return n===o4?void ...
function u4 (line 18) | function u4(e){var t=this.__data__;return Ms?t[e]!==void 0:i4.call(t,e)}
function d4 (line 18) | function d4(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n...
function rr (line 18) | function rr(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){va...
function f4 (line 18) | function f4(){this.__data__=[],this.size=0}
function Cl (line 18) | function Cl(e,t){for(var n=e.length;n--;)if($u(e[n][0],t))return n;retur...
function m4 (line 18) | function m4(e){var t=this.__data__,n=Cl(t,e);if(n<0)return!1;var o=t.len...
function h4 (line 18) | function h4(e){var t=this.__data__,n=Cl(t,e);return n<0?void 0:t[n][1]}
function g4 (line 18) | function g4(e){return Cl(this.__data__,e)>-1}
function b4 (line 18) | function b4(e,t){var n=this.__data__,o=Cl(n,e);return o<0?(++this.size,n...
function mo (line 18) | function mo(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){va...
function y4 (line 18) | function y4(){this.size=0,this.__data__={hash:new rr,map:new(Is||mo),str...
function _4 (line 18) | function _4(e){var t=typeof e;return t=="string"||t=="number"||t=="symbo...
function El (line 18) | function El(e,t){var n=e.__data__;return _4(t)?n[typeof t=="string"?"str...
function w4 (line 18) | function w4(e){var t=El(this,e).delete(e);return this.size-=t?1:0,t}
function S4 (line 18) | function S4(e){return El(this,e).get(e)}
function C4 (line 18) | function C4(e){return El(this,e).has(e)}
function E4 (line 18) | function E4(e,t){var n=El(this,e),o=n.size;return n.set(e,t),this.size+=...
function ho (line 18) | function ho(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){va...
function Vu (line 18) | function Vu(e,t){if(typeof e!="function"||t!=null&&typeof t!="function")...
function x4 (line 18) | function x4(e){var t=Vu(e,function(o){return n.size===$4&&n.clear(),o}),...
function M4 (line 18) | function M4(e){return e==null?"":Xv(e)}
function Tl (line 18) | function Tl(e,t){return bn(e)?e:Au(e,t)?[e]:k4(M4(e))}
function Ys (line 18) | function Ys(e){if(typeof e=="string"||_l(e))return e;var t=e+"";return t...
function Lu (line 18) | function Lu(e,t){t=Tl(t,e);for(var n=0,o=t.length;e!=null&&n<o;)e=e[Ys(t...
function Un (line 18) | function Un(e,t,n){var o=e==null?void 0:Lu(e,t);return o===void 0?n:o}
function Ru (line 18) | function Ru(e,t){for(var n=-1,o=t.length,r=e.length;++n<o;)e[r+n]=t[n];r...
function A4 (line 18) | function A4(e){return bn(e)||ku(e)||!!(Ad&&e&&e[Ad])}
function V4 (line 18) | function V4(e,t,n,o,r){var s=-1,a=e.length;for(n||(n=A4),r||(r=[]);++s<a...
function L4 (line 18) | function L4(e){var t=e==null?0:e.length;return t?V4(e):[]}
function R4 (line 18) | function R4(e){return ey(ly(e,void 0,L4),e+"")}
function sn (line 18) | function sn(){if(!arguments.length)return[];var e=arguments[0];return bn...
function N4 (line 18) | function N4(){this.__data__=new mo,this.size=0}
function B4 (line 18) | function B4(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}
function F4 (line 18) | function F4(e){return this.__data__.get(e)}
function z4 (line 18) | function z4(e){return this.__data__.has(e)}
function j4 (line 18) | function j4(e,t){var n=this.__data__;if(n instanceof mo){var o=n.__data_...
function Kn (line 18) | function Kn(e){var t=this.__data__=new mo(e);this.size=t.size}
function H4 (line 18) | function H4(e,t){return e&&wl(t,Sl(t),e)}
function U4 (line 18) | function U4(e,t){return e&&wl(t,Iu(t),e)}
function W4 (line 18) | function W4(e,t){if(t)return e.slice();var n=e.length,o=Rd?Rd(n):new e.c...
function q4 (line 18) | function q4(e,t){for(var n=-1,o=e==null?0:e.length,r=0,s=[];++n<o;){var ...
function cm (line 18) | function cm(){return[]}
function J4 (line 18) | function J4(e,t){return wl(e,Nu(e),t)}
function Z4 (line 18) | function Z4(e,t){return wl(e,dm(e),t)}
function fm (line 18) | function fm(e,t,n){var o=t(e);return bn(e)?o:Ru(o,n(e))}
function Ai (line 18) | function Ai(e){return fm(e,Sl,Nu)}
function Q4 (line 18) | function Q4(e){return fm(e,Iu,dm)}
function i3 (line 18) | function i3(e){var t=e.length,n=new e.constructor(t);return t&&typeof e[...
function Bu (line 18) | function Bu(e){var t=new e.constructor(e.byteLength);return new Qa(t).se...
function u3 (line 18) | function u3(e,t){var n=t?Bu(e.buffer):e.buffer;return new e.constructor(...
function d3 (line 18) | function d3(e){var t=new e.constructor(e.source,c3.exec(e));return t.las...
function f3 (line 18) | function f3(e){return Ud?Object(Ud.call(e)):{}}
function p3 (line 18) | function p3(e,t){var n=t?Bu(e.buffer):e.buffer;return new e.constructor(...
function A3 (line 18) | function A3(e,t,n){var o=e.constructor;switch(t){case S3:return Bu(e);ca...
function V3 (line 18) | function V3(e){return typeof e.constructor=="function"&&!Pu(e)?K2(im(e))...
function R3 (line 18) | function R3(e){return Ro(e)&&$n(e)==L3}
function F3 (line 18) | function F3(e){return Ro(e)&&$n(e)==B3}
function gs (line 18) | function gs(e,t,n,o,r,s){var a,l=t&D3,i=t&j3,u=t&H3;if(a!==void 0)return...
function qd (line 18) | function qd(e){return gs(e,p_)}
function Gd (line 18) | function Gd(e){return gs(e,v_|m_)}
function g_ (line 18) | function g_(e){return this.__data__.set(e,h_),this}
function b_ (line 18) | function b_(e){return this.__data__.has(e)}
function el (line 18) | function el(e){var t=-1,n=e==null?0:e.length;for(this.__data__=new ho;++...
function y_ (line 18) | function y_(e,t){for(var n=-1,o=e==null?0:e.length;++n<o;)if(t(e[n],n,e)...
function __ (line 18) | function __(e,t){return e.has(t)}
function hm (line 18) | function hm(e,t,n,o,r,s){var a=n&w_,l=e.length,i=t.length;if(l!=i&&!(a&&...
function C_ (line 18) | function C_(e){var t=-1,n=Array(e.size);return e.forEach(function(o,r){n...
function E_ (line 18) | function E_(e){var t=-1,n=Array(e.size);return e.forEach(function(o){n[+...
function B_ (line 18) | function B_(e,t,n,o,r,s,a){switch(n){case N_:if(e.byteLength!=t.byteLeng...
function j_ (line 18) | function j_(e,t,n,o,r,s){var a=n&F_,l=Ai(e),i=l.length,u=Ai(t),c=u.lengt...
function K_ (line 18) | function K_(e,t,n,o,r,s){var a=bn(e),l=bn(t),i=a?Xd:$n(e),u=l?Xd:$n(t);i...
function $l (line 18) | function $l(e,t,n,o,r){return e===t?!0:e==null||t==null||!Ro(e)&&!Ro(t)?...
function G_ (line 18) | function G_(e,t,n,o){var r=n.length,s=r;if(e==null)return!s;for(e=Object...
function gm (line 18) | function gm(e){return e===e&&!yn(e)}
function Y_ (line 18) | function Y_(e){for(var t=Sl(e),n=t.length;n--;){var o=t[n],r=e[o];t[n]=[...
function bm (line 18) | function bm(e,t){return function(n){return n==null?!1:n[e]===t&&(t!==voi...
function J_ (line 18) | function J_(e){var t=Y_(e);return t.length==1&&t[0][2]?bm(t[0][0],t[0][1...
function X_ (line 18) | function X_(e,t){return e!=null&&t in Object(e)}
function Z_ (line 18) | function Z_(e,t,n){t=Tl(t,e);for(var o=-1,r=t.length,s=!1;++o<r;){var a=...
function ym (line 18) | function ym(e,t){return e!=null&&Z_(e,t,X_)}
function t6 (line 18) | function t6(e,t){return Au(e)&&gm(t)?bm(Ys(e),t):function(n){var o=Un(n,...
function n6 (line 18) | function n6(e){return function(t){return t==null?void 0:t[e]}}
function o6 (line 18) | function o6(e){return function(t){return Lu(t,e)}}
function r6 (line 18) | function r6(e){return Au(e)?n6(Ys(e)):o6(e)}
function s6 (line 18) | function s6(e){return typeof e=="function"?e:e==null?Zv:typeof e=="objec...
function tl (line 18) | function tl(e,t,n){var o,r,s,a,l,i,u=0,c=!1,f=!1,p=!0;if(typeof e!="func...
function u6 (line 18) | function u6(e,t,n){var o=e==null?0:e.length;if(!o)return-1;var r=o-1;ret...
function nl (line 18) | function nl(e){for(var t=-1,n=e==null?0:e.length,o={};++t<n;){var r=e[t]...
function Ir (line 18) | function Ir(e,t){return $l(e,t)}
function Qt (line 18) | function Qt(e){return e==null}
function c6 (line 18) | function c6(e){return e===v
Condensed preview — 195 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (8,417K chars).
[
{
"path": ".gitattributes",
"chars": 264,
"preview": "# 指定 Python 为主要语言\n*.py linguist-detectable=true\n\n# 将 HTML 和 JavaScript 归类为文档或前端资源,降低权重\n*.html linguist-vendored\n*.js lin"
},
{
"path": ".github/FUNDING.yml",
"chars": 57,
"preview": "github: [hanxi]\ncustom: ['https://afdian.com/a/imhanxi']\n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 5781,
"preview": "name: CI Workflow\n\non:\n push:\n branches:\n - \"*\" # 所有分支触发\n tags:\n - \"v*\"\n workflow_dispatch:\n\nenv:\n VE"
},
{
"path": ".github/workflows/dockerhub-description.yml",
"chars": 560,
"preview": "name: Update Docker Hub Description\n\npermissions:\n contents: read\n\non:\n push:\n branches:\n - main\n paths:\n "
},
{
"path": ".github/workflows/fmt.yml",
"chars": 1169,
"preview": "name: fmt\n\non:\n push:\n branches:\n - \"*\"\n workflow_dispatch:\n\npermissions:\n contents: read\n\njobs:\n format:\n "
},
{
"path": ".github/workflows/static.yml",
"chars": 3215,
"preview": "# Simple workflow for deploying static content to GitHub Pages\nname: Deploy static content to Pages\n\non:\n # Runs on pus"
},
{
"path": ".gitignore",
"chars": 3233,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".gitmodules",
"chars": 0,
"preview": ""
},
{
"path": ".pre-commit-config.yaml",
"chars": 154,
"preview": "repos:\n- hooks:\n - id: commitizen\n - id: commitizen-branch\n stages:\n - push\n repo: https://github.com/commitize"
},
{
"path": "CHANGELOG.md",
"chars": 18933,
"preview": "## v0.4.26 (2026-03-20)\n\n### Fix\n\n- proxy handler CDN safeguard & content-type based FFmpeg routing (#791)\n\n## v0.4.25 ("
},
{
"path": "Dockerfile",
"chars": 3065,
"preview": "# 定义构建参数,用于指定架构和基础镜像\nARG PYTHON_VERSION=3.14\n\n# 根据不同架构选择对应的基础镜像\nFROM python:${PYTHON_VERSION}-alpine AS base-linux-amd64"
},
{
"path": "LICENSE",
"chars": 1059,
"preview": "MIT License\n\nCopyright (c) 2023 涵曦\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this"
},
{
"path": "README.md",
"chars": 11284,
"preview": "# XiaoMusic: 无限听歌,解放小爱音箱\n\n[](https://github.com/"
},
{
"path": "check_plugins.py",
"chars": 1525,
"preview": "#!/usr/bin/env python3\n\"\"\"\n检查所有插件的加载状态\n\"\"\"\n\nimport sys\n\nsys.path.append(\".\")\n\nfrom xiaomusic.config import Config\nfrom x"
},
{
"path": "config-example.json",
"chars": 3632,
"preview": "{\n \"account\": \"\",\n \"password\": \"\",\n \"mi_did\": \"\",\n \"cookie\": \"\",\n \"verbose\": false,\n \"music_path\": \"music\",\n \"tem"
},
{
"path": "docs/.vitepress/config.mts",
"chars": 2096,
"preview": "import { loadEnv, defineConfig } from 'vitepress'\nimport AutoSidebar from 'vite-plugin-vitepress-auto-sidebar';\nimport G"
},
{
"path": "docs/.vitepress/vitepress-plugin-github-issues.mts",
"chars": 5528,
"preview": "import axios from 'axios';\nimport fs from 'fs';\nimport path from 'path';\nimport type { Plugin } from 'vitepress';\n\ninter"
},
{
"path": "docs/CNAME",
"chars": 14,
"preview": "xdocs.hanxi.cc"
},
{
"path": "docs/index.md",
"chars": 538,
"preview": "---\n# https://vitepress.dev/reference/default-theme-home-page\nlayout: home\n\nhero:\n name: \"XiaoMusic\"\n text: \"无限听歌<br>解"
},
{
"path": "docs/issues/101.md",
"chars": 9256,
"preview": "---\ntitle: 群晖docker安装 xiaomusic\n---\n# 群晖docker安装 xiaomusic\n由于现在群晖已经无法正常下载 docker 里的镜像了,绕了好多弯;现在用 ssh 服务命令拉取镜像来创建容器;\n\n## "
},
{
"path": "docs/issues/105.md",
"chars": 21819,
"preview": "---\ntitle: 【插件】自定义口令功能\n---\n# 【插件】自定义口令功能\n自定义口令配置需要配置到 config.json 文件里,使用 config.json 方式启动。参考 </issues/94.html> 。\r\n\r\n口令的配"
},
{
"path": "docs/issues/182.md",
"chars": 3670,
"preview": "---\ntitle: 定时任务配置格式\n---\n# 定时任务配置格式\n支持采用 crontab 的格式配置定时任务,已经支持下面的任务类型:\n\n- stop 关机\n- play 播放歌曲\n- play_music_list 播放列表\n- t"
},
{
"path": "docs/issues/19.md",
"chars": 4408,
"preview": "---\ntitle: 如何修改默认的8090端口\n---\n# 如何修改默认的8090端口\ndocker-compose 修改映射端口会播放失败\r\n\r\n```\r\nports:\r\n - 80:8090\r\n```\r\n\r\n从日志看继续调用"
},
{
"path": "docs/issues/210.md",
"chars": 3157,
"preview": "---\ntitle: yt-dlp cookies 文件上传功能\n---\n# yt-dlp cookies 文件上传功能\n此功能用于解决 yt-dlp 下载资源失败时使用,比如 **ip 被 B站或者 youtube 加入黑名单**后才需要"
},
{
"path": "docs/issues/211.md",
"chars": 6274,
"preview": "---\ntitle: 📝 文档汇总\n---\n# 📝 文档汇总\n## 1️⃣ 基础文档\n\n- [💬 FAQ问题集合](/issues/99.html)\n- [如何修改默认的8090端口](/issues/19.html)\n- [如何配置网络歌"
},
{
"path": "docs/issues/212.md",
"chars": 2232,
"preview": "---\ntitle: 如何批量下载歌曲\n---\n# 如何批量下载歌曲\n批量下载歌曲依赖的是 yt-dlp 批量下载播放列表里的视频并转为 mp3 实现的。\r\n\r\n先进入到歌曲下载工具页面:\r\n\r\n> 默认主题 => 设置 => 歌曲下载工具"
},
{
"path": "docs/issues/235.md",
"chars": 3036,
"preview": "---\ntitle: xiaomusic立体声\n---\n# xiaomusic立体声\n有多个不同版本的小爱,怎么能选择多个音箱一起播放?\n## 评论\n\n### 评论 1 - hanxi\n参考这个文档,配到一个组里就能同时播放,但是会有播放进"
},
{
"path": "docs/issues/269.md",
"chars": 16708,
"preview": "---\ntitle: 如何添加 网易云音乐playlist\n---\n# 如何添加 网易云音乐playlist\n利用 NeteaseCloudMusicApi 获取歌单和播放地址\r\n我在你基础上改了一下,但是我的逻辑不合理\r\nhttps://"
},
{
"path": "docs/issues/285.md",
"chars": 3415,
"preview": "---\ntitle: 相关工具推荐\n---\n# 相关工具推荐\n- [xhongc/music-tag-web 刮削音乐歌词图片](https://github.com/xhongc/music-tag-web)\n- [onlyLTY/doc"
},
{
"path": "docs/issues/294.md",
"chars": 1113,
"preview": "---\ntitle: 关于M01型号的注意事项\n---\n# 关于M01型号的注意事项\nM01:在0.3.55版本【型号兼容模式】与【特殊型号获取对话记录】都设为false或true,都可以语音了。\r\n 如果【型号兼容模式】为 "
},
{
"path": "docs/issues/297.md",
"chars": 18366,
"preview": "---\ntitle: xiaomusic极空间安装教程(2024/12/28更新)\n---\n# xiaomusic极空间安装教程(2024-12-28更新)\n> 本教程同步更新于最新版的xiaomusic\r\n\r\n<s>看不懂/嫌麻烦/懒 但"
},
{
"path": "docs/issues/312.md",
"chars": 453,
"preview": "---\ntitle: 同步网易云歌单\n---\n# 同步网易云歌单\n推广一波 [netease-playlist](https://github.com/qiujie8092916/netease-playlist) \r\n通过网易云音乐歌单"
},
{
"path": "docs/issues/333.md",
"chars": 2002,
"preview": "---\ntitle: 设置项功能介绍\n---\n# 设置项功能介绍\n- XIAOMUSIC_ACTIVE_CMD 环境变量,对应后台的 【允许唤醒的命令】,用于唤醒口令,配置成'play,random_play',在非播放状态下,只有这两个指"
},
{
"path": "docs/issues/350.md",
"chars": 332,
"preview": "---\ntitle: 播放本地歌曲无法切歌\n---\n# 播放本地歌曲无法切歌\n播放本地歌曲时,通过语音控制小爱音箱切歌(测试指令:小爱同学,切歌;小爱同学,播放下一首;),每次都是重新播放上一首歌曲;版本为docker最新版本[0.3.70"
},
{
"path": "docs/issues/360.md",
"chars": 2928,
"preview": "---\ntitle: docker compose 命令行安装教程\n---\n# docker compose 命令行安装教程\n本教程针对于有命令行环境,且已经安装好 docker compose 的用户。步骤超级简单,基本只要复制粘贴就能跑"
},
{
"path": "docs/issues/365.md",
"chars": 651,
"preview": "---\ntitle: 已知 ttsCommand\n---\n# 已知 ttsCommand\n参考: https://github.com/idootop/mi-gpt/blob/main/docs/compatibility.md\n\n- 小爱"
},
{
"path": "docs/issues/366.md",
"chars": 1105,
"preview": "---\ntitle: 多设备单独播放功能设计\n---\n# 多设备单独播放功能设计\n## 分组功能\n\n为设备设计分组功能,可以把一个或者多个设备加入到一个分组,一个分组内的设备会被控制同时播放。设备的音量需要支持独立配置。\n\n分组用 grou"
},
{
"path": "docs/issues/389.md",
"chars": 230,
"preview": "---\ntitle: 搞了个入门配置教程\n---\n# 搞了个入门配置教程\n官方文档不直观啊,得摸索好久才搞定,第一步怎么设置我找了好久,哈哈哈。\n\n写了个分3步的教程,希望有帮助吧: [xiaomusic 安装、配置和使用教程,免费用小爱音"
},
{
"path": "docs/issues/398.md",
"chars": 245,
"preview": "---\ntitle: docker 镜像源收集\n---\n# docker 镜像源收集\n- https://docker.1ms.run/\n- https://dockermirror.com\n## 评论\n\n### 评论 1 - coolxy"
},
{
"path": "docs/issues/417.md",
"chars": 2205,
"preview": "---\ntitle: Jellyfin歌单同步到映射目录\n---\n# Jellyfin歌单同步到映射目录\n因为我一直是用jellyfin 听歌, 下载的都是在Pt网站 按照歌手几十张cd一起下载,所以有非常多不想听的歌,于是歌单功能非常重要"
},
{
"path": "docs/issues/520.md",
"chars": 376,
"preview": "---\ntitle: 局域网 ip 问题,排查了两个小时。希望能对大家有所帮助\n---\n# 局域网 ip 问题,排查了两个小时。希望能对大家有所帮助\n家里使用了两个路由器 \n\n- A 路由器 192.168.1.x\n- B 路由器 192."
},
{
"path": "docs/issues/533.md",
"chars": 6094,
"preview": "---\ntitle: 反向代理因为html中绝对路径导致超链接失效\n---\n# 反向代理因为html中绝对路径导致超链接失效\n反向代理因为html中绝对路径导致超链接失效\n## 评论\n\n### 评论 1 - wuxinyumrx\n更新一下解"
},
{
"path": "docs/issues/595.md",
"chars": 280,
"preview": "---\ntitle: Docker 镜像推荐\n---\n# Docker 镜像推荐\n监控网站:https://status.anye.xyz/\n\n<img width=\"1498\" height=\"958\" alt=\"Image\" src=\""
},
{
"path": "docs/issues/600.md",
"chars": 752,
"preview": "---\ntitle: 1Panel 安装运行 xiaomusic 教程\n---\n# 1Panel 安装运行 xiaomusic 教程\n## 1Panel 安装运行\n\n本教程将指导您如何在 1Panel 面板中安装和配置 XiaoMusic。"
},
{
"path": "docs/issues/637.md",
"chars": 5086,
"preview": "---\ntitle: Jellyfin 插件将音乐歌单转成支持xiaomusic的网络歌单\n---\n# Jellyfin 插件将音乐歌单转成支持xiaomusic的网络歌单\nJellyfin插件安装仓库地址:https://raw.gith"
},
{
"path": "docs/issues/688.md",
"chars": 16827,
"preview": "---\ntitle: 使用cookie登陆\n---\n# 使用cookie登陆\n> 使用 cookie 登录时不要写填账号密码\n\n## 步骤\n\n1. 在电脑上使用 Chrome 浏览器登录小米账号官网 👉 <https://account.x"
},
{
"path": "docs/issues/764.md",
"chars": 441,
"preview": "---\ntitle: 酷狗歌单导出工具,可快速导入 xiaomusic\n---\n# 酷狗歌单导出工具,可快速导入 xiaomusic\n开发了一个酷狗音乐歌单导出工具,可以将酷狗音乐的歌单导出为 xiaomusic 兼容的 JSON 格式。\n"
},
{
"path": "docs/issues/767.md",
"chars": 296,
"preview": "---\ntitle: 自己用的Android TV上连接xiaomusic的播放软件\n---\n# 自己用的Android TV上连接xiaomusic的播放软件\n一直没找到TV上用的播放软件,自己整了一个,分享给大家\n[https://gi"
},
{
"path": "docs/issues/78.md",
"chars": 26153,
"preview": "---\ntitle: 已支持配置自定义网络歌单,在这里分享你的歌单\n---\n# 已支持配置自定义网络歌单,在这里分享你的歌单\n设置页面新增一个输入框配置json格式,可以定义配置音乐源,可以是电台或者其他的m3u8格式的。\n再加一个输入框配"
},
{
"path": "docs/issues/86.md",
"chars": 753,
"preview": "---\ntitle: 微信交流群二维码\n---\n# 微信交流群二维码\n<img width=\"1031\" height=\"1440\" alt=\"Image\" src=\"https://gproxy.hanxi.cc/proxy/user-a"
},
{
"path": "docs/issues/88.md",
"chars": 1773,
"preview": "---\ntitle: 如何添加m3u格式文件的电台\n---\n# 如何添加m3u格式文件的电台\n比如可以找到这样的 m3u 电台文件: https://github.com/YueChan/Live/blob/main/Radio.m3u\r\n"
},
{
"path": "docs/issues/94.md",
"chars": 2297,
"preview": "---\ntitle: 采用config.json配置方式\n---\n# 采用config.json配置方式\ndocker 方式部署默认推荐使用环境变量的方式来配置参数,如果是自己用命令行启动,目前支持的参数配置比较少,但是是支持 `--con"
},
{
"path": "docs/issues/96.md",
"chars": 795,
"preview": "---\ntitle: ios系统上的捷径配置\n---\n# ios系统上的捷径配置\n下面是播放音乐和关机两个示例。只要在 web 页面上能看到的功能,都有对应的 http 请求接口,都可以用来配置捷径。\r\n\r\n![mmexport171976"
},
{
"path": "docs/issues/99.md",
"chars": 66749,
"preview": "---\ntitle: 💬 FAQ问题集合\n---\n# 💬 FAQ问题集合\n> [!NOTE]\n> 这个 issue 用来总结报错日志和对应的解决方法。\n\n## Login Failed 登陆失败\n\n表现就是 **后台看不到设备列表** ,日"
},
{
"path": "docs/issues/changelog.md",
"chars": 18941,
"preview": "# 版本日志\n\n## v0.4.26 (2026-03-20)\n\n### Fix\n\n- proxy handler CDN safeguard & content-type based FFmpeg routing (#791)\n\n## v"
},
{
"path": "docs/issues/index.md",
"chars": 11284,
"preview": "# XiaoMusic: 无限听歌,解放小爱音箱\n\n[](https://github.com/"
},
{
"path": "docs/package.json",
"chars": 313,
"preview": "{\n \"devDependencies\": {\n \"vitepress\": \"^1.6.4\"\n },\n \"scripts\": {\n \"docs:dev\": \"vitepress dev --host=0.0.0.0 --p"
},
{
"path": "get_release.py",
"chars": 1553,
"preview": "import json\nimport os\n\nimport requests\n\n# 替换为你的 GitHub 仓库信息\nGITHUB_OWNER = \"hanxi\"\nGITHUB_REPO = \"xiaomusic\"\nGITHUB_API_"
},
{
"path": "holiday/2007.json",
"chars": 3883,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2008.json",
"chars": 4221,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2009.json",
"chars": 4015,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2010.json",
"chars": 4422,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2011.json",
"chars": 4086,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2012.json",
"chars": 4123,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2013.json",
"chars": 4868,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2014.json",
"chars": 3311,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2015.json",
"chars": 3903,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2016.json",
"chars": 3646,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2017.json",
"chars": 3571,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2018.json",
"chars": 3537,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2019.json",
"chars": 3835,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2020.json",
"chars": 4541,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2021.json",
"chars": 4535,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2022.json",
"chars": 4535,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2023.json",
"chars": 4131,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2024.json",
"chars": 4312,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2025.json",
"chars": 4016,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2026.json",
"chars": 4639,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/2027.json",
"chars": 236,
"preview": "{\n \"$schema\": \"https://raw.githubusercontent.com/NateScarlet/holiday-cn/master/schema.json\",\n \"$id\": \"https://raw."
},
{
"path": "holiday/renovate.json",
"chars": 109,
"preview": "{\n \"extends\": [\n \"config:best-practices\",\n \":automergeMinor\",\n \":disableDependencyDashboard\"\n ]\n}\n"
},
{
"path": "holiday/schema.json",
"chars": 801,
"preview": "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": \"https://raw.githubusercontent.com/NateScarlet/holida"
},
{
"path": "install_dependencies.sh",
"chars": 1049,
"preview": "#!/bin/bash\n\n# yt-dlp 依赖 ffmpeg\n# https://github.com/yt-dlp/yt-dlp#dependencies\n\n# 判断系统架构\narch=$(uname -m)\n\n# 输出架构信息\nech"
},
{
"path": "newpatch.sh",
"chars": 221,
"preview": "#!/bin/bash\n\n./update-static-version.py\n./update-holiday.sh\ngit add xiaomusic/static\ngit commit -m 'build: update static"
},
{
"path": "newversion.sh",
"chars": 203,
"preview": "#!/bin/bash\n\n./update-static-version.py\n./update-holiday.sh\ngit add xiaomusic/static\ngit commit -m 'build: update static"
},
{
"path": "package.json",
"chars": 348,
"preview": "{\n \"name\": \"xiaomusic-js-plugins\",\n \"version\": \"1.0.0\",\n \"description\": \"JS plugins for xiaomusic\",\n \"main\": \"xiaomu"
},
{
"path": "plugins/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "plugins/code1.py",
"chars": 268,
"preview": "async def code1(arg1):\n global log, xiaomusic\n log.info(f\"code1:{arg1}\")\n did = xiaomusic.get_cur_did()\n awa"
},
{
"path": "plugins/httpget.py",
"chars": 242,
"preview": "import requests\n\n\ndef httpget(url):\n global log\n\n # 发起请求\n response = requests.get(url, timeout=5) # 增加超时以避免长时间"
},
{
"path": "plugins/httppost.py",
"chars": 356,
"preview": "import requests\n\ntarget = \"HTTP://192.168.1.10:58091/items/\"\n\n\ndef httppost(data, url=target):\n global log\n # 发起请求"
},
{
"path": "pyproject.toml",
"chars": 2142,
"preview": "[project]\nname = \"xiaomusic\"\nversion = \"0.4.26\"\ndescription = \"Play Music with xiaomi AI speaker\"\nauthors = [\n{name = \"涵"
},
{
"path": "test/test_difflib.py",
"chars": 802,
"preview": "import difflib\n\nfrom xiaomusic.utils.text_utils import find_best_match, keyword_detection\n\nif __name__ == \"__main__\":\n "
},
{
"path": "test/test_music_duration.py",
"chars": 763,
"preview": "import math\n\nfrom xiaomusic.const import SUPPORT_MUSIC_TYPE\nfrom xiaomusic.utils.file_utils import traverse_music_direct"
},
{
"path": "test/test_music_tags.py",
"chars": 1044,
"preview": "import traceback\n\nfrom xiaomusic.const import SUPPORT_MUSIC_TYPE\nfrom xiaomusic.utils.file_utils import traverse_music_d"
},
{
"path": "test/test_remove_common_prefix.py",
"chars": 746,
"preview": "import re\n\n\ndef removepre(filename):\n match = re.search(r\"^[pP]?(\\d+)\\s+\\d*(.+?)\\.(.*$)\", filename.strip())\n new_f"
},
{
"path": "test/test_update.py",
"chars": 309,
"preview": "from xiaomusic.utils.system_utils import download_and_extract\n\nif __name__ == \"__main__\":\n import asyncio\n\n url = "
},
{
"path": "update-holiday.sh",
"chars": 152,
"preview": "#!/bin/bash\n\nrm -rf holiday-cn\ngit clone https://github.com/NateScarlet/holiday-cn.git\nmkdir -p holiday\ncp holiday-cn/*."
},
{
"path": "update-static-version.py",
"chars": 1249,
"preview": "#!/usr/bin/env python3\n\nimport re\nfrom pathlib import Path\n\n\ndef get_html_files(directory):\n \"\"\"\n 获取指定目录下所有HTML文件的"
},
{
"path": "xiaomusic/__init__.py",
"chars": 23,
"preview": "__version__ = \"0.4.26\"\n"
},
{
"path": "xiaomusic/analytics.py",
"chars": 5022,
"preview": "import asyncio\nimport copy\nimport platform\nimport traceback\nfrom datetime import datetime\n\nimport aiohttp\nfrom ga4mp imp"
},
{
"path": "xiaomusic/api/__init__.py",
"chars": 106,
"preview": "\"\"\"API 模块统一入口\"\"\"\n\nfrom xiaomusic.api.app import (\n HttpInit,\n app,\n)\n\n__all__ = [\"app\", \"HttpInit\"]\n"
},
{
"path": "xiaomusic/api/app.py",
"chars": 2174,
"preview": "\"\"\"FastAPI 应用实例和中间件配置\"\"\"\n\nimport asyncio\nimport os\nfrom contextlib import asynccontextmanager\nfrom typing import TYPE_CH"
},
{
"path": "xiaomusic/api/dependencies.py",
"chars": 5022,
"preview": "\"\"\"依赖注入和认证相关功能\"\"\"\n\nimport hashlib\nimport secrets\nfrom typing import (\n TYPE_CHECKING,\n Annotated,\n)\n\nfrom fastapi "
},
{
"path": "xiaomusic/api/models.py",
"chars": 1120,
"preview": "\"\"\"Pydantic 数据模型定义\"\"\"\n\nfrom pydantic import BaseModel\n\n\nclass Did(BaseModel):\n did: str\n\n\nclass DidVolume(BaseModel):"
},
{
"path": "xiaomusic/api/routers/__init__.py",
"chars": 641,
"preview": "\"\"\"路由注册\"\"\"\n\nfrom xiaomusic.api import websocket\nfrom xiaomusic.api.routers import (\n device,\n file,\n music,\n "
},
{
"path": "xiaomusic/api/routers/device.py",
"chars": 3162,
"preview": "\"\"\"设备控制路由\"\"\"\n\nimport asyncio\nimport urllib.parse\n\nfrom fastapi import (\n APIRouter,\n Depends,\n)\n\nfrom xiaomusic.ap"
},
{
"path": "xiaomusic/api/routers/file.py",
"chars": 24265,
"preview": "import asyncio\nimport base64\nimport os\nimport shutil\nfrom urllib.parse import urlparse\n\nimport aiohttp\nfrom fastapi impo"
},
{
"path": "xiaomusic/api/routers/music.py",
"chars": 7667,
"preview": "\"\"\"音乐管理路由\"\"\"\n\nimport base64\nimport json\nimport urllib.parse\n\nfrom fastapi import (\n APIRouter,\n Depends,\n HTTPE"
},
{
"path": "xiaomusic/api/routers/playlist.py",
"chars": 3043,
"preview": "\"\"\"播放列表路由\"\"\"\n\nfrom fastapi import (\n APIRouter,\n Depends,\n)\n\nfrom xiaomusic.api.dependencies import (\n log,\n "
},
{
"path": "xiaomusic/api/routers/plugin.py",
"chars": 6604,
"preview": "\"\"\"插件管理路由\"\"\"\n\nimport os\n\nimport aiofiles\nfrom fastapi import (\n APIRouter,\n Depends,\n File,\n HTTPException,\n"
},
{
"path": "xiaomusic/api/routers/system.py",
"chars": 8359,
"preview": "\"\"\"系统管理路由\"\"\"\n\nimport asyncio\nimport base64\nimport io\nimport json\nimport os\nimport shutil\nimport tempfile\nfrom dataclasse"
},
{
"path": "xiaomusic/api/websocket.py",
"chars": 2608,
"preview": "\"\"\"WebSocket 相关功能\"\"\"\n\nimport asyncio\nimport json\nimport secrets\nimport time\n\nimport jwt\nfrom fastapi import (\n APIRou"
},
{
"path": "xiaomusic/auth.py",
"chars": 6716,
"preview": "\"\"\"认证管理模块\n\n本模块负责小米账号认证与会话管理,包括:\n- 小米账号登录\n- Cookie管理\n- 会话维护\n- 设备ID更新\n\"\"\"\n\nimport json\nimport os\n\nfrom aiohttp import Clie"
},
{
"path": "xiaomusic/cli.py",
"chars": 5934,
"preview": "#!/usr/bin/env python3\nimport argparse\nimport json\nimport logging\nimport os\nimport signal\n\nimport sentry_sdk\nfrom sentry"
},
{
"path": "xiaomusic/command_handler.py",
"chars": 4519,
"preview": "\"\"\"命令处理模块\n\n负责语音指令的解析、匹配和路由。\n\"\"\"\n\nimport asyncio\nimport re\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n from x"
},
{
"path": "xiaomusic/config.py",
"chars": 15422,
"preview": "from __future__ import annotations\n\nimport argparse\nimport base64\nimport json\nimport os\nfrom dataclasses import asdict, "
},
{
"path": "xiaomusic/config_manager.py",
"chars": 2424,
"preview": "\"\"\"配置管理模块\n\n负责配置的加载、保存、更新和管理。\n\"\"\"\n\nimport json\nfrom dataclasses import asdict\n\n\nclass ConfigManager:\n \"\"\"配置管理类\n\n 负责"
},
{
"path": "xiaomusic/const.py",
"chars": 1066,
"preview": "SUPPORT_MUSIC_TYPE = [\n \".mp3\",\n \".flac\",\n \".wav\",\n \".ape\",\n \".ogg\",\n \".m4a\",\n \".wma\",\n]\n\nLATEST_AS"
},
{
"path": "xiaomusic/conversation.py",
"chars": 9754,
"preview": "\"\"\"对话记录拉取模块\n\n本模块负责从小爱音箱拉取对话记录,包括:\n- 轮询最新对话记录\n- 从小爱API获取对话\n- 通过Mina服务获取对话\n- 解析和验证对话记录\n\"\"\"\n\nimport asyncio\nimport json\nimp"
},
{
"path": "xiaomusic/crontab.py",
"chars": 7400,
"preview": "import json\n\nfrom apscheduler.schedulers.asyncio import AsyncIOScheduler\nfrom apscheduler.triggers.base import BaseTrigg"
},
{
"path": "xiaomusic/device_manager.py",
"chars": 3829,
"preview": "\"\"\"设备管理模块\n\n本模块负责小米音箱设备的管理,包括:\n- 设备列表管理\n- 设备分组管理\n- 设备信息查询\n\"\"\"\n\nfrom typing import TYPE_CHECKING, Optional\n\nfrom xiaomusic"
},
{
"path": "xiaomusic/device_player.py",
"chars": 33356,
"preview": "\"\"\"设备播放控制模块\n\n负责单个设备的播放控制、下载管理、TTS处理等功能。\n\"\"\"\n\nimport asyncio\nimport copy\nimport json\nimport os\nimport random\nimport time\n"
},
{
"path": "xiaomusic/events.py",
"chars": 1548,
"preview": "\"\"\"事件系统模块\n\n提供简单的事件发布-订阅机制,用于模块间的解耦通信。\n\"\"\"\n\nfrom collections.abc import Callable\n\n# 事件类型常量\nCONFIG_CHANGED = \"config_chang"
},
{
"path": "xiaomusic/file_watcher.py",
"chars": 3617,
"preview": "\"\"\"文件监控模块\n\n提供音乐目录的文件变化监控功能,支持防抖延迟处理。\n\"\"\"\n\nimport os\n\nfrom watchdog.events import (\n FileCreatedEvent,\n FileDeleted"
},
{
"path": "xiaomusic/holiday.py",
"chars": 1823,
"preview": "import json\nimport logging\nimport os\nfrom datetime import date\n\nlog = logging.getLogger(__package__)\n\n# 用于存储已加载的年份数据\nloa"
},
{
"path": "xiaomusic/js_adapter.py",
"chars": 7853,
"preview": "#!/usr/bin/env python3\n\"\"\"\nJS 插件适配器\n将 MusicFree JS 插件的数据格式转换为 xiaomusic 接口规范\n\"\"\"\n\nimport logging\n\n\nclass JSAdapter:\n "
},
{
"path": "xiaomusic/js_plugin_manager.py",
"chars": 53487,
"preview": "#!/usr/bin/env python3\n\"\"\"\nJS 插件管理器\n负责加载、管理和运行 MusicFree JS 插件\n\"\"\"\n\nimport json\nimport logging\nimport os\nimport shutil\ni"
},
{
"path": "xiaomusic/js_plugin_runner.js",
"chars": 24632,
"preview": "#!/usr/bin/env node\n\n/**\n * JS 插件运行器\n * 在安全的沙箱环境中运行 MusicFree JS 插件\n */\n\nconst vm = require('vm');\nconst fs = require('f"
},
{
"path": "xiaomusic/music_library.py",
"chars": 36038,
"preview": "\"\"\"音乐库管理模块\n\n负责音乐库的管理、播放列表操作、音乐搜索和标签管理。\n\"\"\"\n\nimport asyncio\nimport base64\nimport copy\nimport json\nimport os\nimport random"
},
{
"path": "xiaomusic/online_music.py",
"chars": 30348,
"preview": "\"\"\"在线音乐服务模块\n\n负责MusicFree插件集成、在线音乐搜索和播放链接获取。\n\"\"\"\n\nimport asyncio\nimport base64\nimport ipaddress\nimport json\nimport socket"
},
{
"path": "xiaomusic/plugin.py",
"chars": 2360,
"preview": "import importlib\nimport inspect\nimport pkgutil\nfrom typing import TYPE_CHECKING\n\nif TYPE_CHECKING:\n from xiaomusic.xi"
},
{
"path": "xiaomusic/plugins-config-example.json",
"chars": 153,
"preview": "{\n \"account\": \"\",\n \"password\": \"\",\n \"openapi_info\": {\n \"search_url\": \"\",\n \"enabled\": false\n },\n \"enabled_plug"
},
{
"path": "xiaomusic/qrcode_login.py",
"chars": 16443,
"preview": "import base64\nimport hashlib\nimport json\nimport locale\nimport os\nimport random\nimport time\nfrom datetime import datetime"
},
{
"path": "xiaomusic/static/default/debug.html",
"chars": 2130,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"/favicon.ico\">\n <m"
},
{
"path": "xiaomusic/static/default/downloadtool.html",
"chars": 2722,
"preview": "<!DOCTYPE html>\n<html lang=\"zh\">\n\t<head>\n\t\t<meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width\""
},
{
"path": "xiaomusic/static/default/index.html",
"chars": 12289,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "xiaomusic/static/default/m3u.html",
"chars": 2484,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"/favicon.ico\">\n <m"
},
{
"path": "xiaomusic/static/default/main.css",
"chars": 21839,
"preview": "/* ==================== CSS 变量系统 ==================== */\n:root {\n /* 主色调 - 与 setting.html 保持一致 */\n --primary-color: #0"
},
{
"path": "xiaomusic/static/default/md.js",
"chars": 46877,
"preview": "// ============ 字体加载检测 ============\n// 检测字体加载完成,避免图标文字闪烁\n(function () {\n // 使用 Promise.race 实现超时保护\n const fontLoadTime"
},
{
"path": "xiaomusic/static/default/merge/index.html",
"chars": 311,
"preview": "<!DOCTYPE html>\n<html lang=\"zh\">\n<head>\n <meta charset=\"UTF-8\">\n <title>歌单导出工具</title>\n <meta name=\"viewport\" content"
},
{
"path": "xiaomusic/static/default/merge/main.js",
"chars": 375344,
"preview": "var xF=Object.create;var{getPrototypeOf:kF,defineProperty:Nz,getOwnPropertyNames:vF}=Object;var bF=Object.prototype.hasO"
},
{
"path": "xiaomusic/static/default/merge/tailwind.css",
"chars": 10206,
"preview": "*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--t"
},
{
"path": "xiaomusic/static/default/setting.css",
"chars": 21367,
"preview": "/* ==================== Material Icons 字体定义 ==================== */\n/* fallback */\n@font-face {\n font-family: 'Material"
},
{
"path": "xiaomusic/static/default/setting.html",
"chars": 32634,
"preview": "<!doctype html>\n<html lang=\"zh-CN\">\n\n<head>\n <link rel=\"icon\" href=\"/favicon.ico\" />\n <meta name=\"viewport\" conten"
},
{
"path": "xiaomusic/static/default/setting.js",
"chars": 17558,
"preview": "// 获取二维码的函数(点击「获取二维码」后再请求并显示)\nlet qrcodeCountdownTimer = null;\nconst DEFAULT_QRCODE_EXPIRE_SECONDS = 120;\n\nfunction stop"
},
{
"path": "xiaomusic/static/index.html",
"chars": 10139,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n\n<head>\n <meta charset=\"UTF-8\" />\n <link rel=\"manifest\" href=\"/static/manifest.jso"
},
{
"path": "xiaomusic/static/iwebplayer/iwebplayer.html",
"chars": 140789,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "xiaomusic/static/manifest.json",
"chars": 12519,
"preview": "{\n \"lang\": \"zh\",\n \"name\": \"小爱音箱播放器\",\n \"scope\": \"/\",\n \"display\": \"standalone\",\n \"start_url\": \"/\",\n \"short_name\": \"小"
},
{
"path": "xiaomusic/static/onlineSearch/config.js",
"chars": 93,
"preview": "// config.js\nwindow.appConfig = {\n // TODO 版本号\n version: \"1.0.6\",\n // 其他配置项可继续添加\n};\n"
},
{
"path": "xiaomusic/static/onlineSearch/index.html",
"chars": 43469,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"./favicon.ico\">\n <me"
},
{
"path": "xiaomusic/static/onlineSearch/setting.html",
"chars": 28388,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"./favicon.ico\">\n <me"
},
{
"path": "xiaomusic/static/pure/assets/DownloadTool-BWMSO0_N.css",
"chars": 977,
"preview": ".el-divider{position:relative}.el-divider--horizontal{border-top:1px var(--el-border-color) var(--el-border-style);displ"
},
{
"path": "xiaomusic/static/pure/assets/DownloadTool-bty5M9I6.js",
"chars": 3645,
"preview": "import{k as C,l as P,m as E,u as T,n as k,o as y,c as _,p as V,q as c,s as q,v as R,x as z,y as B,z as F,_ as L,A as f,b"
},
{
"path": "xiaomusic/static/pure/assets/M3u2Json-ButJ7G_D.css",
"chars": 631,
"preview": ".converter-container[data-v-ae3a051e]{display:flex;flex-direction:column;align-items:center;padding:0 20px}.converter-ca"
},
{
"path": "xiaomusic/static/pure/assets/M3u2Json-DeAtFyPF.js",
"chars": 2756,
"preview": "import{_ as U,r as i,c as w,a as o,w as l,E as O,o as J,b as _,d as p,t as x,e as r,f as C,g as M,h as S,i as V,j as k}f"
},
{
"path": "xiaomusic/static/pure/assets/index-BAPaOAUA.js",
"chars": 500948,
"preview": "const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[\"assets/M3u2Json-DeAtFyPF.js\",\"assets/M3u2Json-ButJ7G_D.css\",\"a"
},
{
"path": "xiaomusic/static/pure/assets/index-CfMOqlRg.css",
"chars": 194112,
"preview": "@charset \"UTF-8\";html.dark{color-scheme:dark;--el-color-primary:#409eff;--el-color-primary-light-3:#3375b9;--el-color-pr"
},
{
"path": "xiaomusic/static/pure/index.html",
"chars": 771,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"/static/pure/favicon.ico\">\n "
},
{
"path": "xiaomusic/static/soundSpace/assets/features-animation-DOC4MC0a.js",
"chars": 55853,
"preview": "import{n as _,T as Rt,U as It,a as Q,N as Ot,f as D,V as Ln,h as R,O as _n,W as Ie,X as Kt,b as G,p as be,Y as kn,d as B"
},
{
"path": "xiaomusic/static/soundSpace/assets/index-KGjtlaO8.js",
"chars": 70421,
"preview": "import{r as g,u as ht,f as S,L as _t,j as K,n as O,a as C,c as B,b as Rt,i as pt,p as Dt,d as hn,e as hi,S as fi,g as di"
},
{
"path": "xiaomusic/static/soundSpace/assets/index-ckWJnWZz.js",
"chars": 111,
"preview": "import{O as a}from\"./features-animation-DOC4MC0a.js\";import\"./index-fie2kaim.js\";var i=a;export{i as default};\n"
},
{
"path": "xiaomusic/static/soundSpace/assets/index-fie2kaim.js",
"chars": 921529,
"preview": "const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[\"assets/index-ckWJnWZz.js\",\"assets/features-animation-DOC4MC0a."
},
{
"path": "xiaomusic/static/soundSpace/assets/index-qfFWjqIn.css",
"chars": 249970,
"preview": "/*! tailwindcss v4.1.16 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) "
},
{
"path": "xiaomusic/static/soundSpace/assets/src-UW24ZMRV-DgU5LBZm.js",
"chars": 111,
"preview": "import{O as a}from\"./features-animation-DOC4MC0a.js\";import\"./index-fie2kaim.js\";var t=a;export{t as default};\n"
},
{
"path": "xiaomusic/static/soundSpace/index.html",
"chars": 511,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
},
{
"path": "xiaomusic/static/sw.js",
"chars": 929,
"preview": "'use strict'\nvar cacheStorageKey = 'xiaomusic-key';\nlet cacheName = 'xiaomusic-cache'; // 缓存名字\n\nvar cacheList = [ // 所需缓"
},
{
"path": "xiaomusic/static/tailwind/api.js",
"chars": 5120,
"preview": "// API 基础配置\nconst API = {\n // 获取音乐列表\n async getMusicList() {\n const response = await fetch('/musiclist');\n "
},
{
"path": "xiaomusic/static/tailwind/debug.html",
"chars": 2130,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"/favicon.ico\">\n <m"
},
{
"path": "xiaomusic/static/tailwind/downloadtool.html",
"chars": 7924,
"preview": "<!DOCTYPE html>\n<html lang=\"zh\">\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width\">\n\t<"
},
{
"path": "xiaomusic/static/tailwind/index.html",
"chars": 75536,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content"
},
{
"path": "xiaomusic/static/tailwind/libs/daisyui@4.12.23.css",
"chars": 2931184,
"preview": "/**\n * Minified by jsDelivr using clean-css v5.3.3.\n * Original file: /npm/daisyui@4.12.23/dist/full.css\n *\n * Do NOT us"
},
{
"path": "xiaomusic/static/tailwind/libs/jquery-3.6.0.js",
"chars": 89501,
"preview": "/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */\n!function(e,t){\"use strict\";\"ob"
},
{
"path": "xiaomusic/static/tailwind/libs/tailwind.js",
"chars": 407362,
"preview": "(()=>{var qv=Object.create;var Hi=Object.defineProperty;var $v=Object.getOwnPropertyDescriptor;var Lv=Object.getOwnPrope"
},
{
"path": "xiaomusic/static/tailwind/libs/vue@3.5.13.js",
"chars": 157944,
"preview": "/**\n* vue v3.5.13\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**/var Vue=function(e){\"use str"
},
{
"path": "xiaomusic/static/tailwind/m3u.html",
"chars": 2484,
"preview": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"/favicon.ico\">\n <m"
},
{
"path": "xiaomusic/static/tailwind/main.css",
"chars": 2908,
"preview": "/* 自定义滚动条样式 */\r\n::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n::-webkit-scrollbar-track {\r\n background: #f1f1f1;\r\n border-r"
},
{
"path": "xiaomusic/static/tailwind/md.js",
"chars": 42137,
"preview": "// $(function () {\r\n\r\n// })\r\nlet isPlaying = false;\r\nlet playModeIndex = 2;\r\n//重新设计playModes\r\nconst playModes = {\r\n 0: "
},
{
"path": "xiaomusic/static/tailwind/now_playing.html",
"chars": 24021,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"dark\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content="
},
{
"path": "xiaomusic/static/tailwind/now_playing.js",
"chars": 17367,
"preview": "const { createApp, ref, computed, onMounted, watch, onUnmounted } = Vue\n\ncreateApp({\n setup() {\n const currentSong ="
},
{
"path": "xiaomusic/static/tailwind/setting.html",
"chars": 35636,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <link rel=\"icon\" href=\"/favicon.ico\">\n <meta name=\"viewport\" content=\"width=device-wid"
},
{
"path": "xiaomusic/static/tailwind/setting.js",
"chars": 5699,
"preview": "$(function () {\n // 拉取版本\n $.get(\"/getversion\", function (data, status) {\n console.log(data, status, data[\"version\"]"
},
{
"path": "xiaomusic/static/tailwind/tailwind.js",
"chars": 407362,
"preview": "(()=>{var qv=Object.create;var Hi=Object.defineProperty;var $v=Object.getOwnPropertyDescriptor;var Lv=Object.getOwnPrope"
},
{
"path": "xiaomusic/static/tailwind/theme.js",
"chars": 556,
"preview": "// 初始化主题\nfunction initTheme() {\n const theme = localStorage.getItem('theme');\n if (theme === 'dark' || (!theme && wind"
},
{
"path": "xiaomusic/static/weapp/qrcode.html",
"chars": 1137,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "xiaomusic/static/xplayer/assets/index-2Kb1oK2G.css",
"chars": 69634,
"preview": "@charset \"UTF-8\";:where(body){--background-color: #fff;--text-color: #262338}body{background-color:var(--background-colo"
},
{
"path": "xiaomusic/static/xplayer/assets/index-ESKkJcHu.js",
"chars": 269609,
"preview": "(function(){const t=document.createElement(\"link\").relList;if(t&&t.supports&&t.supports(\"modulepreload\"))return;for(cons"
},
{
"path": "xiaomusic/static/xplayer/index.html",
"chars": 479,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <link rel=\"icon\" href=\"/static/xplayer/favicon."
},
{
"path": "xiaomusic/utils/__init__.py",
"chars": 161,
"preview": "#!/usr/bin/env python3\n\"\"\"\n工具函数模块\n\n子模块分类:\n- text_utils: 文本处理和搜索\n- file_utils: 文件和目录操作\n- music_utils: 音乐文件处理\n- network_ut"
},
{
"path": "xiaomusic/utils/file_utils.py",
"chars": 5824,
"preview": "#!/usr/bin/env python3\n\"\"\"文件和目录操作相关工具函数\"\"\"\n\nimport logging\nimport os\nimport re\nimport shutil\n\nlog = logging.getLogger(__"
},
{
"path": "xiaomusic/utils/music_utils.py",
"chars": 20403,
"preview": "#!/usr/bin/env python3\n\"\"\"音乐文件处理相关工具函数\"\"\"\n\nimport asyncio\nimport base64\nimport hashlib\nimport io\nimport json\nimport logg"
},
{
"path": "xiaomusic/utils/network_utils.py",
"chars": 13140,
"preview": "#!/usr/bin/env python3\n\"\"\"网络请求和下载相关工具函数\"\"\"\n\nimport asyncio\nimport hashlib\nimport logging\nimport os\nimport time\nfrom coll"
},
{
"path": "xiaomusic/utils/openai_utils.py",
"chars": 8395,
"preview": "#!/usr/bin/env python3\n\"\"\"用于AI大模型调用的工具类\"\"\"\n\nimport asyncio\nimport json\nimport logging\nfrom typing import Any\n\nimport aio"
},
{
"path": "xiaomusic/utils/system_utils.py",
"chars": 7550,
"preview": "#!/usr/bin/env python3\n\"\"\"系统操作和环境相关工具函数\"\"\"\n\nimport asyncio\nimport copy\nimport hashlib\nimport logging\nimport os\nimport pl"
},
{
"path": "xiaomusic/utils/text_utils.py",
"chars": 6170,
"preview": "#!/usr/bin/env python3\n\"\"\"文本处理和搜索相关工具函数\"\"\"\n\nimport difflib\nimport re\nfrom collections.abc import AsyncIterator\n\nfrom ope"
},
{
"path": "xiaomusic/xiaomusic.py",
"chars": 22641,
"preview": "#!/usr/bin/env python3\nimport asyncio\nimport logging\nimport os\nimport re\nfrom logging.handlers import RotatingFileHandle"
},
{
"path": "xiaomusic.py",
"chars": 98,
"preview": "#!/usr/bin/env python3\n\nif __name__ == \"__main__\":\n from xiaomusic.cli import main\n\n main()\n"
}
]
About this extraction
This page contains the full source code of the hanxi/xiaomusic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 195 files (7.8 MB), approximately 2.1M tokens, and a symbol index with 8105 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.