feat: 开放小爱音箱补丁固件构建源代码和教程
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# 排除全部文件
|
||||
**/*
|
||||
|
||||
# 只保留构建相关的文件
|
||||
!src
|
||||
!package.json
|
||||
!package-lock.json
|
||||
!tsconfig.json
|
||||
@@ -0,0 +1,9 @@
|
||||
# 你的小米账号/密码
|
||||
MI_USER=23333333
|
||||
MI_PASS=xxxxxxxxx
|
||||
|
||||
# 你的小爱音箱名称/DID
|
||||
MI_DID=小爱音箱Pro
|
||||
|
||||
# 你的 SSH 登录密码(默认为 open-xiaoai)
|
||||
SSH_PASSWORD=open-xiaoai
|
||||
@@ -0,0 +1,3 @@
|
||||
temp
|
||||
assets/*
|
||||
!assets/.gitkeep
|
||||
@@ -0,0 +1,20 @@
|
||||
FROM node:22-alpine
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||
&& echo $TZ > /etc/timezone \
|
||||
&& apk add --no-cache \
|
||||
file \
|
||||
patch \
|
||||
python3 \
|
||||
openssl \
|
||||
squashfs-tools
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install
|
||||
|
||||
CMD ["npm", "run", "build"]
|
||||
@@ -0,0 +1,87 @@
|
||||
# OpenXiaoAI Patch
|
||||
|
||||
> [!CAUTION]
|
||||
> 刷机有风险,操作需谨慎。
|
||||
|
||||
小爱音箱 Pro 补丁固件制作流程:
|
||||
- 固件提取(登录小米账号获取 OTA 链接)
|
||||
- 开启固化 SSH(支持自定义登录密码)
|
||||
- 禁用系统自动更新(系统更新后需要重新刷机打补丁)
|
||||
- 添加开机启动脚本 `/data/init.sh`(方便执行一些初始化脚本)
|
||||
|
||||
## 准备条件
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 本教程仅适用于 **小爱音箱 Pro(LX06)** 和 **Xiaomi 智能音箱 Pro(OH2P)** 这两款机型,**其他型号**的小爱音箱可能存在兼容性问题,请勿直接使用!🚨
|
||||
|
||||
|
||||
- Windows 电脑(需为 x86_64 架构,用于刷机)
|
||||
- 数据线(不能只是充电线,需要连接到电脑传输数据)
|
||||
- Type-C(适用于新款小爱音箱 Pro,无需拆机)
|
||||
- Micro USB(旧款小爱音箱 Pro 用这种,需要拆机)
|
||||
|
||||
|
||||
## 下载固件
|
||||
|
||||
> [!NOTE]
|
||||
> 构建版本待发布,敬请期待。
|
||||
|
||||
|
||||
你可以直接在 Github Release 页面下载打包好的补丁固件。
|
||||
|
||||
> 默认 SSH 登录密码为 `open-xiaoai`
|
||||
|
||||
## 制作固件
|
||||
|
||||
或者你也可以按照下面的 2 种方法,制作自定义固件。
|
||||
|
||||
### 基础配置
|
||||
|
||||
修改 `.env.example` 文件里的配置,然后重命名为 `.env`。
|
||||
|
||||
```shell
|
||||
# 你的小米账号/密码
|
||||
MI_USER=23333333
|
||||
MI_PASS=xxxxxxxxx
|
||||
|
||||
# 你的小爱音箱名称/DID
|
||||
MI_DID=小爱音箱Pro
|
||||
|
||||
# 你的 SSH 登录密码(默认为 open-xiaoai)
|
||||
SSH_PASSWORD=open-xiaoai
|
||||
```
|
||||
|
||||
### 1. 使用 Docker 打包固件(推荐)
|
||||
|
||||
> [!NOTE]
|
||||
> Docker 镜像待发布,敬请期待。
|
||||
|
||||
```shell
|
||||
# 使用 Docker 进行构建
|
||||
docker run -it --rm --env-file $(pwd)/.env -v $(pwd)/assets:/app/assets -v $(pwd)/patches:/app/patches open-xiaoai
|
||||
|
||||
# ✅ 打包完成,固件文件已复制到 assets 目录...
|
||||
# /app/assets/mico_all_92db90ed6_1.88.197/root.squashfs
|
||||
```
|
||||
|
||||
### 2. 本地构建(macOS、Linux)
|
||||
|
||||
为了能够正常编译运行该项目,你需要安装以下依赖环境:
|
||||
|
||||
- Docker:https://www.docker.com/get-started/
|
||||
- Python:https://www.python.org/downloads/
|
||||
- Node.js: https://nodejs.org/zh-cn/download
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 打包固件
|
||||
npm run build
|
||||
|
||||
# ✅ 打包成功后,原始固件和补丁固件会保存在 assets 目录下
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> 如果你想要更进一步的定制自己的固件,可以参考 `src/build.sh` 脚本里的构建流程:在提取固件后自行修改固件内的脚本、配置和应用程序,然后重新打包即可。
|
||||
|
||||
Generated
+893
@@ -0,0 +1,893 @@
|
||||
{
|
||||
"name": "open-xiaoai-patch",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "open-xiaoai-patch",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@mi-gpt/miot": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mi-gpt/config": "1.0.0",
|
||||
"@types/node": "^22.13.11",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
|
||||
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
|
||||
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
|
||||
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
|
||||
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
|
||||
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
|
||||
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
|
||||
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
|
||||
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
|
||||
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
|
||||
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
|
||||
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@mi-gpt/config": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@mi-gpt/config/-/config-1.0.0.tgz",
|
||||
"integrity": "sha512-oo/roYiRcGiHsZESjukwPTsRFqddShTJ8gkccR5ZMYn8SySj07A0nCsEsLEfB9mLkvNY8D6PCO1NxbRDDRBBlA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@mi-gpt/miot": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@mi-gpt/miot/-/miot-1.0.0.tgz",
|
||||
"integrity": "sha512-smWhddHzK1du3/LW2FifBrIIyeZNx/RvTSe0Zi4v3lYZwK8NeEwomg1jBjktQCCV4Fh8OuqNbCdNbmiVXmr5kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mi-gpt/utils": "1.0.0",
|
||||
"axios": "^1.8.4",
|
||||
"pako": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@mi-gpt/utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@mi-gpt/utils/-/utils-1.0.0.tgz",
|
||||
"integrity": "sha512-hAU/uYPNiNJgXWkQrVqRLe/9Uz8AgtoiNwcbbwQs8iXJcyq3NjcSHkkwUseJAnaQFE9iv/kvHZZsx3r2HhlKjw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
|
||||
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
|
||||
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.2",
|
||||
"@esbuild/android-arm": "0.25.2",
|
||||
"@esbuild/android-arm64": "0.25.2",
|
||||
"@esbuild/android-x64": "0.25.2",
|
||||
"@esbuild/darwin-arm64": "0.25.2",
|
||||
"@esbuild/darwin-x64": "0.25.2",
|
||||
"@esbuild/freebsd-arm64": "0.25.2",
|
||||
"@esbuild/freebsd-x64": "0.25.2",
|
||||
"@esbuild/linux-arm": "0.25.2",
|
||||
"@esbuild/linux-arm64": "0.25.2",
|
||||
"@esbuild/linux-ia32": "0.25.2",
|
||||
"@esbuild/linux-loong64": "0.25.2",
|
||||
"@esbuild/linux-mips64el": "0.25.2",
|
||||
"@esbuild/linux-ppc64": "0.25.2",
|
||||
"@esbuild/linux-riscv64": "0.25.2",
|
||||
"@esbuild/linux-s390x": "0.25.2",
|
||||
"@esbuild/linux-x64": "0.25.2",
|
||||
"@esbuild/netbsd-arm64": "0.25.2",
|
||||
"@esbuild/netbsd-x64": "0.25.2",
|
||||
"@esbuild/openbsd-arm64": "0.25.2",
|
||||
"@esbuild/openbsd-x64": "0.25.2",
|
||||
"@esbuild/sunos-x64": "0.25.2",
|
||||
"@esbuild/win32-arm64": "0.25.2",
|
||||
"@esbuild/win32-ia32": "0.25.2",
|
||||
"@esbuild/win32-x64": "0.25.2"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
|
||||
"integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.19.3",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz",
|
||||
"integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "open-xiaoai-patch",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"ota": "tsx --env-file=.env src/ota.ts",
|
||||
"extract": "sh src/extract.sh",
|
||||
"patch": "sh src/patch.sh",
|
||||
"squashfs": "sh src/squashfs.sh",
|
||||
"build": "sh src/build.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mi-gpt/miot": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mi-gpt/config": "1.0.0",
|
||||
"@types/node": "^22.13.11",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
--- squashfs-root/etc/init.d/dropbear 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/init.d/dropbear 2025-02-13 21:19:31
|
||||
@@ -53,8 +53,7 @@
|
||||
MaxAuthTries mdns ipaddrs
|
||||
|
||||
validate_section_dropbear "${1}" || {
|
||||
- echo "validation failed"
|
||||
- return 1
|
||||
+ echo "validation failed and skip"
|
||||
}
|
||||
|
||||
[ -n "${Interface}" ] && {
|
||||
@@ -64,7 +63,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- [ "${enable}" = "0" ] && return 1
|
||||
PIDCOUNT="$(( ${PIDCOUNT} + 1))"
|
||||
local pid_file="/var/run/${NAME}.${PIDCOUNT}.pid"
|
||||
|
||||
@@ -111,25 +109,12 @@
|
||||
load_interfaces()
|
||||
{
|
||||
config_get interface "$1" Interface
|
||||
- config_get enable "$1" enable 1
|
||||
-
|
||||
- [ "${enable}" = "1" ] && interfaces=" ${interface} ${interfaces}"
|
||||
+ interfaces=" ${interface} ${interfaces}"
|
||||
}
|
||||
|
||||
start_service()
|
||||
{
|
||||
- [ x"bluetooth" == x`micocfg_work_mode` ] && {
|
||||
- logger -t dropbear.init.d -p 3 "in bluetooth mode, will not start"
|
||||
- return
|
||||
- }
|
||||
-
|
||||
- ssh_en=`cat /data/ssh_en 2>/dev/null`
|
||||
- ssh_en_bind=`cat /data/.ssh_en 2>/dev/null`
|
||||
- ssh_en_tmp=`cat /tmp/ssh_en 2>/dev/null`
|
||||
- channel=`micocfg_channel 2>/dev/null`
|
||||
- if [ "$ssh_en" != "1" -a "$ssh_en_bind" != "1" -a "$ssh_en_tmp" != "1" -a "$channel" = "release" ]; then
|
||||
- return 0
|
||||
- fi
|
||||
+ mkdir -p /data/etc/dropbear
|
||||
[ -s /data/etc/dropbear/dropbear_rsa_host_key ] || keygen
|
||||
|
||||
. /lib/functions.sh
|
||||
@@ -0,0 +1,35 @@
|
||||
--- squashfs-root/etc/shadow 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/shadow 2025-02-13 21:07:41
|
||||
@@ -1,4 +1,4 @@
|
||||
-root:$1$N0Iz0LLs$kZ5zG/Y2AUWHNE5I3ElWm1:18128:0:99999:7:::
|
||||
+root:{SSH_PASSWORD}:18128:0:99999:7:::
|
||||
daemon:*:0:0:99999:7:::
|
||||
ftp:*:0:0:99999:7:::
|
||||
network:*:0:0:99999:7:::
|
||||
diff --color -Naur squashfs-root/etc/inittab squashfs-root-patched/etc/inittab
|
||||
--- squashfs-root/etc/inittab 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/inittab 2025-02-13 20:56:13
|
||||
@@ -1,5 +1,5 @@
|
||||
::sysinit:/etc/init.d/rcS S boot
|
||||
::shutdown:/etc/init.d/rcS K shutdown
|
||||
tts/0::askfirst:/bin/ash --login
|
||||
-ttyS0::askfirst:/bin/login
|
||||
+ttyS0::askfirst:/bin/ash --login
|
||||
tty1::askfirst:/bin/ash --login
|
||||
diff --color -Naur squashfs-root/etc/pam.d/common-auth squashfs-root-patched/etc/pam.d/common-auth
|
||||
--- squashfs-root/etc/pam.d/common-auth 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/pam.d/common-auth 2025-02-13 21:09:02
|
||||
@@ -8,10 +8,10 @@
|
||||
# traditional Unix authentication mechanisms.
|
||||
#
|
||||
|
||||
-auth sufficient libmico-pam.so
|
||||
-account sufficient libmico-pam.so
|
||||
+# auth sufficient libmico-pam.so
|
||||
+# account sufficient libmico-pam.so
|
||||
# here are the per-package modules (the "Primary" block)
|
||||
-# auth [success=1 default=ignore] pam_unix.so nullok_secure
|
||||
+auth [success=1 default=ignore] pam_unix.so nullok_secure
|
||||
# here's the fallback if no module succeeds
|
||||
auth requisite pam_deny.so
|
||||
# prime the stack with a positive return value if there isn't one already;
|
||||
@@ -0,0 +1,46 @@
|
||||
--- squashfs-root/bin/ota 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/bin/ota 2025-02-13 21:50:40
|
||||
@@ -133,6 +133,9 @@
|
||||
|
||||
|
||||
download_upgrade() {
|
||||
+ logger -t ota -p 3 "download upgrade failed"
|
||||
+ return 1
|
||||
+
|
||||
if [ -f $OTA_FILE ]; then
|
||||
logger -t ota -p 3 "ota is in progress now, exit this one."
|
||||
exit 1
|
||||
@@ -278,6 +281,9 @@
|
||||
}
|
||||
|
||||
upgrade() {
|
||||
+ logger -t ota -p 3 "no upgrade"
|
||||
+ return 1
|
||||
+
|
||||
if [ -z "$1" -o -z "$2" ]; then
|
||||
upgrade_failed
|
||||
return 1
|
||||
--- squashfs-root/bin/flash.sh 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/bin/flash.sh 2025-02-18 23:21:57
|
||||
@@ -5,6 +5,9 @@
|
||||
. /bin/boardupgrade.sh
|
||||
|
||||
upgrade_param_check() {
|
||||
+ klogger "no upgrade"
|
||||
+ return 1
|
||||
+
|
||||
if [ -z "$1" -o ! -f "$1" ]; then
|
||||
klogger "USAGE: $0 input.bin [1: slient] [1:don't reboot]"
|
||||
return 1
|
||||
--- squashfs-root/etc/init.d/wireless 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/init.d/wireless 2025-02-18 23:15:31
|
||||
@@ -1361,6 +1361,9 @@
|
||||
password=$3
|
||||
identity=$4
|
||||
|
||||
+ echo "download_fail"
|
||||
+ exit 3;
|
||||
+
|
||||
trylock "ota"
|
||||
[ $? != 0 ] && {
|
||||
# shut_led 10
|
||||
@@ -0,0 +1,9 @@
|
||||
--- squashfs-root/etc/rc.local 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/rc.local 2025-02-13 22:06:13
|
||||
@@ -1,4 +1,6 @@
|
||||
# Put your custom commands here that should be executed once
|
||||
# the system init finished. By default this file does nothing.
|
||||
|
||||
+[ -f "/data/init.sh" ] && /data/init.sh &
|
||||
+
|
||||
exit 0
|
||||
@@ -0,0 +1,33 @@
|
||||
--- squashfs-root/etc/asound.conf 2024-12-06 15:11:58
|
||||
+++ squashfs-root-patched/etc/asound.conf 2025-02-18 22:14:10
|
||||
@@ -125,16 +125,20 @@
|
||||
}
|
||||
|
||||
pcm.noop {
|
||||
- type dsnoop
|
||||
- ipc_key 1024
|
||||
- slave {
|
||||
- pcm "hw:0,3"
|
||||
- format S32_LE
|
||||
- rate 48000
|
||||
- channels 8
|
||||
- period_size 480
|
||||
- buffer_size 3840
|
||||
- }
|
||||
+ type plug
|
||||
+ slave.pcm {
|
||||
+ type dsnoop
|
||||
+ ipc_key 1024
|
||||
+ ipc_perm 0666
|
||||
+ slave {
|
||||
+ pcm "hw:0,3"
|
||||
+ format S32_LE
|
||||
+ rate 48000
|
||||
+ channels 8
|
||||
+ period_size 480
|
||||
+ buffer_size 3840
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
defaults.pcm.rate_converter "speexrate_medium"
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 动态获取偏移量
|
||||
OFFSET=$(strings -t d usr/lib/libxaudio_engine.so | grep hw:0,3 | awk '{print $1}')
|
||||
|
||||
# 检查偏移量是否存在
|
||||
if [ -z "$OFFSET" ]; then
|
||||
echo "未找到 \"hw:0,3\" 字符串偏移量,取消 patch usr/lib/libxaudio_engine.so"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
echo -n -e "noop\x00\x00" | dd of=usr/lib/libxaudio_engine.so bs=1 count=6 seek=$OFFSET conv=notrunc
|
||||
|
||||
echo "patched file usr/lib/libxaudio_engine.so"
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# 下载固件
|
||||
if [ -z "$CI" ]; then
|
||||
npm run ota
|
||||
else
|
||||
npx tsx src/ota.ts
|
||||
fi
|
||||
|
||||
# 提取固件
|
||||
npm run extract
|
||||
|
||||
# 打补丁
|
||||
npm run patch
|
||||
|
||||
# 打包固件
|
||||
npm run squashfs
|
||||
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Source: https://github.com/csftech/Xiaomi-OpenWrt-firmware-toolkit
|
||||
# Author: @csftech (Sheng-Fu Chang)
|
||||
# MIT Licensed
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import ctypes
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
class ImageHeader(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("magic", ctypes.c_uint),
|
||||
("signature_offset", ctypes.c_uint),
|
||||
("crc32_checksum", ctypes.c_uint),
|
||||
("file_type", ctypes.c_ushort),
|
||||
("model", ctypes.c_ushort),
|
||||
("segment_offsets", ctypes.c_uint * 8),
|
||||
]
|
||||
|
||||
|
||||
class SegmentHeader(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("magic", ctypes.c_uint),
|
||||
("flash_address", ctypes.c_uint),
|
||||
("length", ctypes.c_uint),
|
||||
("partition", ctypes.c_uint),
|
||||
("segment_name", ctypes.c_char * 32),
|
||||
]
|
||||
|
||||
|
||||
class Firmware:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.image_header = ImageHeader()
|
||||
self.fd = open(self.path, "rb")
|
||||
|
||||
def verify(self, ignore_hash=False):
|
||||
logging.info("[Jobs] Verifying firmware image...")
|
||||
assert self.fd.readinto(self.image_header) == ctypes.sizeof(self.image_header)
|
||||
|
||||
# magic
|
||||
logging.info(f"firmware magic: {hex(self.image_header.magic)}")
|
||||
assert self.image_header.magic in [0x31524448, 0x32524448]
|
||||
|
||||
if self.image_header.magic == 0x32524448:
|
||||
logging.info("NOTE: firmware seems to be encrypted?")
|
||||
|
||||
# signature
|
||||
self.fd.seek(self.image_header.signature_offset)
|
||||
signature_length = int.from_bytes(self.fd.read(16), "little")
|
||||
signature = self.fd.read()
|
||||
logging.info(
|
||||
f"firmware signature_offset: {hex(self.image_header.signature_offset)}"
|
||||
)
|
||||
logging.info(f"firmware signature_length: {signature_length}")
|
||||
logging.info(f"firmware signature: {signature.hex()}")
|
||||
assert len(signature) == signature_length
|
||||
|
||||
# crc32
|
||||
self.fd.seek(12)
|
||||
logging.info(
|
||||
f"firmware crc32_checksum: {hex(self.image_header.crc32_checksum)}"
|
||||
)
|
||||
computed_checksum = ~binascii.crc32(self.fd.read()) & 0xFFFFFFFF
|
||||
logging.info(f"computed crc32_checksum: {hex(computed_checksum)}")
|
||||
assert self.image_header.crc32_checksum == computed_checksum
|
||||
|
||||
# md5
|
||||
self.fd.seek(0)
|
||||
m = hashlib.md5()
|
||||
m.update(self.fd.read())
|
||||
hash = m.hexdigest()
|
||||
logging.info(f"computed md5 hash: {hash}")
|
||||
filename_hash = os.path.basename(self.path).split("_")[-2]
|
||||
try:
|
||||
assert hash[(len(filename_hash) * -1) :] == filename_hash
|
||||
except AssertionError:
|
||||
if ignore_hash:
|
||||
logging.warning(
|
||||
f'Warning: hash "{filename_hash}" does not match expected!'
|
||||
)
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
self.fd.seek(0)
|
||||
return True
|
||||
|
||||
def extract(self, dest: str = None):
|
||||
logging.info("[Jobs] Extracting firmware...")
|
||||
current_time = time.strftime("%Y%m%d_%H%M%S", time.localtime())
|
||||
self.dest_dir = os.path.join(
|
||||
dest, f'{os.path.basename(self.path).replace(".bin", "")}_{current_time}'
|
||||
)
|
||||
if dest is not None:
|
||||
self.dest_dir = dest
|
||||
os.mkdir(self.dest_dir)
|
||||
logging.info(f"create destination directory: {self.dest_dir}")
|
||||
|
||||
for address in self.image_header.segment_offsets:
|
||||
if address:
|
||||
self.fd.seek(address)
|
||||
segment_header = SegmentHeader()
|
||||
assert self.fd.readinto(segment_header) == ctypes.sizeof(segment_header)
|
||||
|
||||
# extract segment
|
||||
with open(
|
||||
os.path.join(self.dest_dir, segment_header.segment_name.decode()),
|
||||
"wb",
|
||||
) as s:
|
||||
s.write(self.fd.read(segment_header.length))
|
||||
logging.info(
|
||||
f'extracting segment: {segment_header.segment_name.decode("ascii")}'
|
||||
)
|
||||
|
||||
|
||||
def run(path, extract=False, dest=None, ignore_hash=False):
|
||||
logging.info(f"[MSG] Input file: {path}")
|
||||
firmware = Firmware(path)
|
||||
if firmware.verify(ignore_hash):
|
||||
logging.info(
|
||||
"[Jobs] Verification success: it's a genuine firmware from Xiaomi."
|
||||
)
|
||||
|
||||
if extract:
|
||||
firmware.extract(dest)
|
||||
logging.info("[Jobs] Extraction complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dest",
|
||||
help="Destination directory to store extracted files, default=current working directory",
|
||||
action="store",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--ignore-hash",
|
||||
help="Do not fail if md5 hash does not match",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument("-e", "--extract", help="Input your firmware", action="store")
|
||||
parser.add_argument("-s", "--show", help="Show firmware info", action="store")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.extract:
|
||||
firmware_path = os.path.abspath(args.extract)
|
||||
dest_path = os.getcwd()
|
||||
if args.dest:
|
||||
dest_path = args.dest
|
||||
run(firmware_path, extract=True, dest=dest_path, ignore_hash=args.ignore_hash)
|
||||
elif args.show:
|
||||
firmware_path = os.path.abspath(args.show)
|
||||
run(firmware_path, ignore_hash=args.ignore_hash)
|
||||
else:
|
||||
parser.print_help()
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
BASE_DIR=$(pwd)
|
||||
WORK_DIR=$BASE_DIR/temp
|
||||
|
||||
FIRMWARE=$(basename $(ls $BASE_DIR/assets/*.bin 2>/dev/null | head -n 1) .bin)
|
||||
|
||||
if [ ! -f "$BASE_DIR/assets/$FIRMWARE.bin" ]; then
|
||||
echo "❌ 固件文件不存在,请先下载固件到:$BASE_DIR/assets/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$WORK_DIR" && mkdir -pv "$WORK_DIR" && cd $WORK_DIR
|
||||
|
||||
python3 $BASE_DIR/src/extract.py -e "$BASE_DIR/assets/$FIRMWARE.bin" -d "$WORK_DIR/$FIRMWARE"
|
||||
|
||||
ln -sf $WORK_DIR/$FIRMWARE/root.squashfs $WORK_DIR/root.squashfs
|
||||
|
||||
unsquashfs $WORK_DIR/root.squashfs
|
||||
@@ -0,0 +1,261 @@
|
||||
import { getMiNA } from "@mi-gpt/miot";
|
||||
import { createHash } from "node:crypto";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as stream from "node:stream";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const kSupportedDevices = [
|
||||
"LX06", // 小爱音箱 Pro
|
||||
"OH2P", // Xiaomi 智能音箱 Pro
|
||||
];
|
||||
|
||||
// 获取 OTA 信息
|
||||
async function getOTA(channel: "release" | "current" | "stable" = "release") {
|
||||
const MiNA = await getMiNA({
|
||||
userId: process.env.MI_USER!,
|
||||
password: process.env.MI_PASS!,
|
||||
});
|
||||
if (!MiNA) {
|
||||
console.log(`❌ 登录失败,请检查小米账号密码是否正确:`);
|
||||
console.log(`当前账号:${process.env.MI_USER}`);
|
||||
console.log(`当前密码:${process.env.MI_PASS}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const devices = await MiNA.getDevices();
|
||||
const speaker = devices?.find(
|
||||
(e: any) =>
|
||||
e.name === process.env.MI_DID || e.miotDID === process.env.MI_DID
|
||||
);
|
||||
if (!speaker) {
|
||||
console.log(`❌ 找不到设备: ${process.env.MI_DID}`);
|
||||
console.log(`可用设备列表:`);
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
devices.map((e: any) => ({
|
||||
name: e.name,
|
||||
did: e.miotDID,
|
||||
})),
|
||||
null,
|
||||
4
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!kSupportedDevices.includes(speaker.hardware)) {
|
||||
console.log(
|
||||
`❌ 暂不支持当前设备型号: ${speaker.hardware}(${speaker.name})`
|
||||
);
|
||||
console.log(`可用设备列表:`);
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
devices
|
||||
.filter((e: any) => kSupportedDevices.includes(e.hardware))
|
||||
.map((e: any) => ({
|
||||
name: e.name,
|
||||
did: e.miotDID,
|
||||
})),
|
||||
null,
|
||||
4
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const sn = speaker.serialNumber;
|
||||
const model = speaker.hardware;
|
||||
const version = speaker.romVersion;
|
||||
const time = new Date().getTime();
|
||||
const otaInfo = `channel=${channel}&filterID=${sn}&locale=zh_CN&model=${model}&time=${time}&version=${version}&8007236f-a2d6-4847-ac83-c49395ad6d65`;
|
||||
const base64Str = Buffer.from(otaInfo).toString("base64");
|
||||
const code = createHash("md5").update(base64Str).digest("hex");
|
||||
|
||||
return {
|
||||
sn,
|
||||
model,
|
||||
version,
|
||||
url: `http://api.miwifi.com/rs/grayupgrade/v2/${model}?model=${model}&version=${version}&channel=${channel}&filterID=${sn}&locale=zh_CN&time=${time}&s=${code}`,
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`🔥 正在获取设备信息...`);
|
||||
const ota = await getOTA();
|
||||
if (!ota) {
|
||||
console.log(`❌ 获取设备信息失败`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`🔥 正在获取 OTA 信息...`);
|
||||
const res = await fetch(ota.url);
|
||||
const data = await res.json();
|
||||
if (data.code === "0" && data.data) {
|
||||
if (data.data.currentInfo) {
|
||||
console.log("\n=== 当前版本固件 ===\n");
|
||||
const filePath = await downloadFirmware(data.data.currentInfo);
|
||||
if (filePath) {
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), "assets", ".model"),
|
||||
ota.model
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), "assets", ".version"),
|
||||
ota.version
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`❌ 获取固件信息失败: ${data.code || "未知错误"}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
// 下载固件
|
||||
async function downloadFirmware(firmware: {
|
||||
link: string;
|
||||
hash: string;
|
||||
toVersion?: string;
|
||||
size?: number;
|
||||
}): Promise<string | undefined> {
|
||||
if (!firmware || !firmware.link) {
|
||||
console.log("❌ 无效的固件信息");
|
||||
return;
|
||||
}
|
||||
|
||||
const assetsDir = path.join(process.cwd(), "assets");
|
||||
|
||||
// 从链接中提取文件名
|
||||
const url = new URL(firmware.link);
|
||||
const filename = path.basename(url.pathname);
|
||||
|
||||
// 打印固件信息
|
||||
console.log(`- 版本: ${firmware.toVersion || "未知"}`);
|
||||
console.log(
|
||||
`- 大小: ${
|
||||
firmware.size ? (firmware.size / 1024 / 1024).toFixed(2) + "MB" : "未知"
|
||||
}`
|
||||
);
|
||||
console.log(`- 文件: ${filename}`);
|
||||
console.log(`- MD5: ${firmware.hash || "未知"}\n`);
|
||||
|
||||
try {
|
||||
const filePath = await downloadFile(firmware.link, assetsDir, filename);
|
||||
return filePath;
|
||||
} catch (error) {
|
||||
console.error(`❌ 下载固件失败: ${error}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureDir(dirPath: string): Promise<void> {
|
||||
try {
|
||||
await fs.promises.access(dirPath);
|
||||
} catch (error) {
|
||||
await fs.promises.mkdir(dirPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(
|
||||
url: string,
|
||||
destDir: string,
|
||||
filename: string
|
||||
): Promise<string> {
|
||||
await ensureDir(destDir);
|
||||
|
||||
const destPath = path.join(destDir, filename);
|
||||
|
||||
// 检查文件是否已存在
|
||||
try {
|
||||
await fs.promises.access(destPath);
|
||||
console.log(`ℹ️ 文件已存在: ${destPath}`);
|
||||
return destPath;
|
||||
} catch (error) {
|
||||
// 文件不存在,下载它
|
||||
console.log(`⬇️ 开始下载: ${url}`);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
const contentLength = response.headers.get("content-length");
|
||||
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
|
||||
|
||||
// 创建文件写入流
|
||||
const fileStream = fs.createWriteStream(destPath);
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error("下载失败: 无法获取响应体");
|
||||
}
|
||||
|
||||
// 创建进度显示变量
|
||||
let downloadedBytes = 0;
|
||||
let lastLoggedPercent = -1;
|
||||
let startTime = Date.now();
|
||||
|
||||
// 使用 TransformStream 来跟踪下载进度
|
||||
const progressStream = new stream.Transform({
|
||||
transform(chunk, encoding, callback) {
|
||||
downloadedBytes += chunk.length;
|
||||
|
||||
if (totalSize) {
|
||||
const percent = Math.floor((downloadedBytes / totalSize) * 100);
|
||||
|
||||
// 确保每增加1%才打印一次,避免日志过多
|
||||
if (percent > lastLoggedPercent) {
|
||||
lastLoggedPercent = percent;
|
||||
|
||||
// 格式化输出
|
||||
const downloaded = (downloadedBytes / 1024 / 1024).toFixed(2);
|
||||
const total = (totalSize / 1024 / 1024).toFixed(2);
|
||||
|
||||
// 使用\r使光标回到行首,实现同行更新
|
||||
process.stdout.write(
|
||||
`\r下载进度: ${percent}% | ${downloaded}MB/${total}MB`
|
||||
);
|
||||
|
||||
// 如果下载完成,打印换行符
|
||||
if (downloadedBytes === totalSize) {
|
||||
process.stdout.write("\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 无法获取总大小时,只显示已下载大小
|
||||
if (downloadedBytes % (1024 * 1024) === 0) {
|
||||
// 每1MB打印一次
|
||||
const downloaded = (downloadedBytes / 1024 / 1024).toFixed(2);
|
||||
process.stdout.write(`\r已下载: ${downloaded}MB`);
|
||||
}
|
||||
}
|
||||
|
||||
this.push(chunk);
|
||||
callback();
|
||||
},
|
||||
});
|
||||
|
||||
// 完成后清理并打印最终结果
|
||||
progressStream.on("end", () => {
|
||||
const totalTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
||||
console.log(`\n✅ 下载完成: ${destPath}`);
|
||||
console.log(
|
||||
` 总大小: ${(downloadedBytes / 1024 / 1024).toFixed(
|
||||
2
|
||||
)}MB, 用时: ${totalTime}秒`
|
||||
);
|
||||
});
|
||||
|
||||
// 使用pipeline连接流
|
||||
await promisify(stream.pipeline)(
|
||||
// @ts-ignore - response.body 在 Node.js 中是 ReadableStream
|
||||
response.body,
|
||||
progressStream,
|
||||
fileStream
|
||||
);
|
||||
|
||||
return destPath;
|
||||
}
|
||||
}
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
BASE_DIR=$(pwd)
|
||||
WORK_DIR=$BASE_DIR/temp
|
||||
|
||||
PASSWORD=${SSH_PASSWORD:-"open-xiaoai"}
|
||||
PASSWORD=$(openssl passwd -1 -salt "open-xiaoai" "$PASSWORD")
|
||||
|
||||
# 应用指定目录下的补丁文件
|
||||
apply_patches() {
|
||||
local patch_dir="$1"
|
||||
local message="$2"
|
||||
|
||||
echo "🔥 $message"
|
||||
|
||||
if [ -d "$patch_dir" ]; then
|
||||
for file in "$patch_dir"/*; do
|
||||
if [ -f "$file" ]; then
|
||||
if [[ "$file" == *.patch ]]; then
|
||||
# 创建临时文件用于占位符替换
|
||||
local temp_patch=$(mktemp)
|
||||
# 将补丁文件中的 {SSH_PASSWORD} 替换为 PASSWORD
|
||||
sed "s/{SSH_PASSWORD}/$PASSWORD/g" "$file" > "$temp_patch"
|
||||
# 应用替换后的补丁
|
||||
patch -p1 < "$temp_patch"
|
||||
# 清理临时文件
|
||||
rm "$temp_patch"
|
||||
elif [[ "$file" == *.sh ]]; then
|
||||
sh "$file"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
if [ ! -f "$BASE_DIR/assets/.model" ]; then
|
||||
echo "❌ 固件信息不存在,请先下载固件到:$BASE_DIR/assets/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATCH_DIR=$BASE_DIR/patches
|
||||
MODEL=$(cat $BASE_DIR/assets/.model)
|
||||
|
||||
cd $WORK_DIR/squashfs-root
|
||||
|
||||
# 应用通用补丁
|
||||
apply_patches "$PATCH_DIR" "正在应用通用补丁..."
|
||||
|
||||
# 应用特定型号补丁
|
||||
apply_patches "$PATCH_DIR/$MODEL" "正在应用 $MODEL 补丁..."
|
||||
|
||||
echo "✅ 补丁应用完成"
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
BASE_DIR=$(pwd)
|
||||
WORK_DIR=$BASE_DIR/temp
|
||||
|
||||
FIRMWARE=$(basename $(ls $BASE_DIR/assets/*.bin 2>/dev/null | head -n 1) .bin)
|
||||
|
||||
cd $WORK_DIR
|
||||
|
||||
if [ ! -f "$BASE_DIR/assets/$FIRMWARE.bin" ]; then
|
||||
echo "❌ 固件文件不存在,请先下载固件到:$BASE_DIR/assets/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$FIRMWARE" ]; then
|
||||
echo "❌ 解压后的固件文件夹不存在,请先提取固件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SQUASHFS_INFO=$(file $FIRMWARE/root.squashfs)
|
||||
echo "🚗 原始固件信息: $SQUASHFS_INFO"
|
||||
|
||||
COMPRESSION=$(echo "$SQUASHFS_INFO" | grep -o "xz\|gzip\|lzo\|lz4\|zstd compressed" | cut -d' ' -f1)
|
||||
BLOCKSIZE=$(echo "$SQUASHFS_INFO" | grep -o "blocksize: [0-9]* bytes" | cut -d' ' -f2)
|
||||
|
||||
echo "🔥 使用原始参数重新打包固件..."
|
||||
mksquashfs squashfs-root $FIRMWARE/root.squashfs \
|
||||
-comp $COMPRESSION -b $BLOCKSIZE \
|
||||
-noappend -all-root -always-use-fragments -no-xattrs -no-exports
|
||||
|
||||
cp -rf $FIRMWARE $BASE_DIR/assets/$FIRMWARE
|
||||
|
||||
echo "✅ 打包完成,固件文件已复制到 assets 目录..."
|
||||
echo $BASE_DIR/assets/$FIRMWARE/root.squashfs
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@mi-gpt/config/typescript",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user