feat: add one-click installers and Windows launcher

This commit is contained in:
Alex
2026-05-24 23:07:03 +03:00
parent 186139f796
commit a70439194e
18 changed files with 870 additions and 720 deletions
+9
View File
@@ -0,0 +1,9 @@
# Enforce CRLF for Windows batch files to prevent CMD parsing bugs on multi-byte UTF-8
*.bat text eol=crlf
*.cmd text eol=crlf
# Enforce LF for shell scripts
*.sh text eol=lf
# Explicitly set default behavior for other files
* text=auto
+4
View File
@@ -20,3 +20,7 @@ status*.json
*.zip *.zip
*.tar *.tar
*.tar.gz *.tar.gz
# Generated launchers
run_local.sh
run_local.bat
+126 -117
View File
@@ -2,186 +2,195 @@
Русский | [English](README_EN.md) Русский | [English](README_EN.md)
Личный Telegram-бот для данных Xiaomi Fitness / Mi Band. Личный self-hosted Telegram-бот для данных Xiaomi Fitness / Mi Band.
Он сам забирает шаги, сон, пульс и SpO2 из Xiaomi Fitness, складывает их в локальную SQLite-базу и показывает понятное меню в Telegram. Идея простая: данные с браслета остаются у вас на сервере, а Telegram становится удобной кнопкой «посмотреть здоровье за сегодня», «обновить вручную» или «выгрузить CSV». Забирает шаги, сон, пульс и SpO2 из облака Xiaomi Fitness, хранит их
в локальной SQLite-базе и даёт доступ к ним прямо из Telegram —
без сторонних сервисов и без передачи данных третьим лицам.
Проект рассчитан на одного владельца. Это не публичный бот для многих пользователей и не медицинский сервис. > **Проект рассчитан на одного владельца.**
> Это не публичный бот и не медицинский сервис.
## Что умеет ## Возможности
- Показывает последние шаги, сон, пульс и SpO2 в Telegram. - Просмотр последних шагов, сна, пульса и SpO2 в Telegram.
- Запускает ручную синхронизацию кнопкой в меню. - Ручная и автоматическая синхронизация по расписанию.
- Автоматически синхронизирует данные по расписанию. - Хранение истории в SQLite (`data/`).
- Хранит историю в SQLite в папке `data/`. - Экспорт всех таблиц в ZIP с CSV-файлами прямо в чат.
- Экспортирует накопленные таблицы в ZIP с CSV-файлами. - Развёртывание через Docker Compose.
- Работает через Docker Compose. - Атомарная запись Xiaomi-токена с правами `0600`.
- Пишет Xiaomi token атомарно с правами `0600`. - Умная автопривязка к первому пользователю (whitelist).
## Кому подходит
Проект подойдет, если вы:
- пользуетесь Xiaomi Fitness / Mi Band;
- хотите видеть свои данные в Telegram;
- готовы запустить маленький self-hosted сервис;
- понимаете, что неофициальные API могут сломаться после изменений Xiaomi.
Проект не подойдет, если нужен многопользовательский SaaS, медицинская точность, гарантия совместимости со всеми браслетами или официальный Xiaomi API.
## Важно про reverse engineering
`miband-bot` - неофициальный проект. Он не связан с Xiaomi, Zepp, Huami, Telegram или их партнерами.
Доступ к данным Xiaomi Fitness сделан через reverse engineering неофициальных API. Это значит:
- Xiaomi может изменить API без предупреждения;
- вход или синхронизация могут временно перестать работать;
- используйте проект только для своих аккаунтов и своих данных;
- соблюдайте применимые законы и условия сервисов в вашей стране;
- данные браслета не являются медицинским заключением.
## Как это работает ## Как это работает
```text ```text
Mi Band -> Xiaomi Fitness cloud -> miband-bot -> SQLite -> Telegram menu / CSV export Mi Band Xiaomi Fitness cloud miband-bot SQLite Telegram / CSV
``` ```
В Docker Compose запускаются два процесса: Docker Compose запускает два процесса:
- `tracker` - периодически синхронизирует данные из Xiaomi Fitness; - `tracker` периодически синхронизирует данные из Xiaomi Fitness;
- `fitness-bot` - отвечает в Telegram, показывает меню, запускает ручной sync и экспорт. - `fitness-bot` обслуживает Telegram-меню, ручной sync и экспорт.
Оба процесса используют одну папку `./data`. Запись защищена файловым lock, поэтому фоновая и ручная синхронизация не пишут в SQLite/token одновременно. Оба процесса работают с одной папкой `./data`. Конкурентная запись
исключена файловым lock-ом.
## Что понадобится ## Требования
- Сервер или домашняя машина с Docker и Docker Compose. - Docker и Docker Compose (или установленный Python 3.10+).
- Telegram bot token от [@BotFather](https://t.me/BotFather). - Telegram bot token от [@BotFather](https://t.me/BotFather).
- Ваш Telegram user id. - Аккаунт Xiaomi с данными Xiaomi Fitness.
- Xiaomi аккаунт, в котором видны данные Xiaomi Fitness.
## Быстрый запуск ## Быстрый запуск
1. Скопируйте пример секретов: ### Способ 1: Бесшовная установка в один клик (Рекомендуется)
```sh Если у вас еще нет проекта на компьютере, вы можете автоматически скачать и настроить его одной командой в терминале:
cp secrets.env.example secrets.env
```
2. Заполните минимум эти переменные: - **macOS / Linux:**
```sh
curl -fsSL https://raw.githubusercontent.com/iAlexeyRu/miband-bot/main/install.sh | bash
```
- **Windows (PowerShell):**
```powershell
powershell -c "irm https://raw.githubusercontent.com/iAlexeyRu/miband-bot/main/install.ps1 | iex"
```
```env Установщик сам создаст папку `miband-bot`, загрузит и распакует файлы проекта, проверит окружение и запустит интерактивную настройку!
TELEGRAM_BOT_TOKEN=123456:replace-me Повторный запуск этой же PowerShell-команды в уже настроенной установке обновит файлы и сразу запустит бота без повторного ввода токена.
TELEGRAM_ALLOWED_USER_ID=123456789
```
3. Запустите сервис: ---
```sh ### Способ 2: Запуск из скачанной папки
docker compose up -d --build
docker compose logs -f fitness-bot
```
4. Откройте своего Telegram-бота и отправьте `/start`. Если вы уже склонировали репозиторий через `git clone` или скачали архив вручную:
5. Бот покажет кнопку входа в Xiaomi. Подтвердите вход по ссылке/QR. После этого бот сохранит `data/token_<telegram_user_id>.json`, запустит первую синхронизацию и откроет главное меню. - **macOS / Linux:**
```sh
./setup.sh
```
- **Windows:**
Запустите двойным кликом файл `setup.bat` или выполните в консоли:
```cmd
setup.bat
```
Скрипт сам проверит окружение, пошагово поможет получить токен, создаст конфигурацию `secrets.env`, развернет окружение Python (если выбран запуск без Docker) и предложит запустить бота одной кнопкой.
После настройки бот можно запускать повторно через `run_local.bat` из папки `miband-bot`.
---
### Способ 3: Полностью ручная настройка (manual setup):
1. Скопируйте шаблон конфигурации:
```sh
cp secrets.env.example secrets.env
```
2. Укажите ваш `TELEGRAM_BOT_TOKEN` в файле `secrets.env`. Переменную `TELEGRAM_ALLOWED_USER_ID` **оставьте пустой** — бот автоматически привяжется к вам при первом старте.
3. Запустите Docker контейнеры:
```sh
docker compose up -d --build
```
4. Откройте вашего созданного бота в Telegram и отправьте ему команду `/start` — бот распознает ваш аккаунт, привяжет его как единственного владельца и начнет синхронизацию!
## Настройки ## Настройки
Основные переменные лежат в `secrets.env`: Все переменные в `secrets.env`:
```env | Переменная | По умолчанию | Описание |
TELEGRAM_BOT_TOKEN=123456:replace-me | -------------------------- | ------------ | --------------------------------------- |
TELEGRAM_ALLOWED_USER_ID=123456789 | `TELEGRAM_BOT_TOKEN` | — | Token Telegram-бота |
SYNC_INTERVAL=900 | `TELEGRAM_ALLOWED_USER_ID` | — | Разрешённый user id (оставьте пустым для автопривязки) |
QUERY_DURATION=2 | `SYNC_INTERVAL` | `900` | Интервал фоновой синхронизации, секунды |
ENABLE_FDS_SLEEP_DETAILS=true | `QUERY_DURATION` | `2` | Глубина запроса при sync, дней |
``` | `ENABLE_FDS_SLEEP_DETAILS` | `true` | Загружать детальные ночные данные FDS |
- `TELEGRAM_BOT_TOKEN` - token вашего Telegram-бота. Пути к базе и статусу заданы в `compose.yaml`. При запуске без Docker
- `TELEGRAM_ALLOWED_USER_ID` - единственный Telegram user id, которому разрешен доступ. смотрите `secrets.env.example`.
- `SYNC_INTERVAL` - интервал фоновой синхронизации в секундах. `900` = 15 минут.
- `QUERY_DURATION` - сколько последних дней запрашивать при sync.
- `ENABLE_FDS_SLEEP_DETAILS` - пробовать ли загружать детальные ночные данные FDS.
Пути к базе и статусу уже заданы в `compose.yaml`. Если запускаете без Docker, смотрите `secrets.env.example`. ## Файлы данных
## Где лежат данные
Runtime-файлы создаются в `./data`: Runtime-файлы создаются в `./data`:
- `token_<telegram_user_id>.json` - Xiaomi auth token, секретный файл; | Файл | Содержимое |
- `miband_<telegram_user_id>.db` - SQLite-база с health-данными; | ---------------------- | --------------------------------- |
- `status_<telegram_user_id>.json` - последний статус синхронизации; | `token_<id>.json` | Xiaomi auth token (**секретный**) |
- `fitness_bot_state.db` - служебное состояние Telegram-меню; | `miband_<id>.db` | SQLite-база с health-данными |
- `sync_<telegram_user_id>.lock` - lock-файл синхронизации. | `status_<id>.json` | Последний статус синхронизации |
| `allowed_user.id` | ID привязанного владельца |
| `fitness_bot_state.db` | Служебное состояние Telegram-меню |
| `sync_<id>.lock` | Lock-файл синхронизации |
Не коммитьте `secrets.env`, `data/`, `*.db`, `token*.json` и `status*.json`. Эти файлы уже добавлены в `.gitignore`. `secrets.env`, `data/`, `*.db`, `token*.json` и `status*.json`
добавлены в `.gitignore` — не коммитьте их.
## Команды бота ## Команды
- `/start` - открыть меню или начать вход в Xiaomi. | Команда | Действие |
- `/sync` - запустить ручную синхронизацию. | --------- | ------------------------------------- |
- `/status` - показать состояние локальной базы. | `/start` | Открыть меню или начать вход в Xiaomi |
| `/sync` | Запустить ручную синхронизацию |
Основное управление происходит кнопками в Telegram-меню. | `/status` | Показать состояние локальной базы |
## Экспорт CSV
В меню есть экспорт данных. Бот собирает ZIP с CSV-таблицами и отправляет его в Telegram.
Помните: ZIP с health-данными уходит через инфраструктуру Telegram. Не отправляйте экспорт в чужие чаты и не храните его там, где доступ есть у других людей.
## Локальная разработка ## Локальная разработка
```sh ```sh
python3 -m venv .venv python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt -e mi-fitness-python .venv/bin/pip install -r requirements-dev.txt -e mi-fitness-python
.venv/bin/python -m py_compile fitness_bot.py miband_sync.py $(find miband_tracker -name '*.py' | sort) .venv/bin/python -m py_compile fitness_bot.py miband_sync.py \
$(find miband_tracker -name '*.py' | sort)
.venv/bin/python -m pytest .venv/bin/python -m pytest
.venv/bin/python -m pytest mi-fitness-python/tests/unit .venv/bin/python -m pytest mi-fitness-python/tests/unit
.venv/bin/ruff check . .venv/bin/ruff check .
.venv/bin/python -m pip check .venv/bin/python -m pip check
``` ```
Entrypoints сохранены для Docker и локального запуска: Точки входа:
```sh ```sh
python -u miband_sync.py python -u miband_sync.py # или: miband-sync
python -u fitness_bot.py python -u fitness_bot.py # или: miband-fitness-bot
```
Если проект установлен как Python package, доступны console scripts:
```sh
miband-sync
miband-fitness-bot
``` ```
## Troubleshooting ## Troubleshooting
**Бот не отвечает.** **Бот не отвечает** — проверьте `TELEGRAM_BOT_TOKEN`, логи, а также убедитесь, что вы первыми отправили `/start` боту для привязки. При необходимости сбросить привязанного владельца просто удалите файл `data/allowed_user.id` и отправьте `/start` снова.
Проверьте `TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USER_ID` и логи:
```sh ```sh
docker compose logs -f fitness-bot docker compose logs -f fitness-bot
``` ```
**Синхронизация пишет, что token не найден.** **Token не найден** — отправьте `/start` и пройдите Xiaomi login flow.
Откройте бота в Telegram, отправьте `/start` и пройдите Xiaomi login flow.
**Token истек.** **Token истёк** — запустите повторный вход из меню; старый файл
В меню запустите повторный вход в Xiaomi. Старый token можно удалить из `data/`. можно удалить из `data/`.
**Данных мало или нет SpO2/деталей сна.** **Нет SpO2 или деталей сна** — убедитесь, что эти данные отображаются
Проверьте, что Xiaomi Fitness реально показывает эти данные. Часть данных зависит от модели браслета, настроек шаринга и доступности неофициального API. в самом приложении Xiaomi Fitness. Доступность зависит от модели
браслета и настроек шаринга.
**После обновления Xiaomi все сломалось.** **После обновления Xiaomi всё сломалось** — это ожидаемый риск
Это ожидаемый риск reverse-engineering проекта. Проверьте issues/README и логи, затем обновите код или временно отключите проблемную часть. при работе с неофициальным API. Проверьте issues и логи, затем
обновите код или временно отключите проблемный модуль.
## Лицензия и vendored SDK ## Важно: reverse engineering и ограничения
Проект распространяется под GNU GPL v3.0 or later. Полный текст лицензии лежит в [LICENSE](LICENSE). `miband-bot` — неофициальный проект, не связанный с Xiaomi, Zepp,
Huami или Telegram.
SDK `mi-fitness-python` хранится в репозитории как vendored source copy и остается под своей GNU GPL v3.0 лицензией: [mi-fitness-python/LICENSE](mi-fitness-python/LICENSE). Подробности о происхождении и политике обновления описаны в [VENDORED.md](VENDORED.md). Доступ к данным реализован через reverse engineering закрытых API,
поэтому:
- Xiaomi может изменить API без предупреждения;
- авторизация или синхронизация могут временно не работать;
- используйте проект только со своими аккаунтами и данными;
- соблюдайте законодательство и условия использования сервисов;
- данные браслета не являются медицинским заключением.
## Лицензия
Проект распространяется под [GNU GPL v3.0 or later](LICENSE).
SDK `mi-fitness-python` включён как vendored source copy под
[GNU GPL v3.0](mi-fitness-python/LICENSE). Подробности — в
[VENDORED.md](VENDORED.md).
+122 -115
View File
@@ -2,186 +2,193 @@
[Русский](README.md) | English [Русский](README.md) | English
A personal Telegram bot for Xiaomi Fitness / Mi Band data. A personal self-hosted Telegram bot for your Xiaomi Fitness / Mi Band data.
It fetches steps, sleep, heart rate and SpO2 from Xiaomi Fitness, stores the history in a local SQLite database, and shows a practical Telegram menu. The goal is simple: your band data stays on your own server, while Telegram becomes a convenient place to check today's health snapshot, run a manual sync or export CSV files. Fetches steps, sleep, heart rate, and SpO2 from the Xiaomi Fitness cloud,
stores them in a local SQLite database, and provides access to them directly from Telegram —
without third-party services and without sharing your data with anyone.
The project is designed for one owner. It is not a public multi-user bot and not a medical service. > **This project is designed for a single owner.**
> This is not a public bot or a medical service.
## What It Does ## Features
- Shows recent steps, sleep, heart rate and SpO2 in Telegram. - View recent steps, sleep, heart rate, and SpO2 in Telegram.
- Runs manual sync from a Telegram button. - Manual and scheduled automatic synchronization.
- Syncs data automatically on a schedule. - History storage in SQLite (`data/`).
- Stores history in SQLite under `data/`. - Export of all tables to a ZIP archive with CSV files directly into the chat.
- Exports accumulated tables as a ZIP with CSV files. - Deployment via Docker Compose.
- Runs with Docker Compose. - Atomic writing of the Xiaomi token with `0600` permissions.
- Writes the Xiaomi token atomically with mode `0600`. - Smart auto-binding to the first user (whitelist).
## Who It Is For ## How it works
This project is useful if you:
- use Xiaomi Fitness / Mi Band;
- want to see your own data in Telegram;
- are comfortable running a small self-hosted service;
- understand that unofficial APIs can break when Xiaomi changes something.
It is not a good fit if you need a multi-user SaaS, medical-grade accuracy, guaranteed compatibility with every band, or an official Xiaomi API.
## Reverse Engineering Notice
`miband-bot` is an unofficial project. It is not affiliated with Xiaomi, Zepp, Huami, Telegram or their partners.
Xiaomi Fitness access is based on reverse engineering of unofficial APIs. This means:
- Xiaomi can change the API without notice;
- login or sync can temporarily stop working;
- use the project only with your own accounts and your own data;
- follow applicable laws and service terms in your jurisdiction;
- wearable data is not a medical diagnosis.
## How It Works
```text ```text
Mi Band -> Xiaomi Fitness cloud -> miband-bot -> SQLite -> Telegram menu / CSV export Mi Band Xiaomi Fitness cloud miband-bot SQLite Telegram / CSV
``` ```
Docker Compose starts two processes: Docker Compose runs two processes:
- `tracker` - periodically syncs data from Xiaomi Fitness; - `tracker` periodically synchronizes data from Xiaomi Fitness;
- `fitness-bot` - responds in Telegram, shows the menu, starts manual sync and exports data. - `fitness-bot` — serves the Telegram menu, handles manual sync, and performs exports.
Both processes share `./data`. Writes are protected by a file lock, so background sync and manual sync do not write SQLite/token files at the same time. Both processes work with the same `./data` folder. Concurrent write access
is prevented by a file-based lock.
## Requirements ## Requirements
- A server or home machine with Docker and Docker Compose. - Docker and Docker Compose (or installed Python 3.10+).
- A Telegram bot token from [@BotFather](https://t.me/BotFather). - Telegram bot token from [@BotFather](https://t.me/BotFather).
- Your Telegram user id.
- A Xiaomi account with Xiaomi Fitness data. - A Xiaomi account with Xiaomi Fitness data.
## Quick Start ## Quick Start
1. Copy the secrets template: ### Method 1: Seamless One-Click Installation (Recommended)
```sh If you don't have the project files on your machine yet, you can automatically download and set everything up using a single command in your terminal:
cp secrets.env.example secrets.env
```
2. Fill at least these variables: - **macOS / Linux:**
```sh
curl -fsSL https://raw.githubusercontent.com/iAlexeyRu/miband-bot/main/install.sh | bash
```
- **Windows (PowerShell):**
```powershell
powershell -c "irm https://raw.githubusercontent.com/iAlexeyRu/miband-bot/main/install.ps1 | iex"
```
```env The installer will automatically create a `miband-bot` directory, download and extract the project files, verify dependencies, and launch the interactive setup!
TELEGRAM_BOT_TOKEN=123456:replace-me Running the same PowerShell command again on an already configured install will update the files and start the bot without asking for the Telegram token again.
TELEGRAM_ALLOWED_USER_ID=123456789
```
3. Start the service: ---
```sh ### Method 2: Launch from Downloaded Directory
docker compose up -d --build
docker compose logs -f fitness-bot
```
4. Open your Telegram bot and send `/start`. If you have already cloned the repository via `git clone` or downloaded the ZIP archive manually:
5. The bot will show a Xiaomi login button. Confirm login through the link/QR flow. After that the bot saves `data/token_<telegram_user_id>.json`, runs the first sync and opens the main menu. - **macOS / Linux:**
```sh
./setup.sh
```
- **Windows:**
Double-click the `setup.bat` file or run it in the console:
```cmd
setup.bat
```
## Configuration The script will automatically check your environment, guide you step-by-step to get your Telegram bot token, create the `secrets.env` configuration, set up the Python virtual environment (if you choose to run without Docker), and let you launch the bot with a single key press!
After setup, you can start the bot again with `run_local.bat` from the `miband-bot` folder.
Main variables live in `secrets.env`: ---
```env ### Method 3: Fully Manual Setup:
TELEGRAM_BOT_TOKEN=123456:replace-me
TELEGRAM_ALLOWED_USER_ID=123456789
SYNC_INTERVAL=900
QUERY_DURATION=2
ENABLE_FDS_SLEEP_DETAILS=true
```
- `TELEGRAM_BOT_TOKEN` - your Telegram bot token. 1. Copy the configuration template:
- `TELEGRAM_ALLOWED_USER_ID` - the single Telegram user id allowed to access the bot. ```sh
- `SYNC_INTERVAL` - background sync interval in seconds. `900` = 15 minutes. cp secrets.env.example secrets.env
- `QUERY_DURATION` - how many recent days to query on each sync. ```
- `ENABLE_FDS_SLEEP_DETAILS` - whether to try fetching detailed FDS sleep data. 2. Specify your `TELEGRAM_BOT_TOKEN` in the `secrets.env` file. **Leave the `TELEGRAM_ALLOWED_USER_ID` variable blank** — the bot will automatically bind to you upon the first start.
3. Start the Docker containers:
```sh
docker compose up -d --build
```
4. Open your created bot in Telegram and send the `/start` command — the bot will recognize your account, bind it as the sole owner, and begin synchronization!
Database and status paths are already set in `compose.yaml`. If you run without Docker, see `secrets.env.example`. ## Settings
## Where Data Lives All variables are in `secrets.env`:
Runtime files are created under `./data`: | Variable | Default | Description |
| --- | --- | --- |
| `TELEGRAM_BOT_TOKEN` | — | Telegram bot token |
| `TELEGRAM_ALLOWED_USER_ID` | — | Allowed user ID (leave empty for auto-binding) |
| `SYNC_INTERVAL` | `900` | Background sync interval, in seconds |
| `QUERY_DURATION` | `2` | Fetch depth during sync, in days |
| `ENABLE_FDS_SLEEP_DETAILS` | `true` | Download detailed FDS night sleep data |
- `token_<telegram_user_id>.json` - Xiaomi auth token, secret file; Paths to the database and status files are defined in `compose.yaml`. For running without Docker, refer to `secrets.env.example`.
- `miband_<telegram_user_id>.db` - SQLite database with health data;
- `status_<telegram_user_id>.json` - latest sync status;
- `fitness_bot_state.db` - Telegram menu state;
- `sync_<telegram_user_id>.lock` - sync lock file.
Do not commit `secrets.env`, `data/`, `*.db`, `token*.json` or `status*.json`. These files are already listed in `.gitignore`. ## Data Files
## Bot Commands Runtime files are created in `./data`:
- `/start` - open the menu or start Xiaomi login. | File | Content |
- `/sync` - run manual sync. | --- | --- |
- `/status` - show local database status. | `token_<id>.json` | Xiaomi auth token (**secret**) |
| `miband_<id>.db` | SQLite database with health data |
| `status_<id>.json` | Last sync status |
| `allowed_user.id` | ID of the bound owner |
| `fitness_bot_state.db` | Telegram menu internal state |
| `sync_<id>.lock` | Sync lock file |
Most actions are done with buttons in the Telegram menu. `secrets.env`, `data/`, `*.db`, `token*.json`, and `status*.json`
are added to `.gitignore` — do not commit them.
## CSV Export ## Commands
The menu includes data export. The bot builds a ZIP with CSV tables and sends it through Telegram. | Command | Action |
| --- | --- |
Remember: the ZIP contains health data and travels through Telegram infrastructure. Do not send it to shared chats or store it where other people can access it. | `/start` | Open menu or start Xiaomi login flow |
| `/sync` | Start manual synchronization |
| `/status` | Show local database status |
## Local Development ## Local Development
```sh ```sh
python3 -m venv .venv python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt -e mi-fitness-python .venv/bin/pip install -r requirements-dev.txt -e mi-fitness-python
.venv/bin/python -m py_compile fitness_bot.py miband_sync.py $(find miband_tracker -name '*.py' | sort) .venv/bin/python -m py_compile fitness_bot.py miband_sync.py \
$(find miband_tracker -name '*.py' | sort)
.venv/bin/python -m pytest .venv/bin/python -m pytest
.venv/bin/python -m pytest mi-fitness-python/tests/unit .venv/bin/python -m pytest mi-fitness-python/tests/unit
.venv/bin/ruff check . .venv/bin/ruff check .
.venv/bin/python -m pip check .venv/bin/python -m pip check
``` ```
Entrypoints are kept for Docker and local compatibility: Entry points:
```sh ```sh
python -u miband_sync.py python -u miband_sync.py # or: miband-sync
python -u fitness_bot.py python -u fitness_bot.py # or: miband-fitness-bot
```
If installed as a Python package, console scripts are available:
```sh
miband-sync
miband-fitness-bot
``` ```
## Troubleshooting ## Troubleshooting
**The bot does not respond.** **The bot does not respond** — check `TELEGRAM_BOT_TOKEN`, check the logs, and make sure you were the first to send `/start` to the bot to bind it. If you need to reset the bound owner, simply delete the file `data/allowed_user.id` and send `/start` again.
Check `TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USER_ID` and logs:
```sh ```sh
docker compose logs -f fitness-bot docker compose logs -f fitness-bot
``` ```
**Sync says token is missing.** **Token not found** — send `/start` and complete the Xiaomi login flow.
Open the bot in Telegram, send `/start` and complete the Xiaomi login flow.
**The token expired.** **Token expired** — start a re-login from the menu; the old file
Run Xiaomi login again from the menu. You can remove the old token from `data/`. can be deleted from `data/`.
**Some data is missing or there is no SpO2/sleep detail.** **No SpO2 or sleep details** — make sure this data is visible
Check that Xiaomi Fitness itself shows that data. Some data depends on the band model, sharing settings and unofficial API availability. in the Xiaomi Fitness app itself. Availability depends on the band model
and data sharing settings.
**Everything broke after a Xiaomi update.** **Everything broke after a Xiaomi update** — this is an expected risk
That is an expected risk for a reverse-engineering project. Check issues/README and logs, then update the code or temporarily disable the affected feature. when working with unofficial APIs. Check issues and logs, then
update the code or temporarily disable the problematic module.
## License And Vendored SDK ## Important: Reverse Engineering and Limitations
This project is licensed under GNU GPL v3.0 or later. The full license text is in [LICENSE](LICENSE). `miband-bot` is an unofficial project, not affiliated with Xiaomi, Zepp,
Huami, or Telegram.
The `mi-fitness-python` SDK is kept in this repository as a vendored source copy and remains under its own GNU GPL v3.0 license: [mi-fitness-python/LICENSE](mi-fitness-python/LICENSE). Origin and update policy are documented in [VENDORED.md](VENDORED.md). Data access is implemented via reverse engineering of closed APIs,
therefore:
- Xiaomi may change the API without warning;
- authorization or synchronization may temporarily stop working;
- use this project only with your own accounts and data;
- comply with applicable laws and services' terms of use;
- wristband data is not a medical opinion.
## License
The project is distributed under the [GNU GPL v3.0 or later](LICENSE).
SDK `mi-fitness-python` is included as a vendored source copy under
[GNU GPL v3.0](mi-fitness-python/LICENSE). Details are in [VENDORED.md](VENDORED.md).
+5 -1
View File
@@ -4,7 +4,11 @@
from __future__ import annotations from __future__ import annotations
from miband_tracker.bot.app import main from miband_tracker.stdio import configure_utf8_stdio
configure_utf8_stdio()
from miband_tracker.bot.app import main # noqa: E402
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+92
View File
@@ -0,0 +1,92 @@
# Отключаем показ прогресс-баров (скрывает спам при скачивании)
$ProgressPreference = 'SilentlyContinue'
# Кодировка UTF-8 для корректного вывода русских символов
$OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Write-Host "=== Установка miband-bot ===" -ForegroundColor Blue
function Test-ConfiguredInstall {
param([string]$ProjectPath)
$secretsPath = Join-Path $ProjectPath "secrets.env"
if (-not (Test-Path -LiteralPath $secretsPath)) {
return $false
}
try {
return [bool](Select-String -LiteralPath $secretsPath -Pattern '^TELEGRAM_BOT_TOKEN=.+$' -Quiet)
} catch {
return $true
}
}
# Проверяем, запущен ли скрипт в защищенной системной папке (например, System32 или C:\Windows)
if ($PWD.Path -like "*\system32*" -or $PWD.Path -eq $env:SystemRoot) {
Write-Host "Предупреждение: Вы находитесь в защищенной системной папке ($($PWD.Path))." -ForegroundColor Yellow
Write-Host "Чтобы избежать ошибок доступа, переключаемся в вашу домашнюю папку..." -ForegroundColor Yellow
Set-Location -Path $env:USERPROFILE
Write-Host "Новый путь установки: $($PWD.Path)\miband-bot`n" -ForegroundColor Gray
}
$INSTALL_DIR = "miband-bot"
$projectPath = Join-Path $PWD.Path $INSTALL_DIR
$autoStart = $false
# 1. Проверяем существование директории
if (Test-Path -Path $INSTALL_DIR) {
if (Test-ConfiguredInstall -ProjectPath $projectPath) {
Write-Host "Найдена настроенная установка. Обновляю файлы и запускаю бота..." -ForegroundColor Green
$autoStart = $true
} else {
Write-Host "Папка '$INSTALL_DIR' уже существует в этой директории." -ForegroundColor Yellow
$overwrite = Read-Host "Хотите перезаписать файлы проекта внутри нее? [Y/n]"
if ($overwrite -eq "") { $overwrite = "y" }
if ($overwrite -notmatch "^[Yy]$") {
Write-Host "Установка отменена." -ForegroundColor Red
Exit
}
}
} else {
New-Item -ItemType Directory -Force -Path $INSTALL_DIR | Out-Null
}
# Переходим в папку проекта
Set-Location -Path $INSTALL_DIR
# 2. Скачиваем ZIP-архив с GitHub
$zipUrl = "https://github.com/iAlexeyRu/miband-bot/archive/refs/heads/main.zip"
$tempZip = Join-Path $env:TEMP "miband-bot-temp.zip"
$unpackDir = "temp-unpack"
Write-Host "Загрузка последней версии проекта с GitHub..." -ForegroundColor Gray
if (Test-Path -LiteralPath $tempZip) {
Remove-Item -LiteralPath $tempZip -Force
}
Invoke-WebRequest -Uri $zipUrl -OutFile $tempZip
# 3. Распаковываем во временную папку
Write-Host "Распаковка файлов проекта..." -ForegroundColor Gray
if (Test-Path -LiteralPath $unpackDir) {
Remove-Item -LiteralPath $unpackDir -Recurse -Force
}
Expand-Archive -Path $tempZip -DestinationPath $unpackDir -Force
Remove-Item $tempZip
# 4. Копируем все файлы (включая скрытые) в корень папки установки
Get-ChildItem -Path "$unpackDir\miband-bot-main" -Force | ForEach-Object {
Copy-Item -Path $_.FullName -Destination "." -Recurse -Force
}
Remove-Item -Path $unpackDir -Recurse -Force
# 5. Запускаем интерактивный setup.bat
if ($autoStart) {
Write-Host "`n✓ Проект обновлён. Запускаю бота..." -ForegroundColor Green
$setupArgs = "/c setup.bat --start"
} else {
Write-Host "`n✓ Проект успешно загружен! Запускаем интерактивную настройку..." -ForegroundColor Green
$setupArgs = "/c setup.bat"
}
# Используем $PWD.Path вместо "." для передачи абсолютного корректного пути рабочей папки
Start-Process -FilePath "cmd.exe" -ArgumentList $setupArgs -WorkingDirectory $PWD.Path -NoNewWindow -Wait
+70
View File
@@ -0,0 +1,70 @@
#!/bin/bash
# Скрипт бесшовной установки miband-bot для macOS / Linux
set -e
# Цвета для вывода в консоль
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
echo -e "${BLUE}=== Установка miband-bot ===${NC}"
# 1. Проверяем зависимости (curl или wget, и unzip)
if ! command -v curl &> /dev/null && ! command -v wget &> /dev/null; then
echo -e "${RED}Ошибка: Для загрузки требуется утилита curl или wget.${NC}"
exit 1
fi
if ! command -v unzip &> /dev/null; then
echo -e "${RED}Ошибка: Для установки требуется утилита unzip (установите ее через ваш менеджер пакетов).${NC}"
exit 1
fi
# 2. Создаем или проверяем директорию установки
INSTALL_DIR="miband-bot"
if [ -d "$INSTALL_DIR" ]; then
echo -e "${YELLOW}Папка '$INSTALL_DIR' уже существует в этой директории.${NC}"
read -p "Хотите перезаписать файлы проекта внутри нее? [Y/n]: " overwrite_confirm </dev/tty
if [ -z "$overwrite_confirm" ]; then
overwrite_confirm="y"
fi
if [[ ! "$overwrite_confirm" =~ ^[Yy]$ ]]; then
echo -e "${RED}Установка отменена.${NC}"
exit 1
fi
else
mkdir -p "$INSTALL_DIR"
fi
cd "$INSTALL_DIR"
# 3. Скачиваем ZIP-архив с GitHub
echo "Загрузка последней версии проекта с GitHub..."
ZIP_URL="https://github.com/iAlexeyRu/miband-bot/archive/refs/heads/main.zip"
TEMP_ZIP="miband-bot-temp.zip"
if command -v curl &> /dev/null; then
curl -sSL -o "$TEMP_ZIP" "$ZIP_URL"
else
wget -q -O "$TEMP_ZIP" "$ZIP_URL"
fi
# 4. Распаковываем и очищаем временные файлы
echo "Распаковка файлов проекта..."
unzip -q -o "$TEMP_ZIP"
rm "$TEMP_ZIP"
# 5. Копируем файлы из вложенной папки и удаляем ее
cp -r miband-bot-main/. .
rm -rf miband-bot-main
# 6. Запускаем интерактивный setup.sh
chmod +x setup.sh
echo -e "${GREEN}✓ Проект успешно загружен! Запускаем интерактивную настройку...${NC}"
echo ""
# Перенаправляем stdin на /dev/tty, чтобы интерактивные read-промпты работали корректно при запуске через пайп
./setup.sh </dev/tty
+6 -2
View File
@@ -7,8 +7,12 @@ from __future__ import annotations
import asyncio import asyncio
import sys import sys
from miband_tracker.config import ConfigError, Settings from miband_tracker.stdio import configure_utf8_stdio
from miband_tracker.sync import daemon_main, run_sync
configure_utf8_stdio()
from miband_tracker.config import ConfigError, Settings # noqa: E402
from miband_tracker.sync import daemon_main, run_sync # noqa: E402
__all__ = ["run_sync"] __all__ = ["run_sync"]
+14 -4
View File
@@ -35,10 +35,12 @@ from miband_tracker.secure_files import save_auth_token
from miband_tracker.sync import run_sync from miband_tracker.sync import run_sync
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Logging # Logging — console shows only WARNING+ so users don't see debug spam.
# The mi-fitness vendored library uses loguru with Chinese debug messages;
# we suppress those to ERROR as well.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.WARNING,
format="%(asctime)s %(levelname)s %(name)s %(message)s", format="%(asctime)s %(levelname)s %(name)s %(message)s",
) )
logging.getLogger("httpx").setLevel(logging.WARNING) logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -46,6 +48,13 @@ logging.getLogger("telegram").setLevel(logging.WARNING)
logging.getLogger("telegram.ext").setLevel(logging.WARNING) logging.getLogger("telegram.ext").setLevel(logging.WARNING)
logger = logging.getLogger("fitness-bot") logger = logging.getLogger("fitness-bot")
try:
from loguru import logger as _loguru_logger
_loguru_logger.remove() # Remove loguru's default stderr handler
_loguru_logger.add(sys.stderr, level="ERROR", format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")
except Exception:
pass
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Config # Config
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -1499,10 +1508,11 @@ def main() -> None:
ALLOWED_USER_ID = SETTINGS.telegram_allowed_user_id ALLOWED_USER_ID = SETTINGS.telegram_allowed_user_id
if ALLOWED_USER_ID is not None: if ALLOWED_USER_ID is not None:
DB_PATH = str(SETTINGS.user_db_path(ALLOWED_USER_ID)) DB_PATH = str(SETTINGS.user_db_path(ALLOWED_USER_ID))
logger.info("Стартую fitness-bot для пользователя: %s", ALLOWED_USER_ID) print(f"Запуск бота для пользователя ID {ALLOWED_USER_ID}...")
else: else:
DB_PATH = str(SETTINGS.db_path) DB_PATH = str(SETTINGS.db_path)
logger.info("Стартую fitness-bot в режиме ожидания привязки владельца (первое входящее сообщение привяжет бота)...") print("Бот запущен. Отправьте /start в Telegram чтобы привязать аккаунт.")
init_state_db() init_state_db()
app = Application.builder().token(BOT_TOKEN).build() app = Application.builder().token(BOT_TOKEN).build()
+34 -1
View File
@@ -8,6 +8,28 @@ from dataclasses import dataclass
from pathlib import Path from pathlib import Path
def _load_local_env(filename: str = "secrets.env") -> None:
"""Load KEY=VALUE pairs from a local .env file into os.environ.
Only sets variables that are NOT already present in the environment
(explicit env always wins). Skips blank lines and comments (#).
"""
env_file = Path(filename)
if not env_file.exists():
return
try:
for raw_line in env_file.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
key = key.strip()
if key and key not in os.environ:
os.environ[key] = value.strip()
except Exception:
pass # Never crash on env-file read failure
class ConfigError(ValueError): class ConfigError(ValueError):
"""Invalid runtime configuration.""" """Invalid runtime configuration."""
@@ -44,7 +66,18 @@ class Settings:
@classmethod @classmethod
def from_env(cls, *, require_bot: bool = False) -> Settings: def from_env(cls, *, require_bot: bool = False) -> Settings:
data_dir = Path(os.environ.get("DATA_DIR", "/opt/miband-tracker/data")) # Load local secrets.env (if present) before reading env vars.
# This makes Python the single source of truth on all platforms.
_load_local_env()
# Auto-detect local vs Docker mode: if DATA_DIR is not set, use ./data
# when running locally (secrets.env present or ./data already exists),
# otherwise fall back to the Docker default /opt/miband-tracker/data.
_default_data = (
"./data"
if (Path("secrets.env").exists() or Path("data").is_dir())
else "/opt/miband-tracker/data"
)
data_dir = Path(os.environ.get("DATA_DIR", _default_data))
allowed_user_id = parse_single_user_id( allowed_user_id = parse_single_user_id(
os.environ.get("TELEGRAM_ALLOWED_USER_ID", ""), required=False os.environ.get("TELEGRAM_ALLOWED_USER_ID", ""), required=False
) )
+1 -1
View File
@@ -42,7 +42,7 @@ def exclusive_file_lock(path: Path) -> Iterator[None]:
lock_file.seek(0) lock_file.seek(0)
# Блокируем первые 64 байта файла без ожидания (non-blocking) # Блокируем первые 64 байта файла без ожидания (non-blocking)
msvcrt.locking(fd, msvcrt.LK_NBLCK, 64) msvcrt.locking(fd, msvcrt.LK_NBLCK, 64)
except (OSError, IOError) as exc: except OSError as exc:
raise LockUnavailable(f"Lock is already held: {path}") from exc raise LockUnavailable(f"Lock is already held: {path}") from exc
else: else:
# Резервный вариант, если блокировки недоступны на платформе # Резервный вариант, если блокировки недоступны на платформе
+33
View File
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2026 Alexey
from __future__ import annotations
import os
import sys
from typing import TextIO
def configure_utf8_stdio() -> None:
os.environ.setdefault("PYTHONUTF8", "1")
os.environ.setdefault("PYTHONIOENCODING", "utf-8")
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, "reconfigure", None)
if reconfigure is None:
continue
try:
reconfigure(encoding="utf-8", errors="backslashreplace")
except Exception:
pass
def safe_print(text: str, *, file: TextIO | None = None, flush: bool = False) -> None:
stream = file or sys.stdout
try:
print(text, file=stream, flush=flush)
return
except UnicodeEncodeError:
encoding = getattr(stream, "encoding", None) or "utf-8"
fallback = text.encode(encoding, errors="backslashreplace").decode(encoding, errors="replace")
print(fallback, file=stream, flush=flush)
+7 -6
View File
@@ -17,6 +17,7 @@ from .config import ConfigError, Settings
from .fds import download_and_decrypt_sleep_details, parse_all_day_sleep_bytes from .fds import download_and_decrypt_sleep_details, parse_all_day_sleep_bytes
from .lock import LockUnavailable, exclusive_file_lock from .lock import LockUnavailable, exclusive_file_lock
from .secure_files import save_auth_token, write_json_atomic, write_secret_json from .secure_files import save_auth_token, write_json_atomic, write_secret_json
from .stdio import safe_print
from .storage import init_health_db, sqlite_conn from .storage import init_health_db, sqlite_conn
@@ -34,7 +35,7 @@ class SyncResult:
def log(message: str) -> None: def log(message: str) -> None:
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{now}] {message}", flush=True) safe_print(f"[{now}] {message}", flush=True)
def format_epoch(epoch: int | float | None) -> str | None: def format_epoch(epoch: int | float | None) -> str | None:
@@ -364,24 +365,24 @@ def _write_status_file(status_path: Path, latest_steps, latest_heart_rate, lates
async def daemon_main(settings: Settings | None = None) -> int: async def daemon_main(settings: Settings | None = None) -> int:
settings = settings or Settings.from_env() settings = settings or Settings.from_env()
if settings.sync_interval <= 0: if settings.sync_interval <= 0:
log("Running in one-shot mode.")
result = await run_sync(settings=settings) result = await run_sync(settings=settings)
return 0 if result.success else 1 return 0 if result.success else 1
log(f"Running in daemon mode. Sync interval: {settings.sync_interval} seconds.") _waiting_logged = False
while True: while True:
try: try:
current_settings = Settings.from_env() current_settings = Settings.from_env()
if current_settings.telegram_allowed_user_id is None: if current_settings.telegram_allowed_user_id is None:
log("Waiting for allowed user ID to be registered via Telegram bot...") if not _waiting_logged:
log("Синхронизатор ожидает привязки аккаунта через Telegram (/start)...")
_waiting_logged = True
await asyncio.sleep(5) await asyncio.sleep(5)
continue continue
_waiting_logged = False # Reset so we log again if user unregisters
await run_sync(settings=current_settings) await run_sync(settings=current_settings)
except Exception as exc: except Exception as exc:
log(f"Unhandled error in main loop: {exc}") log(f"Unhandled error in main loop: {exc}")
# Load settings again to catch any runtime changes in sync interval
interval = Settings.from_env().sync_interval interval = Settings.from_env().sync_interval
log(f"Sleeping for {interval} seconds...")
await asyncio.sleep(interval) await asyncio.sleep(interval)
+1
View File
@@ -6,3 +6,4 @@ python-telegram-bot==21.6
qrcode==8.0 qrcode==8.0
requests==2.32.3 requests==2.32.3
tenacity==9.1.2 tenacity==9.1.2
tzdata
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2026 Alexey
"""
Local launcher: runs miband_sync and fitness_bot in parallel,
redirecting their output to data/sync.log and data/bot.log.
Handles Ctrl+C and window close gracefully.
"""
from __future__ import annotations
import os
import subprocess
import sys
from contextlib import suppress
from pathlib import Path
from miband_tracker.lock import LockUnavailable, exclusive_file_lock
from miband_tracker.stdio import configure_utf8_stdio, safe_print
configure_utf8_stdio()
def python_env() -> dict[str, str]:
env = os.environ.copy()
env["PYTHONUTF8"] = "1"
env["PYTHONIOENCODING"] = "utf-8"
return env
def stop_processes(procs: list[subprocess.Popen[bytes]]) -> None:
for proc in procs:
if proc.poll() is None:
proc.terminate()
for proc in procs:
if proc.poll() is None:
with suppress(subprocess.TimeoutExpired):
proc.wait(timeout=5)
for proc in procs:
if proc.poll() is None:
proc.kill()
def run_processes(data_dir: Path) -> int:
procs: list[subprocess.Popen[bytes]] = []
exit_code = 0
child_env = python_env()
with (data_dir / "sync.log").open("w", encoding="utf-8") as sync_log, (
data_dir / "bot.log"
).open("w", encoding="utf-8") as bot_log:
try:
sync_proc = subprocess.Popen(
[sys.executable, "-u", "miband_sync.py"],
stdout=sync_log,
stderr=sync_log,
env=child_env,
)
procs.append(sync_proc)
bot_proc = subprocess.Popen(
[sys.executable, "-u", "fitness_bot.py"],
stdout=bot_log,
stderr=bot_log,
env=child_env,
)
procs.append(bot_proc)
exit_code = bot_proc.wait()
except KeyboardInterrupt:
exit_code = 130
finally:
stop_processes(procs)
return exit_code
def main() -> None:
data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
try:
with exclusive_file_lock(data_dir / "run.lock"):
exit_code = run_processes(data_dir)
except LockUnavailable:
safe_print("miband-bot уже запущен. Закройте старое окно перед повторным запуском.", flush=True)
exit_code = 2
raise SystemExit(exit_code)
if __name__ == "__main__":
main()
+98 -190
View File
@@ -1,259 +1,167 @@
@echo off @echo off
:: ==============================================================================
:: miband-bot Smart Setup Script for Windows (100% Flat robust version)
:: ==============================================================================
chcp 65001 > nul chcp 65001 > nul
set AUTO_START=0
if /i "%~1"=="--start" set AUTO_START=1
if /i "%MIBAND_BOT_AUTO_START%"=="1" set AUTO_START=1
cls cls
echo ======================================================================
echo 🚀 УМНЫЙ ЗАПУСК И НАСТРОЙКА miband-bot 🚀
echo ======================================================================
echo Этот скрипт поможет вам быстро развернуть личного Telegram-бота
echo для сбора данных Xiaomi Fitness / Mi Band.
echo. echo.
echo miband-bot
:: ------------------------------------------------------------------------------
:: Шаг 1. Выбор метода установки
:: ------------------------------------------------------------------------------
echo Как вы хотите запустить бота?
echo 1] Напрямую на Python (Рекомендуется. Потребляет минимум памяти)
echo 2] В Docker контейнерах (Всё работает в фоне)
echo. echo.
:CHOOSE_MODE :CHOOSE_MODE
if "%AUTO_START%"=="1" if exist secrets.env goto MODE_PYTHON
set install_mode=1 set install_mode=1
set /p install_mode="Выберите вариант (1 или 2) [1]: " set /p install_mode=" [1] Python (рек.) [2] Docker Выбор [1]: "
if "%install_mode%"=="1" goto MODE_PYTHON if "%install_mode%"=="1" goto MODE_PYTHON
if "%install_mode%"=="2" goto MODE_DOCKER if "%install_mode%"=="2" goto MODE_DOCKER
echo Неверный ввод. Пожалуйста, введите 1 или 2.
echo.
goto CHOOSE_MODE goto CHOOSE_MODE
:: ------------------------------------------------------------------------------
:: Шаг 2. Проверка требований
:: ------------------------------------------------------------------------------
:MODE_DOCKER :MODE_DOCKER
echo.
echo [1/3] Проверка окружения Docker...
where docker >nul 2>nul where docker >nul 2>nul
if errorlevel 1 ( if errorlevel 1 ( echo ! Docker не найден. Установите Docker Desktop. & pause & exit /b 1 )
echo ❌ Docker CLI не найден. Пожалуйста, установите Docker Desktop или настройте WSL 2.
pause
exit /b 1
)
docker info >nul 2>nul docker info >nul 2>nul
if errorlevel 1 ( if errorlevel 1 ( echo ! Docker не запущен. Запустите Docker Desktop. & pause & exit /b 1 )
echo ❌ Демон Docker не запущен. Пожалуйста, запустите Docker и перезапустите скрипт. echo OK Docker готов
pause
exit /b 1
)
echo ✓ Движок Docker активен и готов к работе!
set DOCKER_ACTIVE=1 set DOCKER_ACTIVE=1
goto SETUP_ENV goto SETUP_ENV
:MODE_PYTHON :MODE_PYTHON
echo.
echo [1/3] Проверка окружения Python...
python --version >nul 2>nul python --version >nul 2>nul
if not errorlevel 1 goto PYTHON_OK if not errorlevel 1 goto PYTHON_OK
echo Python не найден или не настроен в вашей системе. echo ! Python не найден: https://www.python.org/downloads/
echo Вы можете скачать его вручную по ссылке: https://www.python.org/downloads/
echo. echo.
set install_python=y set install_python=y
set /p install_python="Хотите, чтобы я автоматически скачал и установил Python 3.11? [Y/n]: " set /p install_python=" Установить Python 3.11 автоматически? [Y/n]: "
if /i not "%install_python%"=="y" ( if /i not "%install_python%"=="y" ( echo Установите Python вручную и повторите. & pause & exit /b 1 )
echo Установка отменена. Пожалуйста, установите Python вручную.
pause
exit /b 1
)
echo. echo Скачивание Python 3.11.9...
echo Скачивание установщика Python 3.11.9... powershell -Command "$ProgressPreference='SilentlyContinue'; Invoke-WebRequest -Uri 'https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe' -OutFile 'python_installer.exe'"
powershell -Command "Invoke-WebRequest -Uri 'https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe' -OutFile 'python_installer.exe'" echo Установка...
echo Установка Python (это займет около минуты, пожалуйста, подождите)...
python_installer.exe /quiet InstallAllUsers=0 PrependPath=1 Include_test=0 python_installer.exe /quiet InstallAllUsers=0 PrependPath=1 Include_test=0
del python_installer.exe del python_installer.exe
:: Добавляем установленный Python в PATH текущей сессии
set "PATH=%LocalAppData%\Programs\Python\Python311;%LocalAppData%\Programs\Python\Python311\Scripts;%PATH%" set "PATH=%LocalAppData%\Programs\Python\Python311;%LocalAppData%\Programs\Python\Python311\Scripts;%PATH%"
python --version >nul 2>nul python --version >nul 2>nul
if errorlevel 1 ( if errorlevel 1 ( echo ! Не удалось установить Python. & pause & exit /b 1 )
echo ❌ Не удалось автоматически установить Python или добавить его в PATH.
echo Пожалуйста, установите Python вручную по ссылке: https://www.python.org/downloads/
pause
exit /b 1
)
:PYTHON_OK :PYTHON_OK
echo Python найден! echo OK Python готов
set DOCKER_ACTIVE=0 set DOCKER_ACTIVE=0
goto SETUP_ENV goto SETUP_ENV
:: ------------------------------------------------------------------------------
:: Шаг 3. Настройка secrets.env
:: ------------------------------------------------------------------------------
:SETUP_ENV :SETUP_ENV
echo. echo.
echo [2/3] Конфигурация параметров бота (secrets.env)...
if not exist secrets.env goto INPUT_ENV_VALUES if not exist secrets.env goto INPUT_ENV_VALUES
echo Файл конфигурации secrets.env уже существует. if "%AUTO_START%"=="1" goto LAUNCH_PHASE
set overwrite_env=n set overwrite_env=n
set /p overwrite_env="Хотите перезаписать его и настроить заново? [y/N]: " set /p overwrite_env=" Конфигурация уже есть. Перенастроить? [y/N]: "
if /i not "%overwrite_env%"=="y" ( if /i not "%overwrite_env%"=="y" goto LAUNCH_PHASE
echo ✓ Сохраняем существующий secrets.env.
goto LAUNCH_PHASE
)
:INPUT_ENV_VALUES :INPUT_ENV_VALUES
echo. echo.
echo Сейчас мы настроим основные параметры безопасности. echo Токен бота от @BotFather в Telegram:
echo.
:INPUT_TOKEN :INPUT_TOKEN
echo 🔑 Шаг 1. Получение Telegram Bot Token set bot_token=
echo Для работы бота необходим токен. Его можно получить бесплатно за 1 минуту: set /p bot_token=" Токен: "
echo 1. Откройте Telegram и перейдите к официальному боту @BotFather. if "%bot_token%"=="" goto TOKEN_EMPTY
echo 2. Отправьте ему команду /newbot. echo %bot_token% | find ":" >nul 2>nul
echo 3. Введите название для бота (например, Мой Mi Band Бот). if errorlevel 1 goto TOKEN_INVALID
echo 4. Введите уникальное имя пользователя (username) на английском, goto TOKEN_OK
echo заканчивающееся на bot (например, my_miband_sync_bot).
echo 5. Скопируйте полученный токен (выглядит как 123456789:ABC-DEF...).
echo.
set /p bot_token="Вставьте ваш Telegram Bot Token: "
if "%bot_token%"=="" (
echo Ошибка: Токен не может быть пустым.
echo.
goto INPUT_TOKEN
)
echo %bot_token% | findstr /c:":" >nul
if errorlevel 1 (
echo Ошибка: Токен должен содержать символ ":"
echo.
goto INPUT_TOKEN
)
echo.
echo 🔒 Шаг 2. Автоматическая привязка владельца (Whitelist ID) :TOKEN_EMPTY
echo Вам НЕ НУЖНО вручную искать и вводить ваш Telegram User ID! echo Токен не может быть пустым.
echo Сразу после запуска бота откройте его в Telegram и отправьте команду /start. goto INPUT_TOKEN
echo Бот автоматически распознает ваш ID, запишет его в белый список
echo и заблокирует доступ для всех остальных пользователей.
echo.
:: Создание secrets.env (запись построчно во избежание багов парсера скобок) :TOKEN_INVALID
echo # ============================================================================== > secrets.env echo Неверный формат - токен должен содержать двоеточие.
echo # Конфигурация secrets.env для miband-bot >> secrets.env goto INPUT_TOKEN
echo # ============================================================================== >> secrets.env
echo. >> secrets.env
echo # Токен вашего Telegram-бота (полученный от @BotFather) >> secrets.env
echo TELEGRAM_BOT_TOKEN=%bot_token% >> secrets.env
echo. >> secrets.env
echo # Единственный разрешенный Telegram User ID (для безопасности данных) >> secrets.env
echo # Оставьте пустым - бот автоматически привяжется к первому, кто напишет /start! >> secrets.env
echo TELEGRAM_ALLOWED_USER_ID= >> secrets.env
echo. >> secrets.env
echo # Интервал фоновой синхронизации данных из облака Xiaomi Fitness (в секундах) >> secrets.env
echo SYNC_INTERVAL=900 >> secrets.env
echo. >> secrets.env
echo # Глубина запроса при автоматической синхронизации (в днях) >> secrets.env
echo QUERY_DURATION=2 >> secrets.env
echo. >> secrets.env
echo # Загружать детальные ночные данные о сне (FDS) >> secrets.env
echo ENABLE_FDS_SLEEP_DETAILS=true >> secrets.env
echo ✓ Файл secrets.env успешно создан! :TOKEN_OK
echo. (
echo # miband-bot config
echo TELEGRAM_BOT_TOKEN=%bot_token%
echo TELEGRAM_ALLOWED_USER_ID=
echo SYNC_INTERVAL=900
echo QUERY_DURATION=2
echo ENABLE_FDS_SLEEP_DETAILS=true
) > secrets.env
echo OK Конфигурация сохранена
:: ------------------------------------------------------------------------------
:: Шаг 4. Установка зависимостей и запуск
:: ------------------------------------------------------------------------------
:LAUNCH_PHASE :LAUNCH_PHASE
echo [3/3] Подготовка среды выполнения... echo.
if not "%DOCKER_ACTIVE%"=="1" goto PYTHON_LAUNCH if not "%DOCKER_ACTIVE%"=="1" goto PYTHON_LAUNCH
:DOCKER_LAUNCH :DOCKER_LAUNCH
set launch_now=y set launch_now=y
set /p launch_now="Хотите запустить miband-bot в Docker прямо сейчас? [Y/n]: " set /p launch_now=" Запустить в Docker сейчас? [Y/n]: "
if /i not "%launch_now%"=="y" ( if /i not "%launch_now%"=="y" ( echo Запуск вручную: docker compose up -d --build & goto END_LAUNCH )
echo.
echo Бот настроен, но не запущен.
echo Запустите вручную командой: docker compose up -d --build
goto END_LAUNCH
)
echo Сборка и запуск контейнеров...
docker compose up -d --build docker compose up -d --build
if not errorlevel 1 ( if not errorlevel 1 (
echo. echo.
echo ====================================================================== echo OK Бот запущен в Docker. Отправьте /start в Telegram.
echo 🎉 miband-bot УСПЕШНО НАСТРОЕН И ЗАПУЩЕН В DOCKER! 🎉 echo Логи: docker compose logs -f fitness-bot
echo ======================================================================
echo Теперь откройте бота в Telegram и отправьте команду /start.
echo.
echo Логи: docker compose logs -f fitness-bot
echo Стоп: docker compose down
echo ======================================================================
) else ( ) else (
echo Ошибка при сборке или запуске Docker Compose. echo ! Ошибка запуска Docker.
) )
goto END_LAUNCH goto END_LAUNCH
:PYTHON_LAUNCH :PYTHON_LAUNCH
echo Создаем виртуальное окружение .venv и устанавливаем зависимости... echo Подготовка...
if not exist .venv ( if not exist .venv ( python -m venv .venv >nul 2>nul )
python -m venv .venv
)
call .venv\Scripts\activate call .venv\Scripts\activate
echo Установка библиотек... pip install -r requirements.txt -e mi-fitness-python > pip_install.log 2>&1
pip install -r requirements.txt -e mi-fitness-python if not errorlevel 1 (
del pip_install.log >nul 2>nul
echo OK Готово
) else (
echo ! Ошибка установки зависимостей:
type pip_install.log
del pip_install.log >nul 2>nul
pause & exit /b 1
)
:: Записываем рабочий запуск в run_local.bat построчно (полностью без скобочных блоков) >run_local.bat echo @echo off
echo @echo off > run_local.bat >>run_local.bat echo chcp 65001 ^> nul
echo chcp 65001 ^> nul >> run_local.bat >>run_local.bat echo cd /d "%%~dp0"
echo cd /d "%%~dp0" >> run_local.bat >>run_local.bat echo if not exist .venv ^(
echo if not exist .venv ^( >> run_local.bat >>run_local.bat echo echo ! .venv не найдено. Запустите setup.bat заново.
echo echo Ошибка: Виртуальное окружение .venv не найдено. Запустите setup.bat сначала. >> run_local.bat >>run_local.bat echo pause
echo pause >> run_local.bat >>run_local.bat echo exit /b 1
echo exit /b 1 >> run_local.bat >>run_local.bat echo ^)
echo ^) >> run_local.bat >>run_local.bat echo if not exist run.py ^(
echo. >> run_local.bat >>run_local.bat echo echo ! run.py не найден. Обновите проект и запустите setup.bat заново.
echo :: Загрузка переменных из secrets.env >> run_local.bat >>run_local.bat echo pause
echo if exist secrets.env ^( >> run_local.bat >>run_local.bat echo exit /b 1
echo for /f "usebackq delims=" %%%%x in ^("secrets.env"^) do ^( >> run_local.bat >>run_local.bat echo ^)
echo echo %%%%x ^| findstr /r "^#" ^>nul >> run_local.bat >>run_local.bat echo call .venv\Scripts\activate
echo if errorlevel 1 ^( >> run_local.bat >>run_local.bat echo set PYTHONUTF8=1
echo set %%%%x >> run_local.bat >>run_local.bat echo set PYTHONIOENCODING=utf-8
echo ^) >> run_local.bat >>run_local.bat echo md data 2^>nul
echo ^) >> run_local.bat >>run_local.bat echo cls
echo ^) >> run_local.bat >>run_local.bat echo echo.
echo. >> run_local.bat >>run_local.bat echo echo miband-bot
echo :: Настройка локальных путей к папке data >> run_local.bat >>run_local.bat echo echo.
echo set DATA_DIR=.\data >> run_local.bat >>run_local.bat echo echo OK Бот запущен. Не закрывайте это окно.
echo set DB_PATH=.\data\miband_%%TELEGRAM_ALLOWED_USER_ID%%.db >> run_local.bat >>run_local.bat echo echo Логи: data\bot.log / data\sync.log
echo set STATUS_PATH=.\data\status_%%TELEGRAM_ALLOWED_USER_ID%%.json >> run_local.bat >>run_local.bat echo echo.
echo set BOT_STATE_DB_PATH=.\data\fitness_bot_state.db >> run_local.bat >>run_local.bat echo python -u run.py
echo. >> run_local.bat >>run_local.bat echo echo.
echo call .venv\Scripts\activate >> run_local.bat >>run_local.bat echo echo Бот остановлен. Можно закрыть окно.
echo === Запуск miband-bot локально ^(без Docker^) === >> run_local.bat >>run_local.bat echo echo.
echo Для завершения работы закройте это окно консоли. >> run_local.bat >>run_local.bat echo pause
echo. >> run_local.bat
echo Запуск синхронизатора и Telegram-бота... >> run_local.bat if "%AUTO_START%"=="1" goto RUN_LOCAL_NOW
echo. >> run_local.bat
echo [Запуск фонового синхронизатора...] >> run_local.bat
echo start /b python -u miband_sync.py >> run_local.bat
echo [Запуск бота...] >> run_local.bat
echo python -u fitness_bot.py >> run_local.bat
echo ✓ Создан удобный скрипт запуска: run_local.bat
echo. echo.
set launch_now=y set launch_now=y
set /p launch_now="Хотите запустить бота локально прямо сейчас? [Y/n]: " set /p launch_now=" Запустить бота сейчас? [Y/n]: "
if /i "%launch_now%"=="y" ( if /i "%launch_now%"=="y" ( call run_local.bat )
call run_local.bat goto END_LAUNCH
)
:RUN_LOCAL_NOW
call run_local.bat
:END_LAUNCH :END_LAUNCH
echo. echo.
+135 -283
View File
@@ -1,319 +1,171 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# miband-bot setup for macOS / Linux
# ==============================================================================
# miband-bot Smart Setup Script for macOS / Linux
# ==============================================================================
# Цвета для вывода в консоль
RED='\033[0;31m'
# Приятный зеленый
GREEN='\033[0;32m' GREEN='\033[0;32m'
# Насыщенный желтый RED='\033[0;31m'
YELLOW='\033[1;33m' NC='\033[0m'
# Глубокий синий
BLUE='\033[0;34m' ok() { echo -e " ${GREEN}${NC} $1"; }
# Пурпурный err() { echo -e " ${RED}${NC} $1"; }
MAGENTA='\033[0;35m'
# Серый / обычный
NC='\033[0;37m'
BOLD='\033[1m'
clear clear
echo -e "${BLUE}${BOLD}======================================================================" echo ""
echo -e " 🚀 УМНЫЙ ЗАПУСК И НАСТРОЙКА miband-bot 🚀" echo " miband-bot"
echo -e "======================================================================${NC}"
echo -e "Этот скрипт поможет вам быстро развернуть личного Telegram-бота"
echo -e "для сбора данных Xiaomi Fitness / Mi Band."
echo ""
# ------------------------------------------------------------------------------
# Шаг 1. Выбор метода установки
# ------------------------------------------------------------------------------
echo -e "${BOLD}Как вы хотите запустить бота?${NC}"
echo -e " ${BOLD}1)${NC} ${GREEN}Напрямую на Python${NC} (Рекомендуется. Потребляет минимум памяти)"
echo -e " ${BOLD}2)${NC} ${MAGENTA}В Docker контейнерах${NC} (Всё работает в фоне, легко управлять)"
echo "" echo ""
# --- Выбор режима ---
while true; do while true; do
read -p "Выберите вариант (1 или 2) [1]: " install_mode read -p " [1] Python (рек.) [2] Docker Выбор [1]: " install_mode
if [ -z "$install_mode" ]; then install_mode="${install_mode:-1}"
install_mode="1" [ "$install_mode" = "1" ] || [ "$install_mode" = "2" ] && break
fi
if [ "$install_mode" = "1" ] || [ "$install_mode" = "2" ]; then
break
else
echo -e "${RED}Неверный ввод. Пожалуйста, введите 1 или 2.${NC}"
fi
done done
echo "" echo ""
# ------------------------------------------------------------------------------ # --- Проверка требований ---
# Шаг 2. Проверка требований выбранного режима
# ------------------------------------------------------------------------------
if [ "$install_mode" = "2" ]; then if [ "$install_mode" = "2" ]; then
echo -e "${BOLD}[1/3] Проверка окружения Docker...${NC}"
DOCKER_AVAILABLE=true
DOCKER_RUNNING=true
if ! command -v docker &> /dev/null; then if ! command -v docker &> /dev/null; then
DOCKER_AVAILABLE=false err "Docker не найден. Установите Docker Desktop или Colima (brew install colima)."
fi
if [ "$DOCKER_AVAILABLE" = true ]; then
if ! docker info &> /dev/null; then
DOCKER_RUNNING=false
fi
fi
if [ "$DOCKER_AVAILABLE" = false ]; then
echo -e "${RED}❌ Docker CLI не найден в вашей системе.${NC}"
echo -e "Пожалуйста, установите Docker движок перед продолжением:"
echo -e " - ${BLUE}Linux:${NC} Установите 'docker' и 'docker-compose-plugin' через менеджер пакетов."
echo -e " - ${BLUE}macOS:${NC} Запустите легкий ${BOLD}Colima${NC} ('brew install colima') или Docker Desktop."
echo -e "Или вернитесь назад и выберите вариант запуска напрямую на Python."
exit 1 exit 1
elif [ "$DOCKER_RUNNING" = false ]; then fi
echo -e "${RED}❌ Служба Docker установлена, но сейчас не запущена.${NC}" if ! docker info &> /dev/null; then
echo -e "Пожалуйста, запустите Docker-демон (например, 'colima start' на Mac" err "Docker не запущен. Запустите демон (colima start / Docker Desktop) и повторите."
echo -e "или 'sudo systemctl start docker' на Linux) и перезапустите скрипт."
exit 1 exit 1
fi
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else else
echo -e "${GREEN}✓ Движок Docker активен и готов к работе!${NC}" err "Плагин docker compose не найден."
if docker compose version &> /dev/null; then exit 1
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
echo -e "${YELLOW}⚠️ Команда 'docker compose' не найдена. Убедитесь, что плагин Compose установлен.${NC}"
fi
fi fi
ok "Docker готов"
DOCKER_MODE=true
else else
echo -e "${BOLD}[1/3] Проверка окружения Python...${NC}"
PYTHON_CMD="" PYTHON_CMD=""
if command -v python3 &> /dev/null; then for cmd in python3 python; do
PYTHON_CMD="python3" if command -v "$cmd" &> /dev/null; then
elif command -v python &> /dev/null; then ver=$("$cmd" -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
# Проверяем версию python [ "$ver" = "3" ] && PYTHON_CMD="$cmd" && break
py_ver=$(python -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
if [ "$py_ver" = "3" ]; then
PYTHON_CMD="python"
fi
fi
if [ -z "$PYTHON_CMD" ]; then
echo -e "${RED}❌ Python 3 не найден в вашей системе.${NC}"
echo -e "Пожалуйста, установите Python версии 3.10 или выше."
exit 1
else
py_full_ver=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")')
echo -e "${GREEN}✓ Найден Python $py_full_ver!${NC}"
fi
fi
echo ""
# ------------------------------------------------------------------------------
# Шаг 3. Настройка secrets.env
# ------------------------------------------------------------------------------
echo -e "${BOLD}[2/3] Конфигурация параметров бота (secrets.env)...${NC}"
ENV_FILE="secrets.env"
SETUP_CONFIG=true
if [ -f "$ENV_FILE" ]; then
echo -e "${YELLOW}Файл конфигурации secrets.env уже существует.${NC}"
read -p "Хотите перезаписать его и настроить параметры заново? [y/N]: " overwrite_env
if [ -z "$overwrite_env" ]; then
overwrite_env="n"
fi
if [[ ! $overwrite_env =~ ^[Yy]$ ]]; then
echo -e "${GREEN}✓ Сохраняем существующий secrets.env.${NC}"
SETUP_CONFIG=false
fi
fi
if [ "$SETUP_CONFIG" = true ]; then
echo -e "Сейчас мы настроим основные параметры безопасности."
echo ""
# Ввод токена бота с подробной инструкцией
while true; do
echo -e "🔑 ${BOLD}Шаг 1. Получение Telegram Bot Token${NC}"
echo -e "Для работы бота необходим токен. Его можно получить бесплатно за 1 минуту:"
echo -e " 1. Откройте Telegram и перейдите к официальному боту ${BLUE}@BotFather${NC}."
echo -e " 2. Отправьте ему команду ${BOLD}/newbot${NC}."
echo -e " 3. Введите название для бота (например, ${YELLOW}Мой Mi Band Бот${NC})."
echo -e " 4. Введите уникальное имя пользователя (username) на английском,"
echo -e " заканчивающееся на ${BOLD}bot${NC} (например, ${YELLOW}my_miband_sync_bot${NC})."
echo -e " 5. Скопируйте полученный токен (выглядит как ${BLUE}123456789:ABC-DEF...${NC})."
echo ""
read -p "Вставьте ваш Telegram Bot Token: " bot_token
if [ -n "$bot_token" ] && [[ "$bot_token" == *":"* ]]; then
break
else
echo -e "${RED}Ошибка: Токен должен быть непустым и содержать символ ':'${NC}\n"
fi fi
done done
echo "" if [ -z "$PYTHON_CMD" ]; then
err "Python 3 не найден. Установите: https://www.python.org/downloads/"
echo -e "🔒 ${BOLD}Шаг 2. Автоматическая привязка владельца (Whitelist ID)${NC}"
echo -e "Вам ${BOLD}НЕ НУЖНО${NC} вручную искать и вводить ваш Telegram User ID!"
echo -e "Сразу после запуска бота откройте его в Telegram и отправьте команду ${BOLD}/start${NC}."
echo -e "Бот автоматически распознает ваш ID, запишет его в белый список"
echo -e "и заблокирует доступ для всех остальных пользователей."
echo ""
# Создание secrets.env
cat << EOF > "$ENV_FILE"
# ==============================================================================
# Конфигурация secrets.env для miband-bot
# ==============================================================================
# Токен вашего Telegram-бота (полученный от @BotFather)
TELEGRAM_BOT_TOKEN=$bot_token
# Единственный разрешенный Telegram User ID (для безопасности данных)
# Оставьте пустым - бот автоматически привяжется к первому, кто напишет /start!
TELEGRAM_ALLOWED_USER_ID=
# Интервал фоновой синхронизации данных из облака Xiaomi Fitness (в секундах)
SYNC_INTERVAL=900
# Глубина запроса при автоматической синхронизации (в днях)
QUERY_DURATION=2
# Загружать детальные ночные данные о сне (FDS)
ENABLE_FDS_SLEEP_DETAILS=true
EOF
chmod 600 "$ENV_FILE" 2>/dev/null
echo -e "${GREEN}✓ Файл secrets.env успешно создан!${NC}"
fi
echo ""
# ------------------------------------------------------------------------------
# Шаг 4. Установка зависимостей и запуск
# ------------------------------------------------------------------------------
echo -e "${BOLD}[3/3] Подготовка среды выполнения...${NC}"
if [ "$install_mode" = "2" ]; then
# Режим Docker
read -p "Хотите запустить miband-bot в Docker прямо сейчас? [Y/n]: " launch_now
if [ -z "$launch_now" ]; then
launch_now="y"
fi
if [[ $launch_now =~ ^[Yy]$ ]]; then
echo -e "${BLUE}Сборка и старт контейнеров в фоне...${NC}"
if $COMPOSE_CMD up -d --build; then
echo ""
echo -e "${GREEN}${BOLD}======================================================================"
echo -e " 🎉 miband-bot УСПЕШНО НАСТРОЕН И ЗАПУЩЕН В DOCKER! 🎉"
echo -e "======================================================================${NC}"
echo -e "Следующие шаги:"
echo -e "1. Откройте вашего бота в Telegram и отправьте ${BOLD}/start${NC}."
echo -e "2. Войдите в аккаунт Xiaomi по ссылке или QR-коду."
echo ""
echo -e "Команда для просмотра логов:"
echo -e " ${BLUE}$COMPOSE_CMD logs -f fitness-bot${NC}"
echo -e "Команда для остановки бота:"
echo -e " ${BLUE}$COMPOSE_CMD down${NC}"
echo "======================================================================"
else
echo -e "${RED}Ошибка при запуске Docker Compose.${NC}"
fi
else
echo -e "${YELLOW}Бот настроен, но не запущен.${NC}"
echo -e "Запустите вручную командой: ${BLUE}$COMPOSE_CMD up -d --build${NC}"
fi
else
# Режим чистого Python
echo -e "Создаем виртуальное окружение .venv..."
if [ ! -d ".venv" ]; then
if ! $PYTHON_CMD -m venv .venv; then
echo -e "${RED}❌ Не удалось создать виртуальное окружение .venv.${NC}"
echo -e "В вашей системе (вероятно, Debian/Ubuntu) отсутствует пакет для виртуальных сред."
echo -e "Пожалуйста, установите его с помощью команды:"
echo -e " ${BOLD}sudo apt update && sudo apt install -y python3-venv${NC}"
exit 1
fi
fi
source .venv/bin/activate
echo -e "${BLUE}Установка библиотек из requirements.txt и mi-fitness-python...${NC}"
if pip install -r requirements.txt -e mi-fitness-python; then
echo -e "${GREEN}✓ Все зависимости успешно установлены!${NC}"
else
echo -e "${RED}❌ Не удалось установить зависимости. Проверьте подключение к сети.${NC}"
exit 1 exit 1
fi fi
ok "Python готов ($PYTHON_CMD)"
# Создаем удобный скрипт для локального запуска DOCKER_MODE=false
LAUNCHER_FILE="run_local.sh" fi
cat << 'EOF' > "$LAUNCHER_FILE"
#!/usr/bin/env bash
# ==============================================================================
# Скрипт локального запуска miband-bot (без Docker)
# ==============================================================================
cd "$(dirname "$0")"
# --- Конфигурация ---
echo ""
if [ -f secrets.env ]; then
read -p " Конфигурация уже есть. Перенастроить? [y/N]: " overwrite < /dev/tty
if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then
: # skip to launch
else
rm -f secrets.env
fi
fi
if [ ! -f secrets.env ]; then
echo ""
echo " Токен бота от @BotFather в Telegram:"
while true; do
read -p " > " bot_token < /dev/tty
[ -z "$bot_token" ] && echo " Токен не может быть пустым." && continue
[[ "$bot_token" == *":"* ]] && break
echo " Неверный формат (должен содержать ':')."
done
{
echo "# miband-bot"
echo "TELEGRAM_BOT_TOKEN=$bot_token"
echo "TELEGRAM_ALLOWED_USER_ID="
echo "SYNC_INTERVAL=900"
echo "QUERY_DURATION=2"
echo "ENABLE_FDS_SLEEP_DETAILS=true"
} > secrets.env
ok "Конфигурация сохранена"
fi
# --- Запуск ---
echo ""
if [ "$DOCKER_MODE" = true ]; then
read -p " Запустить в Docker сейчас? [Y/n]: " launch_now < /dev/tty
launch_now="${launch_now:-y}"
if [[ ! "$launch_now" =~ ^[Yy]$ ]]; then
echo " Запуск вручную: $COMPOSE_CMD up -d --build"
exit 0
fi
$COMPOSE_CMD up -d --build
if [ $? -eq 0 ]; then
echo ""
ok "Бот запущен в Docker. Отправьте /start в Telegram."
echo " Логи: $COMPOSE_CMD logs -f fitness-bot"
else
err "Ошибка запуска Docker."
fi
exit 0
fi
# Python режим
echo " Подготовка..."
if [ ! -d ".venv" ]; then if [ ! -d ".venv" ]; then
echo "Ошибка: Папка .venv не найдена. Запустите setup.sh заново." if ! $PYTHON_CMD -m venv .venv; then
err "Не удалось создать .venv. На Debian/Ubuntu: sudo apt install -y python3-venv"
exit 1
fi
fi
source .venv/bin/activate
if pip install -r requirements.txt -e mi-fitness-python > pip_install.log 2>&1; then
rm -f pip_install.log
ok "Готово"
else
err "Ошибка установки зависимостей:"
cat pip_install.log
rm -f pip_install.log
exit 1 exit 1
fi fi
# Загружаем переменные из secrets.env в окружение текущего процесса # Генерируем run_local.sh — логи Python уходят в файлы
if [ -f secrets.env ]; then cat << 'EOF' > run_local.sh
while IFS= read -r line || [ -n "$line" ]; do #!/usr/bin/env bash
# Очищаем пробелы и переносы # Запуск miband-bot (Python читает secrets.env автоматически)
line=$(echo "$line" | xargs 2>/dev/null || echo "$line") cd "$(dirname "$0")"
# Игнорируем комментарии и пустые строки
if [[ ! "$line" =~ ^# ]] && [[ ! -z "$line" ]]; then if [ ! -d ".venv" ]; then
export "$line" echo " ❌ .venv не найдена. Запустите setup.sh заново."
fi exit 1
done < secrets.env
fi fi
# Настраиваем локальные пути к папке data (чтобы не писать в глобальные пути /opt) if [ ! -f "run.py" ]; then
export DATA_DIR="./data" echo " ❌ run.py не найден. Обновите проект и запустите setup.sh заново."
export DB_PATH="./data/miband_${TELEGRAM_ALLOWED_USER_ID}.db" exit 1
export STATUS_PATH="./data/status_${TELEGRAM_ALLOWED_USER_ID}.json" fi
export BOT_STATE_DB_PATH="./data/fitness_bot_state.db"
source .venv/bin/activate source .venv/bin/activate
echo "=== Запуск miband-bot локально на Python ===" mkdir -p data
echo "Нажмите Ctrl+C для завершения работы обоих процессов." clear
echo ""
echo " miband-bot"
echo ""
echo " ✓ Бот запущен. Не закрывайте это окно."
echo " Логи: data/bot.log / data/sync.log"
echo "" echo ""
# При завершении скрипта (Ctrl+C) убиваем все запущенные нами фоновые задачи python -u run.py
trap "kill 0" EXIT echo ""
echo " Бот остановлен. Можно закрыть окно."
# Запускаем фоновый синхронизатор и сам Telegram бот параллельно echo ""
python -u miband_sync.py &
python -u fitness_bot.py &
# Ожидаем завершения
wait
EOF EOF
chmod +x "$LAUNCHER_FILE" chmod +x run_local.sh
echo -e "${GREEN}✓ Создан удобный скрипт запуска: ${BOLD}./run_local.sh${NC}" ok "Создан скрипт запуска: ./run_local.sh"
echo ""
read -p "Хотите запустить бота локально прямо сейчас? [Y/n]: " launch_now
if [ -z "$launch_now" ]; then
launch_now="y"
fi
if [[ $launch_now =~ ^[Yy]$ ]]; then
./run_local.sh
else
echo -e "${YELLOW}Бот готов к запуску.${NC}"
echo -e "Запускайте его в любое время с помощью скрипта:"
echo -e " ${BLUE}./run_local.sh${NC}"
fi
fi
echo "" echo ""
read -p " Запустить бота сейчас? [Y/n]: " launch_now < /dev/tty
launch_now="${launch_now:-y}"
if [[ "$launch_now" =~ ^[Yy]$ ]]; then
./run_local.sh
fi
+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2026 Alexey
from __future__ import annotations
import io
from miband_tracker.stdio import safe_print
def test_safe_print_does_not_crash_on_non_ascii_with_charmap_stream() -> None:
raw = io.BytesIO()
stream = io.TextIOWrapper(raw, encoding="cp1251", errors="strict")
safe_print("API 业务错误", file=stream, flush=True)
stream.flush()
assert b"API " in raw.getvalue()
assert b"\\u4e1a" in raw.getvalue()