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

Compare commits

...

54 Commits

Author SHA1 Message Date
涵曦
82aa453e50 bump: version 0.3.8 → 0.3.9 2024-07-17 10:59:13 +00:00
涵曦
1718211619 build: update static version 2024-07-17 10:59:13 +00:00
涵曦
09310675fc feat: #119 音乐目录支持软连接 2024-07-17 09:57:24 +00:00
涵曦
ca711bbdb8 fix: 修复日志下载报错问题 2024-07-17 06:05:15 +00:00
涵曦
fb44f88df2 fix: 兼容旧的setting.json文件中conf_path为空的情况 2024-07-17 06:05:15 +00:00
涵曦
e5b32b2831 fix: 修复设置页面可能打不开的问题 2024-07-17 06:05:15 +00:00
涵曦
19ddbb7ca9 Update README.md 2024-07-17 00:32:42 +08:00
涵曦
3e82d7acdc build: 默认映射目录设置 2024-07-16 16:25:01 +00:00
涵曦
3921c70c86 bump: version 0.3.7 → 0.3.8 2024-07-16 13:58:04 +00:00
涵曦
aea9333e57 build: update static version 2024-07-16 13:58:03 +00:00
涵曦
7043ca31cf fix: 修复播放url接口问题 2024-07-16 13:57:37 +00:00
涵曦
963d86de7c bump: version 0.3.6 → 0.3.7 2024-07-16 09:38:51 +00:00
涵曦
d6b0e974b7 build: update static version 2024-07-16 09:38:50 +00:00
涵曦
8a8340a159 feat: 播放链接按钮对应给个默认的链接用于测试 2024-07-16 09:38:36 +00:00
涵曦
20d3c9fce9 feat: Uvicorn 的日志信息合并到 xiaomusic 日志里显示 2024-07-16 05:01:50 +00:00
涵曦
729549a7a9 bump: version 0.3.5 → 0.3.6 2024-07-15 17:22:30 +00:00
涵曦
d28614177c build: update static version 2024-07-15 17:22:30 +00:00
涵曦
db53517784 fix: #126 修复pip安装时主页打不开的问题 2024-07-15 17:22:24 +00:00
涵曦
186e9c1417 bump: version 0.3.4 → 0.3.5 2024-07-15 17:17:26 +00:00
涵曦
7498016d61 build: update static version 2024-07-15 17:17:26 +00:00
涵曦
7114ea2e6e fix: #116 播放失败自动切下首歌 2024-07-15 17:17:08 +00:00
涵曦
e8ca1f8678 bump: version 0.3.3 → 0.3.4 2024-07-15 16:46:06 +00:00
涵曦
2275bc2600 build: update static version 2024-07-15 16:46:06 +00:00
涵曦
45a94f4bfe fix: #125 修复本地英文歌曲匹大小写字母配不到的问题 2024-07-15 16:45:27 +00:00
涵曦
5cb2c84715 build: update code 2024-07-15 15:40:16 +00:00
涵曦
a3bf8d8aaa build: 静态文件版本自动更新脚本修改 2024-07-15 15:37:45 +00:00
涵曦
0a88b7be26 build: update static version 2024-07-15 15:24:26 +00:00
涵曦
05da3e8fc4 bump: version 0.3.2 → 0.3.3 2024-07-15 15:19:40 +00:00
涵曦
560ea1aeca fix: 尝试修复播放卡顿问题 see #124 2024-07-15 15:16:38 +00:00
涵曦
5ef1f2d940 ci: 发版本自动更新静态文件版本号 2024-07-15 14:53:25 +00:00
涵曦
e01fcdcecd bump: version 0.3.1 → 0.3.2 2024-07-15 14:51:09 +00:00
涵曦
8ff04dd0f6 fix: #122 pip安装方式下,static目录找不到报错 2024-07-15 14:44:57 +00:00
涵曦
4a7b5ac2b0 fix: 版本更新时更新页面缓存 2024-07-15 07:07:16 +00:00
涵曦
ac74ef3c15 Update README.md 2024-07-15 10:15:35 +08:00
涵曦
8913057c27 bump: version 0.3.0 → 0.3.1 2024-07-15 00:59:55 +00:00
涵曦
a75030a73c fix: 修复主页选择设备不生效的问题 see #120 2024-07-15 00:58:59 +00:00
涵曦
e2e74f9459 Update README.md 2024-07-14 23:14:16 +08:00
涵曦
09febb66dc bump: version 0.2.0 → 0.3.0 2024-07-14 14:00:05 +00:00
涵曦
ad0db8c9a9 Update README.md 2024-07-14 21:49:01 +08:00
涵曦
90b0fecd3b feat: 建议音乐目录和配置目录分开不同目录 2024-07-14 13:46:22 +00:00
涵曦
a8f88e8bfc fix: #114 修复部分 mp3 文件长度识别错误 2024-07-14 13:20:38 +00:00
涵曦
d8035f0713 fix: 删除 armv6 的支持 2024-07-14 12:48:05 +00:00
涵曦
76c1a8952f fix: 修复编译问题 2024-07-14 12:38:48 +00:00
涵曦
d8a66ca152 fix: 修复音乐路径设置后找不到音乐的问题 2024-07-14 09:52:14 +00:00
涵曦
dd77176035 fix: 修复启动报错的问题 2024-07-14 08:39:16 +00:00
涵曦
6d7d90d642 feat: 优化后台网络设置,同时支持ipv4和ipv6 2024-07-14 07:24:23 +00:00
涵曦
514bf9b8b2 Update README.md 2024-07-13 12:28:48 +08:00
涵曦
cd5869ff84 feat: 使用fastapi替换flask,解决多线程问题 2024-07-13 01:56:40 +00:00
涵曦
ee6f4ee4e4 feat: #106 网页上显示音箱当前状态(播放中or空闲中)以及当前的播放模式 2024-07-11 11:09:12 +00:00
涵曦
0189a00155 feat: 优化首页加载慢的问题 2024-07-11 10:34:23 +00:00
涵曦
e3d60d3f2e fix: 修复CI警告问题 2024-07-11 04:03:27 +00:00
涵曦
27fecd788b feat: 优化设置页面布局,方便配置必须项 2024-07-11 03:54:25 +00:00
涵曦
e9b1d94fb3 feat: 优化配置界面,支持配置分组 2024-07-11 03:40:10 +00:00
涵曦
043a9303a5 feat: 支持多设备分开播放 see #65 2024-07-11 03:05:56 +00:00
26 changed files with 2426 additions and 1151 deletions

View File

@@ -2,7 +2,8 @@ name: ci
on:
push:
branches: [ main ]
branches:
- "*"
workflow_dispatch:
jobs:
@@ -25,7 +26,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}
- name: Docker Hub Description

View File

@@ -61,6 +61,6 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:latest, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:stable

View File

@@ -1,3 +1,87 @@
## 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

View File

@@ -2,7 +2,7 @@ FROM python:3.10 AS builder
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /app
COPY requirements.txt .
RUN python3 -m venv .venv && .venv/bin/pip install --no-cache-dir -r requirements.txt
RUN python3 -m venv .venv && .venv/bin/pip install --upgrade pip && .venv/bin/pip install --no-cache-dir -r requirements.txt
COPY install_dependencies.sh .
RUN bash install_dependencies.sh
@@ -16,7 +16,8 @@ COPY xiaomusic.py .
ENV XDG_CONFIG_HOME=/config
ENV XIAOMUSIC_HOSTNAME=192.168.2.5
ENV XIAOMUSIC_PORT=8090
VOLUME /config
VOLUME /app/conf
VOLUME /app/music
EXPOSE 8090
ENV PATH=/app/.venv/bin:$PATH
ENTRYPOINT [".venv/bin/python3","xiaomusic.py"]

View File

@@ -29,6 +29,7 @@ services:
- 8090:8090
volumes:
- ./music:/app/music
- ./conf:/app/conf
```
对应的 docker 启动命令如下:
@@ -36,9 +37,12 @@ services:
```yaml
docker run -p 8090:8090 \
-v ./music:/app/music \
-v ./conf:/app/conf
hanxi/xiaomusic
```
其中 conf 目录为配置文件存放目录music 目录为音乐存放目录,建议分开配置为不同的目录。
启动成功后,在 web 页面可以配置其他参数,带有 `*` 号的配置是必须要配置的,其他的用不上时不用修改。初次配置时需要在页面上输入小米账号和密码保存后才能获取到设备列表。
### ✨✨✨ 修改默认8090端口映射 ✨✨✨
@@ -116,6 +120,8 @@ export XIAOMUSIC_SEARCH='bilisearch:'
pdm run xiaomusic.py
````
如果是开发前端界面,可以通过 <http://localhost:8090/docs> 查看有什么接口。
### 支持口令
- **播放歌曲**
@@ -146,11 +152,13 @@ pdm run xiaomusic.py
| 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) |
| LX04 X10A X08A | 已经支持的触屏版 |
型号与产品名称对照可以在这里查询 <https://home.miot-spec.com/s/xiaomi.wifispeaker>
> 如果你的设备支持播放,请反馈给我添加到支持列表里,谢谢。
> 目前应该所有设备类型都已经支持播放,有问题随时反馈。
> 其他触屏版不能播放可以设置 XIAOMUSIC_USE_MUSIC_API 为 true 试试。
## 支持音乐格式
@@ -314,9 +322,9 @@ services:
- XIAOMUSIC_DISABLE_HTTPAUTH 配置成 false 表示开启密码访问web控制台具体见 <https://github.com/hanxi/xiaomusic/issues/47>
- XIAOMUSIC_HTTPAUTH_USERNAME 配置 web 控制台用户
- XIAOMUSIC_HTTPAUTH_PASSWORD 配置 web 控制台密码
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认情况会把配置存放在music目录,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
- XIAOMUSIC_CONF_PATH 用来存放配置文件的目录,记得把目录映射到主机,默认`/app/config` ,具体见 <https://github.com/hanxi/xiaomusic/issues/74>
- XIAOMUSIC_DISABLE_DOWNLOAD 设为 true 时关闭下载功能,见 <https://github.com/hanxi/xiaomusic/issues/82>
- ~XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,目前应该所有型号都已经兼容,可以不用设置这个~。
- XIAOMUSIC_USE_MUSIC_API 设为 true 时使用 player_play_music 接口播放音乐,用于兼容不能播放的型号,如果发现需要设置这个选项的时候请告知我加一下设备型号,方便以后不用设置。 见 <https://github.com/hanxi/xiaomusic/issues/30>
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。

View File

@@ -1,81 +1,81 @@
{
"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\")"
}
"account": "",
"password": "",
"mi_did": "",
"cookie": "",
"verbose": false,
"music_path": "music",
"download_path": "",
"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,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": "",
"disable_download": false,
"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": [
"分钟后关机",
"播放歌曲",
"下一首",
"单曲循环",
"全部循环",
"随机播放",
"关机",
"刷新列表",
"播放列表",
"播放本地歌曲",
"本地播放歌曲",
"放歌曲",
"暂停",
"停止",
"停止播放",
"测试自定义口令",
"测试链接"
],
"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": true,
"keywords_playlocal": "播放本地歌曲,本地播放歌曲",
"keywords_play": "播放歌曲,放歌曲",
"keywords_stop": "关机,暂停,停止,停止播放",
"user_key_word_dict": {
"测试自定义口令": "exec#code1(\"hello\")",
"测试链接": "exec#httpget(\"https://github.com/hanxi/xiaomusic\")"
},
"enable_force_stop": false,
"devices": {},
"group_list": ""
}

9
newpatch.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
./update-static-version.py
git add xiaomusic/static
git commit -m 'build: update static version'
cz bump --check-consistency --increment patch
git push -u origin main --tags

View File

@@ -1,5 +1,9 @@
#!/bin/bash
cz bump --check-consistency --increment patch
./update-static-version.py
git add xiaomusic/static
git commit -m 'build: update static version'
cz bump --check-consistency
git push -u origin main --tags

622
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default", "lint", "dev"]
strategy = ["cross_platform"]
lock_version = "4.4.2"
content_hash = "sha256:eb54172c14fe68ae15ebe610f91827d981b225e4b8c44cf1fa7de1521d6f7453"
content_hash = "sha256:fe8f356d82d38641f48975833c27c352829a895c3c3293c36e0e010a3202966e"
[[package]]
name = "aiohttp"
@@ -82,6 +82,32 @@ files = [
{file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
]
[[package]]
name = "annotated-types"
version = "0.7.0"
requires_python = ">=3.8"
summary = "Reusable constraint types to use with typing.Annotated"
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
[[package]]
name = "anyio"
version = "4.4.0"
requires_python = ">=3.8"
summary = "High level compatibility layer for multiple asynchronous event loop implementations"
dependencies = [
"exceptiongroup>=1.0.2; python_version < \"3.11\"",
"idna>=2.8",
"sniffio>=1.1",
"typing-extensions>=4.1; python_version < \"3.11\"",
]
files = [
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
]
[[package]]
name = "argcomplete"
version = "3.3.0"
@@ -92,19 +118,6 @@ files = [
{file = "argcomplete-3.3.0.tar.gz", hash = "sha256:fd03ff4a5b9e6580569d34b273f741e85cd9e072f3feeeee3eba4891c70eda62"},
]
[[package]]
name = "asgiref"
version = "3.7.2"
requires_python = ">=3.7"
summary = "ASGI specs, helper code, and adapters"
dependencies = [
"typing-extensions>=4; python_version < \"3.11\"",
]
files = [
{file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
{file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
]
[[package]]
name = "async-timeout"
version = "4.0.3"
@@ -125,16 +138,6 @@ files = [
{file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
]
[[package]]
name = "blinker"
version = "1.7.0"
requires_python = ">=3.8"
summary = "Fast, simple object-to-object and broadcast signaling"
files = [
{file = "blinker-1.7.0-py3-none-any.whl", hash = "sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9"},
{file = "blinker-1.7.0.tar.gz", hash = "sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"},
]
[[package]]
name = "brotli"
version = "1.1.0"
@@ -383,47 +386,73 @@ files = [
]
[[package]]
name = "flask"
version = "3.0.3"
name = "dnspython"
version = "2.6.1"
requires_python = ">=3.8"
summary = "A simple framework for building complex web applications."
dependencies = [
"Jinja2>=3.1.2",
"Werkzeug>=3.0.0",
"blinker>=1.6.2",
"click>=8.1.3",
"itsdangerous>=2.1.2",
]
summary = "DNS toolkit"
files = [
{file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
{file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
{file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
{file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
]
[[package]]
name = "flask-httpauth"
version = "4.8.0"
summary = "HTTP authentication for Flask routes"
name = "email-validator"
version = "2.2.0"
requires_python = ">=3.8"
summary = "A robust email address syntax and deliverability validation library."
dependencies = [
"flask",
"dnspython>=2.0.0",
"idna>=2.0.0",
]
files = [
{file = "Flask-HTTPAuth-4.8.0.tar.gz", hash = "sha256:66568a05bc73942c65f1e2201ae746295816dc009edd84b482c44c758d75097a"},
{file = "Flask_HTTPAuth-4.8.0-py3-none-any.whl", hash = "sha256:a58fedd09989b9975448eef04806b096a3964a7feeebc0a78831ff55685b62b0"},
{file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"},
{file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"},
]
[[package]]
name = "flask"
version = "3.0.3"
extras = ["async"]
name = "exceptiongroup"
version = "1.2.1"
requires_python = ">=3.7"
summary = "Backport of PEP 654 (exception groups)"
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
]
[[package]]
name = "fastapi"
version = "0.111.0"
requires_python = ">=3.8"
summary = "A simple framework for building complex web applications."
summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
dependencies = [
"asgiref>=3.2",
"flask==3.0.3",
"email-validator>=2.0.0",
"fastapi-cli>=0.0.2",
"httpx>=0.23.0",
"jinja2>=2.11.2",
"orjson>=3.2.1",
"pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4",
"python-multipart>=0.0.7",
"starlette<0.38.0,>=0.37.2",
"typing-extensions>=4.8.0",
"ujson!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,>=4.0.1",
"uvicorn[standard]>=0.12.0",
]
files = [
{file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"},
{file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"},
{file = "fastapi-0.111.0-py3-none-any.whl", hash = "sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0"},
{file = "fastapi-0.111.0.tar.gz", hash = "sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7"},
]
[[package]]
name = "fastapi-cli"
version = "0.0.4"
requires_python = ">=3.8"
summary = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀"
dependencies = [
"typer>=0.12.3",
]
files = [
{file = "fastapi_cli-0.0.4-py3-none-any.whl", hash = "sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809"},
{file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"},
]
[[package]]
@@ -465,6 +494,77 @@ files = [
{file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"},
]
[[package]]
name = "h11"
version = "0.14.0"
requires_python = ">=3.7"
summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.5"
requires_python = ">=3.8"
summary = "A minimal low-level HTTP client."
dependencies = [
"certifi",
"h11<0.15,>=0.13",
]
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[[package]]
name = "httptools"
version = "0.6.1"
requires_python = ">=3.8.0"
summary = "A collection of framework independent HTTP protocol utils."
files = [
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"},
{file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"},
{file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"},
{file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"},
{file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"},
{file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"},
{file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"},
{file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"},
{file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"},
{file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"},
{file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"},
{file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"},
{file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"},
{file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"},
{file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"},
{file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"},
{file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"},
{file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"},
{file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"},
{file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"},
{file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"},
{file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"},
]
[[package]]
name = "httpx"
version = "0.27.0"
requires_python = ">=3.8"
summary = "The next generation HTTP client."
dependencies = [
"anyio",
"certifi",
"httpcore==1.*",
"idna",
"sniffio",
]
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[[package]]
name = "idna"
version = "3.4"
@@ -488,16 +588,6 @@ files = [
{file = "importlib_metadata-7.2.1.tar.gz", hash = "sha256:509ecb2ab77071db5137c655e24ceb3eee66e7bbc6574165d0d114d9fc4bbe68"},
]
[[package]]
name = "itsdangerous"
version = "2.1.2"
requires_python = ">=3.7"
summary = "Safely pass data to untrusted environments and back."
files = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
]
[[package]]
name = "jinja2"
version = "3.1.3"
@@ -637,6 +727,45 @@ files = [
{file = "mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99"},
]
[[package]]
name = "orjson"
version = "3.10.6"
requires_python = ">=3.8"
summary = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
files = [
{file = "orjson-3.10.6-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143"},
{file = "orjson-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5"},
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43"},
{file = "orjson-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2"},
{file = "orjson-3.10.6-cp310-none-win32.whl", hash = "sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3"},
{file = "orjson-3.10.6-cp310-none-win_amd64.whl", hash = "sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c"},
{file = "orjson-3.10.6-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba"},
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b"},
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863"},
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0"},
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a"},
{file = "orjson-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0"},
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212"},
{file = "orjson-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5"},
{file = "orjson-3.10.6-cp311-none-win32.whl", hash = "sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd"},
{file = "orjson-3.10.6-cp311-none-win_amd64.whl", hash = "sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b"},
{file = "orjson-3.10.6-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb"},
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed"},
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7"},
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e"},
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f"},
{file = "orjson-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a"},
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148"},
{file = "orjson-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34"},
{file = "orjson-3.10.6-cp312-none-win32.whl", hash = "sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5"},
{file = "orjson-3.10.6-cp312-none-win_amd64.whl", hash = "sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc"},
{file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"},
]
[[package]]
name = "packaging"
version = "24.1"
@@ -699,6 +828,98 @@ files = [
{file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"},
]
[[package]]
name = "pydantic"
version = "2.8.2"
requires_python = ">=3.8"
summary = "Data validation using Python type hints"
dependencies = [
"annotated-types>=0.4.0",
"pydantic-core==2.20.1",
"typing-extensions>=4.12.2; python_version >= \"3.13\"",
"typing-extensions>=4.6.1; python_version < \"3.13\"",
]
files = [
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
]
[[package]]
name = "pydantic-core"
version = "2.20.1"
requires_python = ">=3.8"
summary = "Core functionality for Pydantic validation and serialization"
dependencies = [
"typing-extensions!=4.7.0,>=4.6.0",
]
files = [
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
]
[[package]]
name = "pygments"
version = "2.16.1"
@@ -709,6 +930,26 @@ files = [
{file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"},
]
[[package]]
name = "python-dotenv"
version = "1.0.1"
requires_python = ">=3.8"
summary = "Read key-value pairs from a .env file and set them as environment variables"
files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[[package]]
name = "python-multipart"
version = "0.0.9"
requires_python = ">=3.8"
summary = "A streaming multipart parser for Python"
files = [
{file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"},
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
]
[[package]]
name = "pyyaml"
version = "6.0.1"
@@ -810,6 +1051,39 @@ files = [
{file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
]
[[package]]
name = "shellingham"
version = "1.5.4"
requires_python = ">=3.7"
summary = "Tool to Detect Surrounding Shell"
files = [
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
requires_python = ">=3.7"
summary = "Sniff out which async library your code is running under"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "starlette"
version = "0.37.2"
requires_python = ">=3.8"
summary = "The little ASGI library that shines."
dependencies = [
"anyio<5,>=3.4.0",
]
files = [
{file = "starlette-0.37.2-py3-none-any.whl", hash = "sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee"},
{file = "starlette-0.37.2.tar.gz", hash = "sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823"},
]
[[package]]
name = "termcolor"
version = "2.4.0"
@@ -830,14 +1104,96 @@ files = [
{file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"},
]
[[package]]
name = "typer"
version = "0.12.3"
requires_python = ">=3.7"
summary = "Typer, build great CLIs. Easy to code. Based on Python type hints."
dependencies = [
"click>=8.0.0",
"rich>=10.11.0",
"shellingham>=1.3.0",
"typing-extensions>=3.7.4.3",
]
files = [
{file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
{file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
]
[[package]]
name = "typing-extensions"
version = "4.9.0"
version = "4.12.2"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
files = [
{file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
{file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "ujson"
version = "5.10.0"
requires_python = ">=3.8"
summary = "Ultra fast JSON encoder and decoder for Python"
files = [
{file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"},
{file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"},
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"},
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"},
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"},
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"},
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"},
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"},
{file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"},
{file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"},
{file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"},
{file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"},
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"},
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"},
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"},
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"},
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"},
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"},
{file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"},
{file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"},
{file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"},
{file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"},
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"},
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"},
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"},
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"},
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"},
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"},
{file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"},
{file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"},
{file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"},
{file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"},
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"},
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"},
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"},
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"},
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"},
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"},
{file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"},
{file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"},
{file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"},
{file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"},
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"},
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"},
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"},
{file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"},
{file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"},
{file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"},
{file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"},
{file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"},
{file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"},
{file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"},
{file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"},
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"},
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"},
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"},
{file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"},
{file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"},
]
[[package]]
@@ -851,13 +1207,128 @@ files = [
]
[[package]]
name = "waitress"
version = "3.0.0"
requires_python = ">=3.8.0"
summary = "Waitress WSGI server"
name = "uvicorn"
version = "0.30.1"
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
dependencies = [
"click>=7.0",
"h11>=0.8",
"typing-extensions>=4.0; python_version < \"3.11\"",
]
files = [
{file = "waitress-3.0.0-py3-none-any.whl", hash = "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669"},
{file = "waitress-3.0.0.tar.gz", hash = "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1"},
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
]
[[package]]
name = "uvicorn"
version = "0.30.1"
extras = ["standard"]
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
dependencies = [
"colorama>=0.4; sys_platform == \"win32\"",
"httptools>=0.5.0",
"python-dotenv>=0.13",
"pyyaml>=5.1",
"uvicorn==0.30.1",
"uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"",
"watchfiles>=0.13",
"websockets>=10.4",
]
files = [
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
]
[[package]]
name = "uvloop"
version = "0.19.0"
requires_python = ">=3.8.0"
summary = "Fast implementation of asyncio event loop on top of libuv"
files = [
{file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e"},
{file = "uvloop-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428"},
{file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8"},
{file = "uvloop-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849"},
{file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957"},
{file = "uvloop-0.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd"},
{file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef"},
{file = "uvloop-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2"},
{file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1"},
{file = "uvloop-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24"},
{file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533"},
{file = "uvloop-0.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12"},
{file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650"},
{file = "uvloop-0.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec"},
{file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc"},
{file = "uvloop-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6"},
{file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593"},
{file = "uvloop-0.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3"},
{file = "uvloop-0.19.0.tar.gz", hash = "sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd"},
]
[[package]]
name = "watchfiles"
version = "0.22.0"
requires_python = ">=3.8"
summary = "Simple, modern and high performance file watching and code reload in python."
dependencies = [
"anyio>=3.0.0",
]
files = [
{file = "watchfiles-0.22.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538"},
{file = "watchfiles-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e"},
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1"},
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a"},
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd"},
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb"},
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171"},
{file = "watchfiles-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71"},
{file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39"},
{file = "watchfiles-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848"},
{file = "watchfiles-0.22.0-cp310-none-win32.whl", hash = "sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797"},
{file = "watchfiles-0.22.0-cp310-none-win_amd64.whl", hash = "sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb"},
{file = "watchfiles-0.22.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96"},
{file = "watchfiles-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696"},
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249"},
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550"},
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c"},
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da"},
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1"},
{file = "watchfiles-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f"},
{file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d"},
{file = "watchfiles-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c"},
{file = "watchfiles-0.22.0-cp311-none-win32.whl", hash = "sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67"},
{file = "watchfiles-0.22.0-cp311-none-win_amd64.whl", hash = "sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1"},
{file = "watchfiles-0.22.0-cp311-none-win_arm64.whl", hash = "sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84"},
{file = "watchfiles-0.22.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a"},
{file = "watchfiles-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be"},
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2"},
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c"},
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232"},
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1"},
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6"},
{file = "watchfiles-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27"},
{file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b"},
{file = "watchfiles-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35"},
{file = "watchfiles-0.22.0-cp312-none-win32.whl", hash = "sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e"},
{file = "watchfiles-0.22.0-cp312-none-win_amd64.whl", hash = "sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e"},
{file = "watchfiles-0.22.0-cp312-none-win_arm64.whl", hash = "sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea"},
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68"},
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c"},
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab"},
{file = "watchfiles-0.22.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2"},
{file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2"},
{file = "watchfiles-0.22.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6"},
{file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795"},
{file = "watchfiles-0.22.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71"},
{file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed"},
{file = "watchfiles-0.22.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc"},
{file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31"},
{file = "watchfiles-0.22.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2"},
{file = "watchfiles-0.22.0.tar.gz", hash = "sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb"},
]
[[package]]
@@ -927,19 +1398,6 @@ files = [
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
]
[[package]]
name = "werkzeug"
version = "3.0.1"
requires_python = ">=3.8"
summary = "The comprehensive WSGI web application library."
dependencies = [
"MarkupSafe>=2.1.1",
]
files = [
{file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
{file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
]
[[package]]
name = "yarl"
version = "1.9.2"

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.2.0"
version = "0.3.9"
description = "Play Music with xiaomi AI speaker"
authors = [
{name = "涵曦", email = "im.hanxi@gmail.com"},
@@ -10,9 +10,9 @@ dependencies = [
"miservice-fork>=2.7.0",
"mutagen>=1.47.0",
"yt-dlp>=2024.07.01",
"flask[async]>=3.0.1",
"waitress>=3.0.0",
"flask-HTTPAuth>=4.8.0",
"uvicorn>=0.30.1",
"fastapi>=0.111.0",
"starlette>=0.37.2",
]
requires-python = ">=3.10"
readme = "README.md"
@@ -55,6 +55,9 @@ include = ["**/*.py", "**/*.pyi", "**/pyproject.toml"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = ["fastapi.Depends", "fastapi.params.Depends", "fastapi.Query", "fastapi.params.Query"]
[tool.pdm.scripts]
lint = "ruff check ."
fmt = "ruff format ."

View File

@@ -51,18 +51,18 @@ aiohttp==3.9.5 \
aiosignal==1.3.1 \
--hash=sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc \
--hash=sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17
asgiref==3.7.2 \
--hash=sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e \
--hash=sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed
annotated-types==0.7.0 \
--hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \
--hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89
anyio==4.4.0 \
--hash=sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94 \
--hash=sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7
async-timeout==4.0.3; python_version < "3.11" \
--hash=sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f \
--hash=sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028
attrs==23.1.0 \
--hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \
--hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015
blinker==1.7.0 \
--hash=sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9 \
--hash=sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182
brotli==1.1.0; implementation_name == "cpython" \
--hash=sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128 \
--hash=sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9 \
@@ -217,15 +217,24 @@ charset-normalizer==3.3.0 \
click==8.1.7 \
--hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \
--hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de
colorama==0.4.6; platform_system == "Windows" \
colorama==0.4.6; platform_system == "Windows" or sys_platform == "win32" \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
flask-HTTPAuth==4.8.0 \
--hash=sha256:66568a05bc73942c65f1e2201ae746295816dc009edd84b482c44c758d75097a \
--hash=sha256:a58fedd09989b9975448eef04806b096a3964a7feeebc0a78831ff55685b62b0
flask[async]==3.0.3 \
--hash=sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3 \
--hash=sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842
dnspython==2.6.1 \
--hash=sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50 \
--hash=sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc
email-validator==2.2.0 \
--hash=sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631 \
--hash=sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7
exceptiongroup==1.2.1; python_version < "3.11" \
--hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \
--hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16
fastapi==0.111.0 \
--hash=sha256:97ecbf994be0bcbdadedf88c3150252bed7b2087075ac99735403b1b76cc8fc0 \
--hash=sha256:b9db9dd147c91cb8b769f7183535773d8741dd46f9dc6676cd82eab510228cd7
fastapi-cli==0.0.4 \
--hash=sha256:a2552f3a7ae64058cdbb530be6fa6dbfc975dc165e4fa66d224c3d396e25e809 \
--hash=sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32
frozenlist==1.4.0 \
--hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \
--hash=sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251 \
@@ -258,13 +267,42 @@ frozenlist==1.4.0 \
--hash=sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb \
--hash=sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963 \
--hash=sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f
h11==0.14.0 \
--hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \
--hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761
httpcore==1.0.5 \
--hash=sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61 \
--hash=sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5
httptools==0.6.1 \
--hash=sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563 \
--hash=sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142 \
--hash=sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b \
--hash=sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658 \
--hash=sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2 \
--hash=sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837 \
--hash=sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3 \
--hash=sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58 \
--hash=sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d \
--hash=sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90 \
--hash=sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0 \
--hash=sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1 \
--hash=sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2 \
--hash=sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0 \
--hash=sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc \
--hash=sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503 \
--hash=sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949 \
--hash=sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84 \
--hash=sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb \
--hash=sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a \
--hash=sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f \
--hash=sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185
httpx==0.27.0 \
--hash=sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5 \
--hash=sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5
idna==3.4 \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
itsdangerous==2.1.2 \
--hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \
--hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a
Jinja2==3.1.3 \
jinja2==3.1.3 \
--hash=sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa \
--hash=sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90
markdown-it-py==3.0.0 \
@@ -343,6 +381,38 @@ multidict==6.0.4 \
mutagen==1.47.0 \
--hash=sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99 \
--hash=sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719
orjson==3.10.6 \
--hash=sha256:05ac3d3916023745aa3b3b388e91b9166be1ca02b7c7e41045da6d12985685f0 \
--hash=sha256:0943e4c701196b23c240b3d10ed8ecd674f03089198cf503105b474a4f77f21f \
--hash=sha256:1335d4ef59ab85cab66fe73fd7a4e881c298ee7f63ede918b7faa1b27cbe5212 \
--hash=sha256:1c680b269d33ec444afe2bdc647c9eb73166fa47a16d9a75ee56a374f4a45f43 \
--hash=sha256:227df19441372610b20e05bdb906e1742ec2ad7a66ac8350dcfd29a63014a83b \
--hash=sha256:446dee5a491b5bc7d8f825d80d9637e7af43f86a331207b9c9610e2f93fee22a \
--hash=sha256:450e39ab1f7694465060a0550b3f6d328d20297bf2e06aa947b97c21e5241fbd \
--hash=sha256:4bbc6d0af24c1575edc79994c20e1b29e6fb3c6a570371306db0993ecf144dc5 \
--hash=sha256:55d43d3feb8f19d07e9f01e5b9be4f28801cf7c60d0fa0d279951b18fae1932b \
--hash=sha256:57985ee7e91d6214c837936dc1608f40f330a6b88bb13f5a57ce5257807da143 \
--hash=sha256:61272a5aec2b2661f4fa2b37c907ce9701e821b2c1285d5c3ab0207ebd358d38 \
--hash=sha256:633a3b31d9d7c9f02d49c4ab4d0a86065c4a6f6adc297d63d272e043472acab5 \
--hash=sha256:64c81456d2a050d380786413786b057983892db105516639cb5d3ee3c7fd5148 \
--hash=sha256:7275664f84e027dcb1ad5200b8b18373e9c669b2a9ec33d410c40f5ccf4b257e \
--hash=sha256:874ce88264b7e655dde4aeaacdc8fd772a7962faadfb41abe63e2a4861abc3dc \
--hash=sha256:95a0cce17f969fb5391762e5719575217bd10ac5a189d1979442ee54456393f3 \
--hash=sha256:960db0e31c4e52fa0fc3ecbaea5b2d3b58f379e32a95ae6b0ebeaa25b93dfd34 \
--hash=sha256:9c1c4b53b24a4c06547ce43e5fee6ec4e0d8fe2d597f4647fc033fd205707365 \
--hash=sha256:a6ea7afb5b30b2317e0bee03c8d34c8181bc5a36f2afd4d0952f378972c4efd5 \
--hash=sha256:ac3045267e98fe749408eee1593a142e02357c5c99be0802185ef2170086a863 \
--hash=sha256:b1ec490e10d2a77c345def52599311849fc063ae0e67cf4f84528073152bb2ba \
--hash=sha256:b6f3d167d13a16ed263b52dbfedff52c962bfd3d270b46b7518365bcc2121eed \
--hash=sha256:c27bc6a28ae95923350ab382c57113abd38f3928af3c80be6f2ba7eb8d8db0b0 \
--hash=sha256:d27456491ca79532d11e507cadca37fb8c9324a3976294f68fb1eff2dc6ced5a \
--hash=sha256:df25d9271270ba2133cc88ee83c318372bdc0f2cd6f32e7a450809a111efc45c \
--hash=sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7 \
--hash=sha256:ea2977b21f8d5d9b758bb3f344a75e55ca78e3ff85595d248eee813ae23ecdfb \
--hash=sha256:eadc8fd310edb4bdbd333374f2c8fec6794bbbae99b592f448d8214a5e4050c0 \
--hash=sha256:f710f346e4c44a4e8bdf23daa974faede58f83334289df80bc9cd12fe82573c7 \
--hash=sha256:f759503a97a6ace19e55461395ab0d618b5a117e8d0fbb20e70cfd68a47327f2 \
--hash=sha256:fb0ee33124db6eaa517d00890fc1a55c3bfe1cf78ba4a8899d71a06f2d6ff5c7
pycparser==2.21; implementation_name != "cpython" \
--hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
--hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
@@ -368,24 +438,267 @@ pycryptodomex==3.19.0 \
--hash=sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d \
--hash=sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3 \
--hash=sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2
pydantic==2.8.2 \
--hash=sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a \
--hash=sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8
pydantic-core==2.20.1 \
--hash=sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d \
--hash=sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686 \
--hash=sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482 \
--hash=sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83 \
--hash=sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6 \
--hash=sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88 \
--hash=sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86 \
--hash=sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a \
--hash=sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6 \
--hash=sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6 \
--hash=sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43 \
--hash=sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4 \
--hash=sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e \
--hash=sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203 \
--hash=sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24 \
--hash=sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc \
--hash=sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc \
--hash=sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3 \
--hash=sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98 \
--hash=sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331 \
--hash=sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2 \
--hash=sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6 \
--hash=sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688 \
--hash=sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b \
--hash=sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0 \
--hash=sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840 \
--hash=sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c \
--hash=sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd \
--hash=sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3 \
--hash=sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231 \
--hash=sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1 \
--hash=sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953 \
--hash=sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250 \
--hash=sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a \
--hash=sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20 \
--hash=sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703 \
--hash=sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac \
--hash=sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121 \
--hash=sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e \
--hash=sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b \
--hash=sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906 \
--hash=sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9 \
--hash=sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7 \
--hash=sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b \
--hash=sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e \
--hash=sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237 \
--hash=sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1 \
--hash=sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19 \
--hash=sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad \
--hash=sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0 \
--hash=sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94 \
--hash=sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312 \
--hash=sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f \
--hash=sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1 \
--hash=sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe \
--hash=sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99 \
--hash=sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a \
--hash=sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a \
--hash=sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52 \
--hash=sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c \
--hash=sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1 \
--hash=sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a \
--hash=sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f \
--hash=sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a \
--hash=sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27
pygments==2.16.1 \
--hash=sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692 \
--hash=sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29
python-dotenv==1.0.1 \
--hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \
--hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a
python-multipart==0.0.9 \
--hash=sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026 \
--hash=sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215
pyyaml==6.0.1 \
--hash=sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc \
--hash=sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df \
--hash=sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741 \
--hash=sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290 \
--hash=sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9 \
--hash=sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d \
--hash=sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007 \
--hash=sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938 \
--hash=sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0 \
--hash=sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d \
--hash=sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28 \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515 \
--hash=sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924 \
--hash=sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34 \
--hash=sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43 \
--hash=sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673 \
--hash=sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54 \
--hash=sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a \
--hash=sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b \
--hash=sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab \
--hash=sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d \
--hash=sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
rich==13.7.1 \
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \
--hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432
typing-extensions==4.9.0; python_version < "3.11" \
--hash=sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783 \
--hash=sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd
shellingham==1.5.4 \
--hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \
--hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de
sniffio==1.3.1 \
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc
starlette==0.37.2 \
--hash=sha256:6fe59f29268538e5d0d182f2791a479a0c64638e6935d1c6989e63fb2699c6ee \
--hash=sha256:9af890290133b79fc3db55474ade20f6220a364a0402e0b556e7cd5e1e093823
typer==0.12.3 \
--hash=sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914 \
--hash=sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
ujson==5.10.0 \
--hash=sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e \
--hash=sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b \
--hash=sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6 \
--hash=sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd \
--hash=sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569 \
--hash=sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f \
--hash=sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51 \
--hash=sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20 \
--hash=sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf \
--hash=sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc \
--hash=sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e \
--hash=sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539 \
--hash=sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165 \
--hash=sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126 \
--hash=sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1 \
--hash=sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816 \
--hash=sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64 \
--hash=sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8 \
--hash=sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e \
--hash=sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287 \
--hash=sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3 \
--hash=sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0 \
--hash=sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043 \
--hash=sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557 \
--hash=sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e \
--hash=sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21 \
--hash=sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337 \
--hash=sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753 \
--hash=sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804 \
--hash=sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f \
--hash=sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f \
--hash=sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5 \
--hash=sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5 \
--hash=sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1 \
--hash=sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00 \
--hash=sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2 \
--hash=sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e \
--hash=sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4 \
--hash=sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8 \
--hash=sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6 \
--hash=sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1 \
--hash=sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f \
--hash=sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4 \
--hash=sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b \
--hash=sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88 \
--hash=sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518 \
--hash=sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5 \
--hash=sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770 \
--hash=sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988 \
--hash=sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1 \
--hash=sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7 \
--hash=sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a \
--hash=sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720 \
--hash=sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3 \
--hash=sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b \
--hash=sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9 \
--hash=sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1 \
--hash=sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746
urllib3==2.0.6 \
--hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \
--hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564
waitress==3.0.0 \
--hash=sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1 \
--hash=sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669
uvicorn[standard]==0.30.1 \
--hash=sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81 \
--hash=sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8
uvloop==0.19.0; (sys_platform != "cygwin" and sys_platform != "win32") and platform_python_implementation != "PyPy" \
--hash=sha256:0246f4fd1bf2bf702e06b0d45ee91677ee5c31242f39aab4ea6fe0c51aedd0fd \
--hash=sha256:02506dc23a5d90e04d4f65c7791e65cf44bd91b37f24cfc3ef6cf2aff05dc7ec \
--hash=sha256:2693049be9d36fef81741fddb3f441673ba12a34a704e7b4361efb75cf30befc \
--hash=sha256:31e672bb38b45abc4f26e273be83b72a0d28d074d5b370fc4dcf4c4eb15417d2 \
--hash=sha256:47bf3e9312f63684efe283f7342afb414eea4d3011542155c7e625cd799c3b12 \
--hash=sha256:4ce6b0af8f2729a02a5d1575feacb2a94fc7b2e983868b009d51c9a9d2149bef \
--hash=sha256:5138821e40b0c3e6c9478643b4660bd44372ae1e16a322b8fc07478f92684e24 \
--hash=sha256:5588bd21cf1fcf06bded085f37e43ce0e00424197e7c10e77afd4bbefffef428 \
--hash=sha256:570fc0ed613883d8d30ee40397b79207eedd2624891692471808a95069a007c1 \
--hash=sha256:5a05128d315e2912791de6088c34136bfcdd0c7cbc1cf85fd6fd1bb321b7c849 \
--hash=sha256:5daa304d2161d2918fa9a17d5635099a2f78ae5b5960e742b2fcfbb7aefaa593 \
--hash=sha256:5f17766fb6da94135526273080f3455a112f82570b2ee5daa64d682387fe0dcd \
--hash=sha256:7010271303961c6f0fe37731004335401eb9075a12680738731e9c92ddd96ad6 \
--hash=sha256:7207272c9520203fea9b93843bb775d03e1cf88a80a936ce760f60bb5add92f3 \
--hash=sha256:7b1fd71c3843327f3bbc3237bedcdb6504fd50368ab3e04d0410e52ec293f5b8 \
--hash=sha256:91ab01c6cd00e39cde50173ba4ec68a1e578fee9279ba64f5221810a9e786533 \
--hash=sha256:cd81bdc2b8219cb4b2556eea39d2e36bfa375a2dd021404f90a62e44efaaf957 \
--hash=sha256:da8435a3bd498419ee8c13c34b89b5005130a476bda1d6ca8cfdde3de35cd650 \
--hash=sha256:de4313d7f575474c8f5a12e163f6d89c0a878bc49219641d49e6f1444369a90e
watchfiles==0.22.0 \
--hash=sha256:00095dd368f73f8f1c3a7982a9801190cc88a2f3582dd395b289294f8975172b \
--hash=sha256:00ad0bcd399503a84cc688590cdffbe7a991691314dde5b57b3ed50a41319a31 \
--hash=sha256:00f39592cdd124b4ec5ed0b1edfae091567c72c7da1487ae645426d1b0ffcad1 \
--hash=sha256:030bc4e68d14bcad2294ff68c1ed87215fbd9a10d9dea74e7cfe8a17869785ab \
--hash=sha256:052d668a167e9fc345c24203b104c313c86654dd6c0feb4b8a6dfc2462239249 \
--hash=sha256:067dea90c43bf837d41e72e546196e674f68c23702d3ef80e4e816937b0a3ffd \
--hash=sha256:0bc3b2f93a140df6806c8467c7f51ed5e55a931b031b5c2d7ff6132292e803d6 \
--hash=sha256:0c8e0aa0e8cc2a43561e0184c0513e291ca891db13a269d8d47cb9841ced7c71 \
--hash=sha256:1235c11510ea557fe21be5d0e354bae2c655a8ee6519c94617fe63e05bca4171 \
--hash=sha256:1cc0cba54f47c660d9fa3218158b8963c517ed23bd9f45fe463f08262a4adae1 \
--hash=sha256:1d9188979a58a096b6f8090e816ccc3f255f137a009dd4bbec628e27696d67c1 \
--hash=sha256:213792c2cd3150b903e6e7884d40660e0bcec4465e00563a5fc03f30ea9c166c \
--hash=sha256:2627a91e8110b8de2406d8b2474427c86f5a62bf7d9ab3654f541f319ef22bcb \
--hash=sha256:28585744c931576e535860eaf3f2c0ec7deb68e3b9c5a85ca566d69d36d8dd27 \
--hash=sha256:2bdadf6b90c099ca079d468f976fd50062905d61fae183f769637cb0f68ba59a \
--hash=sha256:2f350cbaa4bb812314af5dab0eb8d538481e2e2279472890864547f3fe2281ed \
--hash=sha256:3218a6f908f6a276941422b035b511b6d0d8328edd89a53ae8c65be139073f84 \
--hash=sha256:425440e55cd735386ec7925f64d5dde392e69979d4c8459f6bb4e920210407f2 \
--hash=sha256:4b9f2a128a32a2c273d63eb1fdbf49ad64852fc38d15b34eaa3f7ca2f0d2b797 \
--hash=sha256:52fc9b0dbf54d43301a19b236b4a4614e610605f95e8c3f0f65c3a456ffd7d35 \
--hash=sha256:55b7cc10261c2786c41d9207193a85c1db1b725cf87936df40972aab466179b6 \
--hash=sha256:581f0a051ba7bafd03e17127735d92f4d286af941dacf94bcf823b101366249e \
--hash=sha256:5834e1f8b71476a26df97d121c0c0ed3549d869124ed2433e02491553cb468c2 \
--hash=sha256:5e45fb0d70dda1623a7045bd00c9e036e6f1f6a85e4ef2c8ae602b1dfadf7550 \
--hash=sha256:61af9efa0733dc4ca462347becb82e8ef4945aba5135b1638bfc20fad64d4f0e \
--hash=sha256:68fe0c4d22332d7ce53ad094622b27e67440dacefbaedd29e0794d26e247280c \
--hash=sha256:72a44e9481afc7a5ee3291b09c419abab93b7e9c306c9ef9108cb76728ca58d2 \
--hash=sha256:7a74436c415843af2a769b36bf043b6ccbc0f8d784814ba3d42fc961cdb0a9dc \
--hash=sha256:8c39987a1397a877217be1ac0fb1d8b9f662c6077b90ff3de2c05f235e6a8f96 \
--hash=sha256:8fdebb655bb1ba0122402352b0a4254812717a017d2dc49372a1d47e24073795 \
--hash=sha256:94ebe84a035993bb7668f58a0ebf998174fb723a39e4ef9fce95baabb42b787f \
--hash=sha256:9624a68b96c878c10437199d9a8b7d7e542feddda8d5ecff58fdc8e67b460848 \
--hash=sha256:988e981aaab4f3955209e7e28c7794acdb690be1efa7f16f8ea5aba7ffdadacb \
--hash=sha256:a8a31bfd98f846c3c284ba694c6365620b637debdd36e46e1859c897123aa232 \
--hash=sha256:a927b3034d0672f62fb2ef7ea3c9fc76d063c4b15ea852d1db2dc75fe2c09696 \
--hash=sha256:ace7d060432acde5532e26863e897ee684780337afb775107c0a90ae8dbccfd2 \
--hash=sha256:aec83c3ba24c723eac14225194b862af176d52292d271c98820199110e31141e \
--hash=sha256:b44b70850f0073b5fcc0b31ede8b4e736860d70e2dbf55701e05d3227a154a67 \
--hash=sha256:b810a2c7878cbdecca12feae2c2ae8af59bea016a78bc353c184fa1e09f76b68 \
--hash=sha256:bbf8a20266136507abf88b0df2328e6a9a7c7309e8daff124dda3803306a9fdb \
--hash=sha256:bd4c06100bce70a20c4b81e599e5886cf504c9532951df65ad1133e508bf20be \
--hash=sha256:c2444dc7cb9d8cc5ab88ebe792a8d75709d96eeef47f4c8fccb6df7c7bc5be71 \
--hash=sha256:c49b76a78c156979759d759339fb62eb0549515acfe4fd18bb151cc07366629c \
--hash=sha256:c4a65474fd2b4c63e2c18ac67a0c6c66b82f4e73e2e4d940f837ed3d2fd9d4da \
--hash=sha256:c5af2347d17ab0bd59366db8752d9e037982e259cacb2ba06f2c41c08af02c39 \
--hash=sha256:c668228833c5619f6618699a2c12be057711b0ea6396aeaece4ded94184304ea \
--hash=sha256:c7b978c384e29d6c7372209cbf421d82286a807bbcdeb315427687f8371c340a \
--hash=sha256:da1e0a8caebf17976e2ffd00fa15f258e14749db5e014660f53114b676e68538 \
--hash=sha256:dc2e8fe41f3cac0660197d95216c42910c2b7e9c70d48e6d84e22f577d106fc1 \
--hash=sha256:e0f0a874231e2839abbf473256efffe577d6ee2e3bfa5b540479e892e47c172d \
--hash=sha256:f7e1f9c5d1160d03b93fc4b68a0aeb82fe25563e12fbcdc8507f8434ab6f823c
websockets==12.0 \
--hash=sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b \
--hash=sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6 \
@@ -437,9 +750,6 @@ websockets==12.0 \
--hash=sha256:f44069528d45a933997a6fef143030d8ca8042f0dfaad753e2906398290e2870 \
--hash=sha256:f764ba54e33daf20e167915edc443b6f88956f37fb606449b4a5b10ba42235a5 \
--hash=sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7
Werkzeug==3.0.1 \
--hash=sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc \
--hash=sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10
yarl==1.9.2 \
--hash=sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571 \
--hash=sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7 \

57
update-static-version.py Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python3
import re
from pathlib import Path
def get_html_files(directory):
"""
获取指定目录下所有HTML文件的列表。
:param directory: 搜索HTML文件的目录。
:return: 搜索到的HTML文件的路径列表。
"""
return list(Path(directory).rglob("*.html"))
def update_html_version(html_files, version):
"""
更新HTML文件中所有以 /static/ 开头的CSS和JS文件引用的版本号。
:param html_files: 需要更新的HTML文件路径的列表。
:param version: 新的版本号字符串。
"""
pattern = re.compile(r'(/static/.*(css|js))\?version=[^"]*"')
# pattern = re.compile(r'(/static/.*html)\?version=[^"]*"')
for html_file in html_files:
if not html_file.exists():
print(f"文件 {html_file} 不存在。")
continue
html_content = html_file.read_text()
# 更新CSS和JS版本号
html_content = pattern.sub(rf'\g<1>?version={version}"', html_content)
# html_content = pattern.sub(fr'\g<1>"', html_content)
# 保存更改到HTML文件
html_file.write_text(html_content)
print(f"文件 {html_file} 已更新为使用新的版本号: {version}")
# 使用案例
if __name__ == "__main__":
import time
t = str(int(time.time()))
# 指定目录
html_directory = "xiaomusic/static" # 修改为实际的HTML文件目录路径
# 获取HTML文件列表
html_files_to_update = get_html_files(html_directory)
# 执行更新
update_html_version(html_files_to_update, t)

View File

@@ -1 +1 @@
__version__ = "0.2.0"
__version__ = "0.3.9"

View File

@@ -1,10 +1,12 @@
#!/usr/bin/env python3
import argparse
import asyncio
from xiaomusic import (
__version__,
)
import uvicorn
from xiaomusic import __version__
from xiaomusic.config import Config
from xiaomusic.httpserver import HttpInit
from xiaomusic.httpserver import app as HttpApp
from xiaomusic.xiaomusic import XiaoMusic
LOGO = r"""
@@ -74,8 +76,28 @@ def main():
config = Config.from_options(options)
xiaomusic = XiaoMusic(config)
loop = asyncio.get_event_loop()
loop.run_until_complete(xiaomusic.run_forever())
HttpInit(xiaomusic)
from uvicorn.config import LOGGING_CONFIG
LOGGING_CONFIG["formatters"]["access"] = {
"format": f"%(asctime)s [{__version__}] [%(levelname)s] %(filename)s:%(lineno)d: %(message)s",
"datefmt": "[%X]",
}
LOGGING_CONFIG["handlers"]["access"] = {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"formatter": "access",
"filename": config.log_file,
"maxBytes": 10 * 1024 * 1024,
"backupCount": 1,
}
uvicorn.run(
HttpApp,
host=["::", "0.0.0.0"],
port=config.port,
log_config=LOGGING_CONFIG,
)
if __name__ == "__main__":

View File

@@ -18,12 +18,10 @@ def default_key_word_dict():
"下一首": "play_next",
"单曲循环": "set_play_type_one",
"全部循环": "set_play_type_all",
"随机播放": "random_play",
"随机播放": "set_random_play",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
"set_volume#": "set_volume",
"get_volume#": "get_volume",
}
@@ -43,8 +41,6 @@ KEY_WORD_ARG_BEFORE_DICT = {
# 口令匹配优先级
def default_key_match_order():
return [
"set_volume#",
"get_volume#",
"分钟后关机",
"播放歌曲",
"下一首",
@@ -57,19 +53,29 @@ def default_key_match_order():
]
@dataclass
class Device:
did: str = ""
device_id: str = ""
hardware: str = ""
name: str = ""
play_type: int = ""
cur_music: str = ""
cur_playlist: str = ""
@dataclass
class Config:
account: str = os.getenv("MI_USER", "")
password: str = os.getenv("MI_PASS", "")
mi_did: str = os.getenv("MI_DID", "") # 逗号分割支持多设备
hardware: str = os.getenv("MI_HARDWARE", "L07A") # 逗号分割支持多设备
cookie: str = ""
verbose: bool = os.getenv("XIAOMUSIC_VERBOSE", "").lower() == "true"
music_path: str = os.getenv(
"XIAOMUSIC_MUSIC_PATH", "music"
) # 只能是music目录下的子目录
download_path: str = os.getenv("XIAOMUSIC_DOWNLOAD_PATH", "")
conf_path: str = os.getenv("XIAOMUSIC_CONF_PATH", None)
conf_path: str = os.getenv("XIAOMUSIC_CONF_PATH", "conf")
hostname: str = os.getenv("XIAOMUSIC_HOSTNAME", "192.168.2.5")
port: int = int(os.getenv("XIAOMUSIC_PORT", "8090")) # 监听端口
public_port: int = int(os.getenv("XIAOMUSIC_PUBLIC_PORT", 0)) # 歌曲访问端口
@@ -79,7 +85,7 @@ class Config:
) # "bilisearch:" or "ytsearch:"
ffmpeg_location: str = os.getenv("XIAOMUSIC_FFMPEG_LOCATION", "./ffmpeg/bin")
active_cmd: str = os.getenv(
"XIAOMUSIC_ACTIVE_CMD", "play,random_play,playlocal,play_music_list,stop"
"XIAOMUSIC_ACTIVE_CMD", "play,set_random_play,playlocal,play_music_list,stop"
)
exclude_dirs: str = os.getenv("XIAOMUSIC_EXCLUDE_DIRS", "@eaDir")
music_path_depth: int = int(os.getenv("XIAOMUSIC_MUSIC_PATH_DEPTH", "10"))
@@ -123,7 +129,10 @@ class Config:
enable_force_stop: bool = (
os.getenv("XIAOMUSIC_ENABLE_FORCE_STOP", "false").lower() == "true"
)
play_type: int = int(os.getenv("XIAOMUSIC_PLAY_TYPE", "2"))
devices: dict[str, Device] = field(default_factory=dict)
group_list: str = os.getenv(
"XIAOMUSIC_GROUP_LIST", ""
) # did1:group_name,did2:group_name
def append_keyword(self, keys, action):
for key in keys.split(","):
@@ -150,7 +159,7 @@ class Config:
if self.enable_config_example:
with open("config-example.json", "w") as f:
data = asdict(self)
json.dump(data, f, ensure_ascii=False, indent=4)
json.dump(data, f, ensure_ascii=False, indent=2)
@classmethod
def from_options(cls, options: argparse.Namespace) -> Config:
@@ -171,6 +180,10 @@ class Config:
converted_value = False
if str(v).lower() == "true":
converted_value = True
elif expected_type == dict[str, Device]:
converted_value = {}
for kk, vv in v.items():
converted_value[kk] = Device(**vv)
else:
converted_value = expected_type(v)
return converted_value
@@ -192,7 +205,7 @@ class Config:
return result
def update_config(self, data):
type_hints = get_type_hints(self)
type_hints = get_type_hints(self, globals(), locals())
for k, v in data.items():
converted_value = self.convert_value(k, v, type_hints)

View File

@@ -9,3 +9,13 @@ SUPPORT_MUSIC_TYPE = [
LATEST_ASK_API = "https://userprofile.mina.mi.com/device_profile/v2/conversation?source=dialogu&hardware={hardware}&timestamp={timestamp}&limit=2"
COOKIE_TEMPLATE = "deviceId={device_id}; serviceToken={service_token}; userId={user_id}"
PLAY_TYPE_ONE = 0 # 单曲循环
PLAY_TYPE_ALL = 1 # 全部循环
PLAY_TYPE_RND = 2 # 随机播放
PLAY_TYPE_TTS = {
PLAY_TYPE_ONE: "已经设置为单曲循环",
PLAY_TYPE_ALL: "已经设置为全部循环",
PLAY_TYPE_RND: "已经设置为随机播放",
}

View File

@@ -1,155 +1,236 @@
#!/usr/bin/env python3
import asyncio
import json
import os
import secrets
import shutil
import tempfile
from contextlib import asynccontextmanager
from dataclasses import asdict
from threading import Thread
from typing import Annotated
from flask import Flask, request, send_file, send_from_directory
from flask_httpauth import HTTPBasicAuth
from waitress import serve
from fastapi import Depends, FastAPI, HTTPException, Request, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from starlette.background import BackgroundTask
from starlette.responses import FileResponse
from xiaomusic import (
__version__,
)
from xiaomusic import __version__
from xiaomusic.utils import (
deepcopy_data_no_sensitive_info,
downloadfile,
)
app = Flask(__name__)
auth = HTTPBasicAuth()
host = "0.0.0.0"
port = 8090
static_path = "music"
xiaomusic = None
config = None
log = None
@auth.verify_password
def verify_password(username, password):
if xiaomusic.config.disable_httpauth:
return True
if (
xiaomusic.config.httpauth_username == username
and xiaomusic.config.httpauth_password == password
):
return username
@asynccontextmanager
async def app_lifespan(app):
if xiaomusic is not None:
task = asyncio.create_task(xiaomusic.run_forever())
yield
task.cancel()
@app.route("/allcmds")
@auth.login_required
def allcmds():
return xiaomusic.config.key_word_dict
security = HTTPBasic()
@app.route("/getversion", methods=["GET"])
def verification(
credentials: Annotated[HTTPBasicCredentials, Depends(security)],
):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = config.httpauth_username.encode("utf8")
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = config.httpauth_password.encode("utf8")
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return True
def no_verification():
return True
app = FastAPI(
lifespan=app_lifespan,
version=__version__,
dependencies=[Depends(verification)],
)
def reset_http_server():
log.info(f"disable_httpauth:{config.disable_httpauth}")
if config.disable_httpauth:
app.dependency_overrides[verification] = no_verification
else:
app.dependency_overrides = {}
# 更新 music 链接
app.router.routes = [route for route in app.router.routes if route.path != "/music"]
app.mount("/music", StaticFiles(directory=config.music_path), name="music")
def HttpInit(_xiaomusic):
global xiaomusic, config, log
xiaomusic = _xiaomusic
config = xiaomusic.config
log = xiaomusic.log
folder = os.path.dirname(__file__)
app.mount("/static", StaticFiles(directory=f"{folder}/static"), name="static")
reset_http_server()
@app.get("/")
async def read_index():
folder = os.path.dirname(__file__)
return FileResponse(f"{folder}/static/index.html")
@app.get("/getversion")
def getversion():
log.debug("getversion %s", __version__)
return {
"version": __version__,
}
return {"version": __version__}
@app.route("/getvolume", methods=["GET"])
@auth.login_required
def getvolume():
volume = xiaomusic.get_volume_ret()
return {
"volume": volume,
}
@app.get("/getvolume")
async def getvolume(did: str = ""):
if not xiaomusic.did_exist(did):
return {"volume": 0}
volume = await xiaomusic.get_volume(did=did)
return {"volume": volume}
@app.route("/searchmusic", methods=["GET"])
@auth.login_required
def searchmusic():
name = request.args.get("name")
class DidVolume(BaseModel):
did: str
volume: int = 0
@app.post("/setvolume")
async def setvolume(data: DidVolume):
did = data.did
volume = data.volume
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
log.info(f"set_volume {did} {volume}")
await xiaomusic.set_volume(did=did, arg1=volume)
return {"ret": "OK", "volume": volume}
@app.get("/searchmusic")
def searchmusic(name: str = ""):
return xiaomusic.searchmusic(name)
@app.route("/playingmusic", methods=["GET"])
@auth.login_required
def playingmusic():
return xiaomusic.playingmusic()
@app.get("/playingmusic")
def playingmusic(did: str = ""):
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
is_playing = xiaomusic.isplaying(did)
cur_music = xiaomusic.playingmusic(did)
return {
"ret": "OK",
"is_playing": is_playing,
"cur_music": cur_music,
}
@app.route("/isplaying", methods=["GET"])
@auth.login_required
def isplaying():
return xiaomusic.isplaying()
class DidCmd(BaseModel):
did: str
cmd: str
@app.route("/", methods=["GET"])
def index():
return send_from_directory("static", "index.html")
@app.post("/cmd")
async def do_cmd(data: DidCmd):
did = data.did
cmd = data.cmd
log.info(f"docmd. did:{did} cmd:{cmd}")
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
@app.route("/cmd", methods=["POST"])
@auth.login_required
async def do_cmd():
data = request.get_json()
cmd = data.get("cmd")
if len(cmd) > 0:
log.debug("docmd. cmd:%s", cmd)
xiaomusic.set_last_record(cmd)
asyncio.create_task(xiaomusic.do_check_cmd(did=did, query=cmd))
return {"ret": "OK"}
return {"ret": "Unknow cmd"}
@app.route("/getsetting", methods=["GET"])
@auth.login_required
async def getsetting():
@app.get("/getsetting")
async def getsetting(need_device_list: bool = False):
config = xiaomusic.getconfig()
data = asdict(config)
alldevices = await xiaomusic.call_main_thread_function(xiaomusic.getalldevices)
log.info(f"getsetting alldevices: {alldevices}")
data["mi_did_list"] = alldevices["did_list"]
data["mi_hardware_list"] = alldevices["hardware_list"]
if need_device_list:
device_list = await xiaomusic.getalldevices()
log.info(f"getsetting device_list: {device_list}")
data["device_list"] = device_list
return data
@app.route("/savesetting", methods=["POST"])
@auth.login_required
async def savesetting():
data = request.get_json()
debug_data = deepcopy_data_no_sensitive_info(data)
log.info(f"saveconfig: {debug_data}")
await xiaomusic.saveconfig(data)
return "save success"
@app.post("/savesetting")
async def savesetting(request: Request):
try:
data_json = await request.body()
data = json.loads(data_json.decode("utf-8"))
debug_data = deepcopy_data_no_sensitive_info(data)
log.info(f"saveconfig: {debug_data}")
await xiaomusic.saveconfig(data)
reset_http_server()
return "save success"
except json.JSONDecodeError as err:
raise HTTPException(status_code=400, detail="Invalid JSON") from err
@app.route("/musiclist", methods=["GET"])
@auth.login_required
async def musiclist():
@app.get("/musiclist")
async def musiclist(Verifcation=Depends(verification)):
return xiaomusic.get_music_list()
@app.route("/curplaylist", methods=["GET"])
@auth.login_required
async def curplaylist():
return xiaomusic.get_cur_play_list()
@app.get("/curplaylist")
async def curplaylist(did: str = ""):
if not xiaomusic.did_exist(did):
return ""
return xiaomusic.get_cur_play_list(did)
@app.route("/delmusic", methods=["POST"])
@auth.login_required
def delmusic():
data = request.get_json()
class MusicItem(BaseModel):
name: str
@app.post("/delmusic")
def delmusic(data: MusicItem):
log.info(data)
xiaomusic.del_music(data["name"])
xiaomusic.del_music(data.name)
return "success"
@app.route("/downloadjson", methods=["POST"])
@auth.login_required
def downloadjson():
data = request.get_json()
class UrlInfo(BaseModel):
url: str
@app.post("/downloadjson")
async def downloadjson(data: UrlInfo):
log.info(data)
url = data["url"]
url = data.url
content = ""
try:
ret = "OK"
content = downloadfile(url)
content = await downloadfile(url)
except Exception as e:
log.warning(f"downloadjson failed. url:{url} e:{e}")
log.exception(f"Execption {e}")
ret = "Download JSON file failed."
return {
"ret": ret,
@@ -157,54 +238,52 @@ def downloadjson():
}
@app.route("/downloadlog", methods=["GET"])
@auth.login_required
def downloadlog():
return send_file(xiaomusic.config.log_file, as_attachment=True)
@app.get("/downloadlog")
def downloadlog(Verifcation=Depends(verification)):
file_path = xiaomusic.config.log_file
if os.path.exists(file_path):
# 创建一个临时文件来保存日志的快照
temp_file = tempfile.NamedTemporaryFile(delete=False)
try:
with open(file_path, "rb") as f:
shutil.copyfileobj(f, temp_file)
temp_file.close()
# 使用BackgroundTask在响应发送完毕后删除临时文件
def cleanup_temp_file(tmp_file_path):
os.remove(tmp_file_path)
background_task = BackgroundTask(cleanup_temp_file, temp_file.name)
return FileResponse(
temp_file.name,
media_type="text/plain",
filename="xiaomusic.txt",
background=background_task,
)
except Exception as e:
os.remove(temp_file.name)
raise HTTPException(
status_code=500, detail="Error capturing log file"
) from e
else:
return {"message": "File not found."}
@app.route("/playurl", methods=["GET"])
@auth.login_required
async def playurl():
url = request.args.get("url")
log.info(f"play_url:{url}")
return await xiaomusic.call_main_thread_function(xiaomusic.play_url, arg1=url)
@app.get("/playurl")
async def playurl(did: str, url: str):
if not xiaomusic.did_exist(did):
return {"ret": "Did not exist"}
log.info(f"playurl did: {did} url: {url}")
return await xiaomusic.play_url(did=did, arg1=url)
@app.route("/debug_play_by_music_url", methods=["POST"])
@auth.login_required
async def debug_play_by_music_url():
data = request.get_json()
log.info(f"data:{data}")
return await xiaomusic.call_main_thread_function(
xiaomusic.debug_play_by_music_url, arg1=data
)
def static_path_handler(filename):
log.debug(filename)
log.debug(static_path)
absolute_path = os.path.abspath(static_path)
log.debug(absolute_path)
return send_from_directory(absolute_path, filename)
def run_app():
serve(app, host=host, port=port)
def StartHTTPServer(_port, _static_path, _xiaomusic):
global port, static_path, xiaomusic, log
port = _port
static_path = _static_path
xiaomusic = _xiaomusic
log = xiaomusic.log
app.add_url_rule(
f"/{static_path}/<path:filename>", "static_path_handler", static_path_handler
)
server_thread = Thread(target=run_app)
server_thread.daemon = True
server_thread.start()
xiaomusic.log.info(f"Serving on {host}:{port}")
@app.post("/debug_play_by_music_url")
async def debug_play_by_music_url(request: Request):
try:
data = await request.body()
data_dict = json.loads(data.decode("utf-8"))
log.info(f"data:{data_dict}")
return await xiaomusic.debug_play_by_music_url(arg1=data_dict)
except json.JSONDecodeError as err:
raise HTTPException(status_code=400, detail="Invalid JSON") from err

View File

@@ -1,8 +1,13 @@
$(function(){
$container=$("#cmds");
append_op_button_name("全部循环");
append_op_button_name("单曲循环");
append_op_button_name("随机播放");
const PLAY_TYPE_ONE = 0; // 单曲循环
const PLAY_TYPE_ALL = 1; // 全部循环
const PLAY_TYPE_RND = 2; // 随机播放
append_op_button("play_type_all", "全部循环", "全部循环");
append_op_button("play_type_one", "单曲循环", "单曲循环");
append_op_button("play_type_rnd", "随机播放", "随机播放");
append_op_button_name("刷新列表");
append_op_button_name("下一首");
append_op_button_name("关机");
@@ -13,11 +18,61 @@ $(function(){
append_op_button_name("30分钟后关机");
append_op_button_name("60分钟后关机");
// 拉取声音
sendcmd("get_volume#");
$.get("/getvolume", function(data, status) {
console.log(data, status, data["volume"]);
$("#volume").val(data.volume);
// 拉取现有配置
$.get("/getsetting", function(data, status) {
console.log(data, status);
localStorage.setItem('mi_did', data.mi_did);
var did = localStorage.getItem('cur_did');
var dids = [];
if (data.mi_did != null) {
dids = data.mi_did.split(',');
}
console.log('cur_did', did);
console.log('dids', dids);
if ((dids.length > 0) && (did == null || did == "" || !dids.includes(did))) {
did = dids[0];
localStorage.setItem('cur_did', did);
}
window.did = did;
$.get(`/getvolume?did=${did}`, function(data, status) {
console.log(data, status, data["volume"]);
$("#volume").val(data.volume);
});
refresh_music_list();
$("#did").empty();
var dids = data.mi_did.split(',');
$.each(dids, function(index, value) {
var cur_device = Object.values(data.devices).find(device => device.did === value);
if (cur_device) {
var option = $('<option></option>')
.val(value)
.text(cur_device.name)
.prop('selected', value === did);
$("#did").append(option);
if (cur_device.play_type == PLAY_TYPE_ALL) {
$("#play_type_all").css('background-color', '#b1a8f3');
$("#play_type_all").text('✔️ 全部循环');
} else if (cur_device.play_type == PLAY_TYPE_ONE) {
$("#play_type_one").css('background-color', '#b1a8f3');
$("#play_type_one").text('✔️ 单曲循环');
} else if (cur_device.play_type == PLAY_TYPE_RND) {
$("#play_type_rnd").css('background-color', '#b1a8f3');
$("#play_type_rnd").text('✔️ 随机播放');
}
}
});
console.log('cur_did', did);
$('#did').change(function() {
did = $(this).val();
localStorage.setItem('cur_did', did);
window.did = did;
console.log('cur_did', did);
})
});
// 拉取版本
@@ -47,13 +102,20 @@ $(function(){
$('#music_list').trigger('change');
// 获取当前播放列表
$.get("curplaylist", function(data, status) {
$('#music_list').val(data);
$('#music_list').trigger('change');
$.get(`curplaylist?did=${did}`, function(data, status) {
if (data != "") {
$('#music_list').val(data);
$('#music_list').trigger('change');
}
})
})
// 每3秒获取下正在播放的音乐
get_playing_music();
setInterval(() => {
get_playing_music();
}, 3000);
}
refresh_music_list();
$("#play_music_list").on("click", () => {
var music_list = $("#music_list").val();
@@ -84,20 +146,23 @@ $(function(){
$("#playurl").on("click", () => {
var url = $("#music-url").val();
$.get(`/playurl?url=${url}`, function(data, status) {
$.get(`/playurl?url=${url}&did=${did}`, function(data, status) {
console.log(data);
});
});
function append_op_button_name(name) {
append_op_button(name, name);
append_op_button(null, name, name);
}
function append_op_button(name, cmd) {
function append_op_button(id, name, cmd) {
// 创建按钮
const $button = $("<button>");
$button.text(name);
$button.attr("type", "button");
if (id !== null) {
$button.attr("id", id);
}
// 设置按钮点击事件
$button.on("click", () => {
@@ -115,9 +180,18 @@ $(function(){
sendcmd(cmd);
});
$("#volume").on('input', function () {
$("#volume").on('change', function () {
var value = $(this).val();
sendcmd("set_volume#"+value);
$.ajax({
type: "POST",
url: "/setvolume",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({did: did, volume: value}),
success: () => {
},
error: () => {
}
});
});
function sendcmd(cmd) {
@@ -125,11 +199,14 @@ $(function(){
type: "POST",
url: "/cmd",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({cmd: cmd}),
data: JSON.stringify({did: did, cmd: cmd}),
success: () => {
if (cmd == "刷新列表") {
setTimeout(refresh_music_list, 3000);
}
if (["全部循环", "单曲循环", "随机播放"].includes(cmd)) {
location.reload();
}
},
error: () => {
// 请求失败时执行的操作
@@ -160,18 +237,18 @@ $(function(){
});
function get_playing_music() {
$.get("/playingmusic", function(data, status) {
$.get(`/playingmusic?did=${did}`, function(data, status) {
console.log(data);
$("#playering-music").text(data);
if (data.ret == "OK") {
if (data.is_playing) {
$("#playering-music").text(`【播放中】 ${data.cur_music}`);
} else {
$("#playering-music").text(`【空闲中】 ${data.cur_music}`);
}
}
});
}
// 每3秒获取下正在播放的音乐
get_playing_music();
setInterval(() => {
get_playing_music();
}, 3000);
function custom_sort_key(a, b) {
// 使用正则表达式提取数字前缀
const numericPrefixA = a.match(/^(\d+)/) ? parseInt(a.match(/^(\d+)/)[1], 10) : null;

View File

@@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width">
<title>Debug For XiaoMusic</title>
<link rel="stylesheet" type="text/css" href="/static/style.css">
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script src="/static/jquery-3.7.1.min.js"></script>
<script src="/static/jquery-3.7.1.min.js?version=1721213953"></script>
<script>
var vConsole = new window.VConsole();

View File

@@ -3,9 +3,17 @@
<head>
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="/static/jquery-3.7.1.min.js"></script>
<script src="/static/app.js"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css">
<script src="/static/jquery-3.7.1.min.js?version=1721213953"></script>
<script src="/static/app.js?version=1721213953"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
<!--
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
var vConsole = new window.VConsole();
</script>
-->
</head>
<body>
<h2>小爱音箱操控面板
@@ -14,6 +22,12 @@
</a>)
</h2>
<hr>
<div class="rows">
<select id="did">
</select>
</div>
<div id="cmds">
</div>
<hr>
@@ -27,14 +41,14 @@
</div>
</div>
<hr>
<div>
<div class="rows">
<datalist id="autocomplete-list"></datalist>
<input id="music-name" type="text" placeholder="请输入搜索关键词(如:MV高清版 周杰伦 七里香)" list="autocomplete-list"></input>
<input id="music-filename" type="text" placeholder="请输入保存为的文件名称(如:周杰伦七里香)"></input>
</div>
<button id="play">播放</button>
<div class="container">
<div id="playering-music" class="text"></div>
<div>
<button id="play">播放</button>
<div id="playering-music" class="text"></div>
</div>
</div>
<hr>
@@ -43,15 +57,17 @@
<select id="music_list"></select>
<label for="music_name">歌曲:</label>
<select id="music_name"></select>
<div>
<button id="play_music_list">播放列表歌曲</button>
<button id="del_music">删除选中歌曲</button>
</div>
</div>
<button id="play_music_list">播放列表歌曲</button>
<button id="del_music">删除选中歌曲</button>
<hr>
<div>
<input id="music-url" type="text" placeholder="链接(http://ngcdn001.cnr.cn/live/zgzs/index.m3u8)"></input>
<div class="rows">
<input id="music-url" type="text" value="https://lhttp.qtfm.cn/live/4915/64k.mp3"></input>
<button id="playurl">播放链接</button>
</div>
<button id="playurl">播放链接</button>
<footer>
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>M3U to JSON Converter</title>
<link rel="stylesheet" type="text/css" href="/static/style.css">
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
<!--
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>

View File

@@ -3,9 +3,9 @@
<head>
<meta name="viewport" content="width=device-width">
<title>小爱音箱操控面板</title>
<script src="/static/jquery-3.7.1.min.js"></script>
<script src="/static/setting.js"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css">
<script src="/static/jquery-3.7.1.min.js?version=1721213953"></script>
<script src="/static/setting.js?version=1721213953"></script>
<link rel="stylesheet" type="text/css" href="/static/style.css?version=1721213953">
<!--
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
@@ -24,114 +24,126 @@ var vConsole = new window.VConsole();
<hr>
<div class="rows">
<label for="mi_did_hardware">*勾选设备(至少勾选1个):</label>
<div id="mi_did_hardware">
<label for="mi_did">*勾选设备(至少勾选1个):</label>
<div id="mi_did">
</div>
</div>
<br>
<div id="setting" class="rows">
<label for="account">*小米账号:</label>
<input id="account" type="text" placeholder="填写小米登录账号"></input>
<div id="setting">
<div class="rows">
<label for="account">*小米账号:</label>
<input id="account" type="text" placeholder="填写小米登录账号"></input>
<label for="password">*小米密码:</label>
<input id="password" type="password" placeholder="填写小米登录密码"></input>
<label for="password">*小米密码:</label>
<input id="password" type="password" placeholder="填写小米登录密码"></input>
<label for="hostname">*XIAOMUSIC_HOSTNAME(IP或域名):</label>
<input id="hostname" type="text"></input>
<label for="hostname">*XIAOMUSIC_HOSTNAME(IP或域名):</label>
<input id="hostname" type="text"></input>
<label for="verbose">是否开启调试日志:</label>
<select id="verbose">
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="verbose">是否开启调试日志:</label>
<select id="verbose">
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="music_path">音乐目录:</label>
<input id="music_path" type="text" value="music"></input>
<div>
<button class="save-button">保存</button>
<button onclick="location.href='/';">返回首页</button>
</div>
</div>
<hr>
<div class="rows">
<label for="group_list">设备分组配置:</label>
<input id="group_list" type="text" placeholder="did1:组名1,did2:组名1,did3:组名2"></input>
<label for="download_path">音乐下载目录(必须是music的子目录):</label>
<input id="download_path" type="text" value='music/download'></input>
<label for="music_path">音乐目录:</label>
<input id="music_path" type="text" value="music"></input>
<label for="conf_path">配置文件目录:</label>
<input id="conf_path" type="text"></input>
<label for="download_path">音乐下载目录(必须是music的子目录):</label>
<input id="download_path" type="text" value='music/download'></input>
<label for="ffmpeg_location">ffmpeg路径:</label>
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin"></input>
<label for="conf_path">配置文件目录:</label>
<input id="conf_path" type="text"></input>
<label for="log_file">日志路径:</label>
<input id="log_file" type="text" value="/tmp/xiaomusic.txt"></input>
<label for="ffmpeg_location">ffmpeg路径:</label>
<input id="ffmpeg_location" type="text" value="./ffmpeg/bin"></input>
<label for="active_cmd">允许唤醒的命令:</label>
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop"></input>
<label for="log_file">日志路径:</label>
<input id="log_file" type="text" value="/tmp/xiaomusic.txt"></input>
<label for="exclude_dirs">忽略目录(逗号分割):</label>
<input id="exclude_dirs" type="text" value="@eaDir"></input>
<label for="active_cmd">允许唤醒的命令:</label>
<input id="active_cmd" type="text" value="play,random_play,playlocal,play_music_list,stop"></input>
<label for="music_path_depth">目录深度:</label>
<input id="music_path_depth" type="number" value="10"></input>
<label for="exclude_dirs">忽略目录(逗号分割):</label>
<input id="exclude_dirs" type="text" value="@eaDir"></input>
<label for="search_prefix">XIAOMUSIC_SEARCH(歌曲下载方式):</label>
<select id="search_prefix">
<option value="bilisearch:">bilisearch:</option>
<option value="ytsearch:">ytsearch:</option>
</select>
<label for="music_path_depth">目录深度:</label>
<input id="music_path_depth" type="number" value="10"></input>
<label for="proxy">XIAOMUSIC_PROXY(ytsearch需要):</label>
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080"></input>
<label for="search_prefix">XIAOMUSIC_SEARCH(歌曲下载方式):</label>
<select id="search_prefix">
<option value="bilisearch:">bilisearch:</option>
<option value="ytsearch:">ytsearch:</option>
</select>
<label for="disable_httpauth">关闭密码验证:</label>
<select id="disable_httpauth">
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="httpauth_username">web控制台账户:</label>
<input id="httpauth_username" type="text" value=""></input>
<label for="httpauth_password">web控制台密码:</label>
<input id="httpauth_password" type="password" value=""></input>
<label for="proxy">XIAOMUSIC_PROXY(ytsearch需要):</label>
<input id="proxy" type="text" placeholder="http://192.168.2.5:8080"></input>
<label for="disable_download">关闭下载功能:</label>
<select id="disable_download">
<option value="true">true</option>
<option value="false" selected>false</option>
</select>
<label for="disable_httpauth">关闭密码验证:</label>
<select id="disable_httpauth">
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="httpauth_username">web控制台账户:</label>
<input id="httpauth_username" type="text" value=""></input>
<label for="httpauth_password">web控制台密码:</label>
<input id="httpauth_password" type="password" value=""></input>
<label for="use_music_audio_id">触屏版显示歌曲ID:</label>
<input id="use_music_audio_id" type="text" value="1582971365183456177"></input>
<label for="use_music_id">触屏版显示歌曲分段ID:</label>
<input id="use_music_id" type="text" value="355454500"></input>
<label for="disable_download">关闭下载功能:</label>
<select id="disable_download">
<option value="true">true</option>
<option value="false" selected>false</option>
</select>
<label for="fuzzy_match_cutoff">模糊匹配阈值(0.1~0.9):</label>
<input id="fuzzy_match_cutoff" type="number" value="0.6"></input>
<label for="use_music_audio_id">触屏版显示歌曲ID:</label>
<input id="use_music_audio_id" type="text" value="1582971365183456177"></input>
<label for="use_music_id">触屏版显示歌曲分段ID:</label>
<input id="use_music_id" type="text" value="355454500"></input>
<label for="enable_fuzzy_match">开启模糊搜索:</label>
<select id="enable_fuzzy_match">
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="fuzzy_match_cutoff">模糊匹配阈值(0.1~0.9):</label>
<input id="fuzzy_match_cutoff" type="number" value="0.6"></input>
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0"></input>
<label for="enable_fuzzy_match">开启模糊搜索:</label>
<select id="enable_fuzzy_match">
<option value="true" selected>true</option>
<option value="false">false</option>
</select>
<label for="stop_tts_msg">停止提示音:</label>
<input id="stop_tts_msg" type="text" value="收到,再见"></input>
<label for="keywords_playlocal">播放本地歌曲口令:</label>
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲"></input>
<label for="keywords_play">播放歌曲口令:</label>
<input id="keywords_play" type="text" value="播放歌曲,放歌曲"></input>
<label for="keywords_stop">停止口令:</label>
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放"></input>
<label for="public_port">外网访问端口(0表示跟监听端口一致):</label>
<input id="public_port" type="number" value="0"></input>
<label for="music_list_url">歌单地址:</label>
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json"></input>
<label for="stop_tts_msg">停止提示音:</label>
<input id="stop_tts_msg" type="text" value="收到,再见"></input>
<label for="keywords_playlocal">播放本地歌曲口令:</label>
<input id="keywords_playlocal" type="text" value="播放本地歌曲,本地播放歌曲"></input>
<label for="keywords_play">播放歌曲口令:</label>
<input id="keywords_play" type="text" value="播放歌曲,放歌曲"></input>
<label for="keywords_stop">停止口令:</label>
<input id="keywords_stop" type="text" value="关机,暂停,停止,停止播放"></input>
<label for="music_list_json">歌单内容:</label>
<textarea id="music_list_json" type="text"></textarea>
<label for="music_list_url">歌单地址:</label>
<input id="music_list_url" type="text" value="https://gist.githubusercontent.com/hanxi/dda82d964a28f8110f8fba81c3ff8314/raw/example.json"></input>
<label for="music_list_json">歌单内容:</label>
<textarea id="music_list_json" type="text"></textarea>
</div>
</div>
<hr>
<button onclick="location.href='/';">返回首页</button>
<button id="get_music_list">获取歌单</button>
<button id="save">保存</button>
<button class="save-button">保存</button>
<a class="button" href="/downloadlog" download="xiaomusic.txt">下载日志文件</a>
<hr>

View File

@@ -16,23 +16,22 @@ $(function(){
});
};
function updateCheckbox(selector, mi_did_list, mi_did, mi_hardware_list) {
function updateCheckbox(selector, mi_did, device_list) {
// 清除现有的内容
$(selector).empty();
// 将 mi_did 字符串通过逗号分割转换为数组,以便于判断默认选中项
var selected_dids = mi_did.split(',');
// 遍历传入的 mi_did_list 和 mi_hardware_list
$.each(mi_did_list, function(index, did) {
// 获取硬件标识,假定列表是一一对应的
var hardware = mi_hardware_list[index];
$.each(device_list, function(index, device) {
var did = device.miotDID;
var hardware = device.hardware;
var name = device.name;
// 创建复选框元素
var checkbox = $('<input>', {
type: 'checkbox',
id: did,
value: `${did}|${hardware}`,
value: `${did}`,
class: 'custom-checkbox', // 添加样式类
// 如果mi_did中包含了该did则默认选中
checked: selected_dids.indexOf(did) !== -1
@@ -42,7 +41,7 @@ $(function(){
var label = $('<label>', {
for: did,
class: 'checkbox-label', // 添加样式类
text: `${hardware} ${did}` // 设定标签内容为did和hardware的拼接
text: `${hardware} ${did}${name}` // 设定标签内容
});
// 将复选框和标签添加到目标选择器元素中
@@ -50,29 +49,22 @@ $(function(){
});
}
function getSelectedDidsAndHardware(containerSelector) {
function getSelectedDids(containerSelector) {
var selectedDids = [];
var selectedHardware = [];
// 仅选择给定容器中选中的复选框
$(containerSelector + ' .custom-checkbox:checked').each(function() {
// 解析当前复选框的值(值中包含了 did 和 hardware使用 '|' 分割)
var parts = this.value.split('|');
selectedDids.push(parts[0]);
selectedHardware.push(parts[1]);
var did = this.value;
selectedDids.push(did);
});
// 返回包含 did_list 和 hardware_list 的对象
return {
did_list: selectedDids.join(','),
hardware_list: selectedHardware.join(',')
};
return selectedDids.join(',');
}
// 拉取现有配置
$.get("/getsetting", function(data, status) {
$.get("/getsetting?need_device_list=true", function(data, status) {
console.log(data, status);
updateCheckbox("#mi_did_hardware", data.mi_did_list, data.mi_did, data.mi_hardware_list);
updateCheckbox("#mi_did", data.mi_did, data.device_list);
// 初始化显示
for (const key in data) {
@@ -93,7 +85,7 @@ $(function(){
autoSelectOne();
});
$("#save").on("click", () => {
$(".save-button").on("click", () => {
var setting = $('#setting');
var inputs = setting.find('input, select, textarea');
var data = {};
@@ -103,9 +95,8 @@ $(function(){
data[id] = $(this).val();
}
});
var selectedData = getSelectedDidsAndHardware("#mi_did_hardware");
data["mi_did"] = selectedData.did_list;
data["hardware"] = selectedData.hardware_list;
var did_list = getSelectedDids("#mi_did");
data["mi_did"] = did_list;
console.log(data)
$.ajax({
@@ -115,6 +106,7 @@ $(function(){
data: JSON.stringify(data),
success: (msg) => {
alert(msg);
location.reload();
},
error: (msg) => {
alert(msg);

View File

@@ -29,35 +29,6 @@ input,select {
height: 40px;
}
.container{
width: 280px;
overflow: hidden;
display: inline-block;
}
@keyframes text-scroll {
0% {
left: 100%;
}
25% {
left: 50%;
}
50% {
left: 0%;
}
75% {
left: -50%;
}
100% {
left: -100%;
}
}
.text {
white-space: nowrap;
font-weight: bold;
position: relative;
animation: text-scroll 10s linear infinite;
}
.rows {
display: flex;
flex-direction: column;
@@ -78,40 +49,31 @@ footer {
height: 200px;
}
/* 隐藏原生复选框 */
.custom-checkbox {
display: none;
display: inline-block;
margin: 10px;
width: 20px;
height: 20px;
vertical-align: middle; /* 确保与标签垂直居中对齐 */
}
/* 修改后的自定义复选框外观 */
.checkbox-label {
display: inline-block;
width: 200px; /* 宽度 */
height: 20px; /* 高度 */
background-color: #fff; /* 背景颜色 */
border: 0px solid #ccc; /* 边框 */
border-radius: 3px; /* 圆角边框 */
position: relative; /* 设置相对定位 */
cursor: pointer; /* 鼠标形状 */
padding-left: 40px; /* 给左边的复选框图标留下空位 */
width: 260px;
background-color: #fff;
border: 0px solid #ccc;
border-radius: 3px;
position: relative;
cursor: pointer;
vertical-align: middle; /* 确保与复选框垂直居中对齐 */
margin-left: 1px; /* 给复选框和标签之间一些距离,如果需要的话 */
}
/* 对勾的样式 */
.custom-checkbox:checked + .checkbox-label::after {
content: '✔';
position: absolute;
left: 10px; /* 对勾图标靠左侧位置 */
color: #000; /* 对勾颜色 */
font-size: 18px; /* 对勾字体大小,视清晰度需调整 */
.text {
margin: 10px;
width: 150px;
height: 50px;
text-align: center;
text-decoration: none;
display: inline-block;
}
/* 标签文本样式,使用 ::before 伪元素表示复选框未选中时的样式 */
.custom-checkbox + .checkbox-label::before {
content: '⬜'; /* 表示未选中时的复选框样式,这里用了白色方块 */
position: absolute;
left: 8px; /* 方块图标靠左侧位置 */
top: 1px; /* 方块图标顶部位置 */
color: #000; /* 方块颜色 */
font-size: 18px; /* 方块字体大小,视清晰度需调整 */
}

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import copy
import difflib
import mimetypes
import os
import random
import re
@@ -14,7 +15,6 @@ from urllib.parse import urlparse
import aiohttp
import mutagen
import requests
from requests.utils import cookiejar_from_dict
from xiaomusic.const import SUPPORT_MUSIC_TYPE
@@ -73,12 +73,21 @@ def validate_proxy(proxy_str: str) -> bool:
# 模糊搜索
def fuzzyfinder(user_input, collection):
return difflib.get_close_matches(user_input, collection, n=10, cutoff=0.1)
lower_collection = {item.lower(): item for item in collection}
user_input = user_input.lower()
matches = difflib.get_close_matches(
user_input, lower_collection.keys(), n=10, cutoff=0.1
)
return [lower_collection[match] for match in matches]
def find_best_match(user_input, collection, cutoff=0.6):
matches = difflib.get_close_matches(user_input, collection, n=1, cutoff=cutoff)
return matches[0] if matches else None
lower_collection = {item.lower(): item for item in collection}
user_input = user_input.lower()
matches = difflib.get_close_matches(
user_input, lower_collection.keys(), n=1, cutoff=cutoff
)
return lower_collection[matches[0]] if matches else None
# 歌曲排序
@@ -127,11 +136,23 @@ def _append_files_result(result, root, joinpath, files, support_extension):
result[dir_name].append(os.path.join(joinpath, file))
def custom_walk(top, followlinks=True):
for root, dirs, files in os.walk(top, followlinks=False):
yield root, dirs, files
for dir in dirs:
path = os.path.join(root, dir)
if followlinks and os.path.islink(path):
# 如果启用 followlinks 并且是符号链接,获取其实际路径
real_path = os.readlink(path)
yield from custom_walk(real_path, followlinks=followlinks)
def traverse_music_directory(
directory, depth=10, exclude_dirs=None, support_extension=None
):
result = {}
for root, dirs, files in os.walk(directory):
for root, dirs, files in custom_walk(directory):
# 忽略排除的目录
dirs[:] = [d for d in dirs if d not in exclude_dirs]
@@ -145,24 +166,35 @@ def traverse_music_directory(
return result
def downloadfile(url):
async def downloadfile(url):
# 清理和验证URL
# 解析URL
parsed_url = urlparse(url)
# 基础验证仅允许HTTP和HTTPS协议
if parsed_url.scheme not in ("http", "https"):
raise Warning(
f"Invalid URL scheme: {parsed_url.scheme}. Only HTTP and HTTPS are allowed."
)
# 构建目标URL
cleaned_url = parsed_url.geturl()
# 发起请求
response = requests.get(cleaned_url, timeout=5) # 增加超时以避免长时间挂起
response.raise_for_status() # 如果响应不是200引发HTTPError异常
return response.text
# 使用 aiohttp 创建一个客户端会话来发起请求
async with aiohttp.ClientSession() as session:
async with session.get(
cleaned_url, timeout=5
) as response: # 增加超时以避免长时间挂起
# 如果响应不是200引发异常
response.raise_for_status()
# 读取响应文本
text = await response.text()
return text
def is_mp3(url):
mt = mimetypes.guess_type(url)
if mt and mt[0] == "audio/mpeg":
return True
return False
async def _get_web_music_duration(session, url, start=0, end=500):
@@ -175,7 +207,10 @@ async def _get_web_music_duration(session, url, start=0, end=500):
name = tmp.name
try:
m = mutagen.File(name)
if is_mp3(url):
m = mutagen.mp3.MP3(name)
else:
m = mutagen.File(name)
duration = m.info.length
except Exception:
pass
@@ -217,7 +252,10 @@ async def get_web_music_duration(url, start=0, end=500):
def get_local_music_duration(filename):
duration = 0
try:
m = mutagen.File(filename)
if is_mp3(filename):
m = mutagen.mp3.MP3(filename)
else:
m = mutagen.File(filename)
duration = m.info.length
except Exception:
pass
@@ -253,3 +291,19 @@ def deepcopy_data_no_sensitive_info(data, fields_to_anonymize=None):
setattr(copy_data, field, "******")
return copy_data
# k1:v1,k2:v2
def parse_str_to_dict(s, d1=",", d2=":"):
# 初始化一个空字典
result = {}
parts = s.split(d1)
for part in parts:
# 根据冒号切割
subparts = part.split(d2)
if len(subparts) == 2: # 防止数据不是成对出现
k, v = subparts
result[k] = v
return result

File diff suppressed because it is too large Load Diff