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
*.tar
*.tar.gz
# Generated launchers
run_local.sh
run_local.bat
+126 -117
View File
@@ -2,186 +2,195 @@
Русский | [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.
- Запускает ручную синхронизацию кнопкой в меню.
- Автоматически синхронизирует данные по расписанию.
- Хранит историю в SQLite в папке `data/`.
- Экспортирует накопленные таблицы в ZIP с CSV-файлами.
- Работает через Docker Compose.
- Пишет Xiaomi token атомарно с правами `0600`.
## Кому подходит
Проект подойдет, если вы:
- пользуетесь 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 без предупреждения;
- вход или синхронизация могут временно перестать работать;
- используйте проект только для своих аккаунтов и своих данных;
- соблюдайте применимые законы и условия сервисов в вашей стране;
- данные браслета не являются медицинским заключением.
- Просмотр последних шагов, сна, пульса и SpO2 в Telegram.
- Ручная и автоматическая синхронизация по расписанию.
- Хранение истории в SQLite (`data/`).
- Экспорт всех таблиц в ZIP с CSV-файлами прямо в чат.
- Развёртывание через Docker Compose.
- Атомарная запись Xiaomi-токена с правами `0600`.
- Умная автопривязка к первому пользователю (whitelist).
## Как это работает
```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;
- `fitness-bot` - отвечает в Telegram, показывает меню, запускает ручной sync и экспорт.
- `tracker` периодически синхронизирует данные из Xiaomi Fitness;
- `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 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
TELEGRAM_BOT_TOKEN=123456:replace-me
TELEGRAM_ALLOWED_USER_ID=123456789
```
Установщик сам создаст папку `miband-bot`, загрузит и распакует файлы проекта, проверит окружение и запустит интерактивную настройку!
Повторный запуск этой же PowerShell-команды в уже настроенной установке обновит файлы и сразу запустит бота без повторного ввода токена.
3. Запустите сервис:
---
```sh
docker compose up -d --build
docker compose logs -f fitness-bot
```
### Способ 2: Запуск из скачанной папки
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
SYNC_INTERVAL=900
QUERY_DURATION=2
ENABLE_FDS_SLEEP_DETAILS=true
```
| Переменная | По умолчанию | Описание |
| -------------------------- | ------------ | --------------------------------------- |
| `TELEGRAM_BOT_TOKEN` | — | Token Telegram-бота |
| `TELEGRAM_ALLOWED_USER_ID` | — | Разрешённый user id (оставьте пустым для автопривязки) |
| `SYNC_INTERVAL` | `900` | Интервал фоновой синхронизации, секунды |
| `QUERY_DURATION` | `2` | Глубина запроса при sync, дней |
| `ENABLE_FDS_SLEEP_DETAILS` | `true` | Загружать детальные ночные данные FDS |
- `TELEGRAM_BOT_TOKEN` - token вашего Telegram-бота.
- `TELEGRAM_ALLOWED_USER_ID` - единственный Telegram user id, которому разрешен доступ.
- `SYNC_INTERVAL` - интервал фоновой синхронизации в секундах. `900` = 15 минут.
- `QUERY_DURATION` - сколько последних дней запрашивать при sync.
- `ENABLE_FDS_SLEEP_DETAILS` - пробовать ли загружать детальные ночные данные FDS.
Пути к базе и статусу заданы в `compose.yaml`. При запуске без Docker
смотрите `secrets.env.example`.
Пути к базе и статусу уже заданы в `compose.yaml`. Если запускаете без Docker, смотрите `secrets.env.example`.
## Где лежат данные
## Файлы данных
Runtime-файлы создаются в `./data`:
- `token_<telegram_user_id>.json` - Xiaomi auth token, секретный файл;
- `miband_<telegram_user_id>.db` - SQLite-база с health-данными;
- `status_<telegram_user_id>.json` - последний статус синхронизации;
- `fitness_bot_state.db` - служебное состояние Telegram-меню;
- `sync_<telegram_user_id>.lock` - lock-файл синхронизации.
| Файл | Содержимое |
| ---------------------- | --------------------------------- |
| `token_<id>.json` | Xiaomi auth token (**секретный**) |
| `miband_<id>.db` | SQLite-база с health-данными |
| `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` - показать состояние локальной базы.
Основное управление происходит кнопками в Telegram-меню.
## Экспорт CSV
В меню есть экспорт данных. Бот собирает ZIP с CSV-таблицами и отправляет его в Telegram.
Помните: ZIP с health-данными уходит через инфраструктуру Telegram. Не отправляйте экспорт в чужие чаты и не храните его там, где доступ есть у других людей.
| Команда | Действие |
| --------- | ------------------------------------- |
| `/start` | Открыть меню или начать вход в Xiaomi |
| `/sync` | Запустить ручную синхронизацию |
| `/status` | Показать состояние локальной базы |
## Локальная разработка
```sh
python3 -m venv .venv
.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 mi-fitness-python/tests/unit
.venv/bin/ruff check .
.venv/bin/python -m pip check
```
Entrypoints сохранены для Docker и локального запуска:
Точки входа:
```sh
python -u miband_sync.py
python -u fitness_bot.py
```
Если проект установлен как Python package, доступны console scripts:
```sh
miband-sync
miband-fitness-bot
python -u miband_sync.py # или: miband-sync
python -u fitness_bot.py # или: miband-fitness-bot
```
## Troubleshooting
**Бот не отвечает.**
Проверьте `TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USER_ID` и логи:
**Бот не отвечает** — проверьте `TELEGRAM_BOT_TOKEN`, логи, а также убедитесь, что вы первыми отправили `/start` боту для привязки. При необходимости сбросить привязанного владельца просто удалите файл `data/allowed_user.id` и отправьте `/start` снова.
```sh
docker compose logs -f fitness-bot
```
**Синхронизация пишет, что token не найден.**
Откройте бота в Telegram, отправьте `/start` и пройдите Xiaomi login flow.
**Token не найден** — отправьте `/start` и пройдите Xiaomi login flow.
**Token истек.**
В меню запустите повторный вход в Xiaomi. Старый token можно удалить из `data/`.
**Token истёк** — запустите повторный вход из меню; старый файл
можно удалить из `data/`.
**Данных мало или нет SpO2/деталей сна.**
Проверьте, что Xiaomi Fitness реально показывает эти данные. Часть данных зависит от модели браслета, настроек шаринга и доступности неофициального API.
**Нет SpO2 или деталей сна** — убедитесь, что эти данные отображаются
в самом приложении Xiaomi Fitness. Доступность зависит от модели
браслета и настроек шаринга.
**После обновления Xiaomi все сломалось.**
Это ожидаемый риск reverse-engineering проекта. Проверьте issues/README и логи, затем обновите код или временно отключите проблемную часть.
**После обновления Xiaomi всё сломалось** — это ожидаемый риск
при работе с неофициальным 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
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.
- Runs manual sync from a Telegram button.
- Syncs data automatically on a schedule.
- Stores history in SQLite under `data/`.
- Exports accumulated tables as a ZIP with CSV files.
- Runs with Docker Compose.
- Writes the Xiaomi token atomically with mode `0600`.
- View recent steps, sleep, heart rate, and SpO2 in Telegram.
- Manual and scheduled automatic synchronization.
- History storage in SQLite (`data/`).
- Export of all tables to a ZIP archive with CSV files directly into the chat.
- Deployment via Docker Compose.
- Atomic writing of the Xiaomi token with `0600` permissions.
- Smart auto-binding to the first user (whitelist).
## Who It Is For
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
## How it works
```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;
- `fitness-bot` - responds in Telegram, shows the menu, starts manual sync and exports data.
- `tracker` periodically synchronizes data from Xiaomi Fitness;
- `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
- A server or home machine with Docker and Docker Compose.
- A Telegram bot token from [@BotFather](https://t.me/BotFather).
- Your Telegram user id.
- Docker and Docker Compose (or installed Python 3.10+).
- Telegram bot token from [@BotFather](https://t.me/BotFather).
- A Xiaomi account with Xiaomi Fitness data.
## Quick Start
1. Copy the secrets template:
### Method 1: Seamless One-Click Installation (Recommended)
```sh
cp secrets.env.example secrets.env
```
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:
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
TELEGRAM_BOT_TOKEN=123456:replace-me
TELEGRAM_ALLOWED_USER_ID=123456789
```
The installer will automatically create a `miband-bot` directory, download and extract the project files, verify dependencies, and launch the interactive setup!
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.
3. Start the service:
---
```sh
docker compose up -d --build
docker compose logs -f fitness-bot
```
### Method 2: Launch from Downloaded Directory
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
TELEGRAM_BOT_TOKEN=123456:replace-me
TELEGRAM_ALLOWED_USER_ID=123456789
SYNC_INTERVAL=900
QUERY_DURATION=2
ENABLE_FDS_SLEEP_DETAILS=true
```
### Method 3: Fully Manual Setup:
- `TELEGRAM_BOT_TOKEN` - your Telegram bot token.
- `TELEGRAM_ALLOWED_USER_ID` - the single Telegram user id allowed to access the bot.
- `SYNC_INTERVAL` - background sync interval in seconds. `900` = 15 minutes.
- `QUERY_DURATION` - how many recent days to query on each sync.
- `ENABLE_FDS_SLEEP_DETAILS` - whether to try fetching detailed FDS sleep data.
1. Copy the configuration template:
```sh
cp secrets.env.example secrets.env
```
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;
- `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.
Paths to the database and status files are defined in `compose.yaml`. For running without Docker, refer to `secrets.env.example`.
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.
- `/sync` - run manual sync.
- `/status` - show local database status.
| File | Content |
| --- | --- |
| `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.
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.
| Command | Action |
| --- | --- |
| `/start` | Open menu or start Xiaomi login flow |
| `/sync` | Start manual synchronization |
| `/status` | Show local database status |
## Local Development
```sh
python3 -m venv .venv
.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 mi-fitness-python/tests/unit
.venv/bin/ruff check .
.venv/bin/python -m pip check
```
Entrypoints are kept for Docker and local compatibility:
Entry points:
```sh
python -u miband_sync.py
python -u fitness_bot.py
```
If installed as a Python package, console scripts are available:
```sh
miband-sync
miband-fitness-bot
python -u miband_sync.py # or: miband-sync
python -u fitness_bot.py # or: miband-fitness-bot
```
## Troubleshooting
**The bot does not respond.**
Check `TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USER_ID` and logs:
**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.
```sh
docker compose logs -f fitness-bot
```
**Sync says token is missing.**
Open the bot in Telegram, send `/start` and complete the Xiaomi login flow.
**Token not found** — send `/start` and complete the Xiaomi login flow.
**The token expired.**
Run Xiaomi login again from the menu. You can remove the old token from `data/`.
**Token expired** — start a re-login from the menu; the old file
can be deleted from `data/`.
**Some data is missing or there is no SpO2/sleep detail.**
Check that Xiaomi Fitness itself shows that data. Some data depends on the band model, sharing settings and unofficial API availability.
**No SpO2 or sleep details** — make sure this data is visible
in the Xiaomi Fitness app itself. Availability depends on the band model
and data sharing settings.
**Everything broke after a Xiaomi update.**
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.
**Everything broke after a Xiaomi update** — this is an expected risk
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 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__":
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 sys
from miband_tracker.config import ConfigError, Settings
from miband_tracker.sync import daemon_main, run_sync
from miband_tracker.stdio import configure_utf8_stdio
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"]
+14 -4
View File
@@ -35,10 +35,12 @@ from miband_tracker.secure_files import save_auth_token
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(
level=logging.INFO,
level=logging.WARNING,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -46,6 +48,13 @@ logging.getLogger("telegram").setLevel(logging.WARNING)
logging.getLogger("telegram.ext").setLevel(logging.WARNING)
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
# ---------------------------------------------------------------------------
@@ -1499,10 +1508,11 @@ def main() -> None:
ALLOWED_USER_ID = SETTINGS.telegram_allowed_user_id
if ALLOWED_USER_ID is not None:
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:
DB_PATH = str(SETTINGS.db_path)
logger.info("Стартую fitness-bot в режиме ожидания привязки владельца (первое входящее сообщение привяжет бота)...")
print("Бот запущен. Отправьте /start в Telegram чтобы привязать аккаунт.")
init_state_db()
app = Application.builder().token(BOT_TOKEN).build()
+34 -1
View File
@@ -8,6 +8,28 @@ from dataclasses import dataclass
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):
"""Invalid runtime configuration."""
@@ -44,7 +66,18 @@ class Settings:
@classmethod
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(
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)
# Блокируем первые 64 байта файла без ожидания (non-blocking)
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
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 .lock import LockUnavailable, exclusive_file_lock
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
@@ -34,7 +35,7 @@ class SyncResult:
def log(message: str) -> None:
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:
@@ -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:
settings = settings or Settings.from_env()
if settings.sync_interval <= 0:
log("Running in one-shot mode.")
result = await run_sync(settings=settings)
return 0 if result.success else 1
log(f"Running in daemon mode. Sync interval: {settings.sync_interval} seconds.")
_waiting_logged = False
while True:
try:
current_settings = Settings.from_env()
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)
continue
_waiting_logged = False # Reset so we log again if user unregisters
await run_sync(settings=current_settings)
except Exception as 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
log(f"Sleeping for {interval} seconds...")
await asyncio.sleep(interval)
+1
View File
@@ -6,3 +6,4 @@ python-telegram-bot==21.6
qrcode==8.0
requests==2.32.3
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
:: ==============================================================================
:: miband-bot Smart Setup Script for Windows (100% Flat robust version)
:: ==============================================================================
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
echo ======================================================================
echo 🚀 УМНЫЙ ЗАПУСК И НАСТРОЙКА miband-bot 🚀
echo ======================================================================
echo Этот скрипт поможет вам быстро развернуть личного Telegram-бота
echo для сбора данных Xiaomi Fitness / Mi Band.
echo.
:: ------------------------------------------------------------------------------
:: Шаг 1. Выбор метода установки
:: ------------------------------------------------------------------------------
echo Как вы хотите запустить бота?
echo 1] Напрямую на Python (Рекомендуется. Потребляет минимум памяти)
echo 2] В Docker контейнерах (Всё работает в фоне)
echo miband-bot
echo.
:CHOOSE_MODE
if "%AUTO_START%"=="1" if exist secrets.env goto MODE_PYTHON
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%"=="2" goto MODE_DOCKER
echo Неверный ввод. Пожалуйста, введите 1 или 2.
echo.
goto CHOOSE_MODE
:: ------------------------------------------------------------------------------
:: Шаг 2. Проверка требований
:: ------------------------------------------------------------------------------
:MODE_DOCKER
echo.
echo [1/3] Проверка окружения Docker...
where docker >nul 2>nul
if errorlevel 1 (
echo ❌ Docker CLI не найден. Пожалуйста, установите Docker Desktop или настройте WSL 2.
pause
exit /b 1
)
if errorlevel 1 ( echo ! Docker не найден. Установите Docker Desktop. & pause & exit /b 1 )
docker info >nul 2>nul
if errorlevel 1 (
echo ❌ Демон Docker не запущен. Пожалуйста, запустите Docker и перезапустите скрипт.
pause
exit /b 1
)
echo ✓ Движок Docker активен и готов к работе!
if errorlevel 1 ( echo ! Docker не запущен. Запустите Docker Desktop. & pause & exit /b 1 )
echo OK Docker готов
set DOCKER_ACTIVE=1
goto SETUP_ENV
:MODE_PYTHON
echo.
echo [1/3] Проверка окружения Python...
python --version >nul 2>nul
if not errorlevel 1 goto PYTHON_OK
echo Python не найден или не настроен в вашей системе.
echo Вы можете скачать его вручную по ссылке: https://www.python.org/downloads/
echo ! Python не найден: https://www.python.org/downloads/
echo.
set install_python=y
set /p install_python="Хотите, чтобы я автоматически скачал и установил Python 3.11? [Y/n]: "
if /i not "%install_python%"=="y" (
echo Установка отменена. Пожалуйста, установите Python вручную.
pause
exit /b 1
)
set /p install_python=" Установить Python 3.11 автоматически? [Y/n]: "
if /i not "%install_python%"=="y" ( echo Установите Python вручную и повторите. & pause & exit /b 1 )
echo.
echo Скачивание установщика Python 3.11.9...
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 Установка Python (это займет около минуты, пожалуйста, подождите)...
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'"
echo Установка...
python_installer.exe /quiet InstallAllUsers=0 PrependPath=1 Include_test=0
del python_installer.exe
:: Добавляем установленный Python в PATH текущей сессии
set "PATH=%LocalAppData%\Programs\Python\Python311;%LocalAppData%\Programs\Python\Python311\Scripts;%PATH%"
python --version >nul 2>nul
if errorlevel 1 (
echo ❌ Не удалось автоматически установить Python или добавить его в PATH.
echo Пожалуйста, установите Python вручную по ссылке: https://www.python.org/downloads/
pause
exit /b 1
)
if errorlevel 1 ( echo ! Не удалось установить Python. & pause & exit /b 1 )
:PYTHON_OK
echo Python найден!
echo OK Python готов
set DOCKER_ACTIVE=0
goto SETUP_ENV
:: ------------------------------------------------------------------------------
:: Шаг 3. Настройка secrets.env
:: ------------------------------------------------------------------------------
:SETUP_ENV
echo.
echo [2/3] Конфигурация параметров бота (secrets.env)...
if not exist secrets.env goto INPUT_ENV_VALUES
echo Файл конфигурации secrets.env уже существует.
if "%AUTO_START%"=="1" goto LAUNCH_PHASE
set overwrite_env=n
set /p overwrite_env="Хотите перезаписать его и настроить заново? [y/N]: "
if /i not "%overwrite_env%"=="y" (
echo ✓ Сохраняем существующий secrets.env.
goto LAUNCH_PHASE
)
set /p overwrite_env=" Конфигурация уже есть. Перенастроить? [y/N]: "
if /i not "%overwrite_env%"=="y" goto LAUNCH_PHASE
:INPUT_ENV_VALUES
echo.
echo Сейчас мы настроим основные параметры безопасности.
echo.
echo Токен бота от @BotFather в Telegram:
:INPUT_TOKEN
echo 🔑 Шаг 1. Получение Telegram Bot Token
echo Для работы бота необходим токен. Его можно получить бесплатно за 1 минуту:
echo 1. Откройте Telegram и перейдите к официальному боту @BotFather.
echo 2. Отправьте ему команду /newbot.
echo 3. Введите название для бота (например, Мой Mi Band Бот).
echo 4. Введите уникальное имя пользователя (username) на английском,
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.
set bot_token=
set /p bot_token=" Токен: "
if "%bot_token%"=="" goto TOKEN_EMPTY
echo %bot_token% | find ":" >nul 2>nul
if errorlevel 1 goto TOKEN_INVALID
goto TOKEN_OK
echo 🔒 Шаг 2. Автоматическая привязка владельца (Whitelist ID)
echo Вам НЕ НУЖНО вручную искать и вводить ваш Telegram User ID!
echo Сразу после запуска бота откройте его в Telegram и отправьте команду /start.
echo Бот автоматически распознает ваш ID, запишет его в белый список
echo и заблокирует доступ для всех остальных пользователей.
echo.
:TOKEN_EMPTY
echo Токен не может быть пустым.
goto INPUT_TOKEN
:: Создание secrets.env (запись построчно во избежание багов парсера скобок)
echo # ============================================================================== > secrets.env
echo # Конфигурация secrets.env для miband-bot >> secrets.env
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
:TOKEN_INVALID
echo Неверный формат - токен должен содержать двоеточие.
goto INPUT_TOKEN
echo ✓ Файл secrets.env успешно создан!
echo.
:TOKEN_OK
(
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
echo [3/3] Подготовка среды выполнения...
echo.
if not "%DOCKER_ACTIVE%"=="1" goto PYTHON_LAUNCH
:DOCKER_LAUNCH
set launch_now=y
set /p launch_now="Хотите запустить miband-bot в Docker прямо сейчас? [Y/n]: "
if /i not "%launch_now%"=="y" (
echo.
echo Бот настроен, но не запущен.
echo Запустите вручную командой: docker compose up -d --build
goto END_LAUNCH
)
echo Сборка и запуск контейнеров...
set /p launch_now=" Запустить в Docker сейчас? [Y/n]: "
if /i not "%launch_now%"=="y" ( echo Запуск вручную: docker compose up -d --build & goto END_LAUNCH )
docker compose up -d --build
if not errorlevel 1 (
echo.
echo ======================================================================
echo 🎉 miband-bot УСПЕШНО НАСТРОЕН И ЗАПУЩЕН В DOCKER! 🎉
echo ======================================================================
echo Теперь откройте бота в Telegram и отправьте команду /start.
echo.
echo Логи: docker compose logs -f fitness-bot
echo Стоп: docker compose down
echo ======================================================================
echo OK Бот запущен в Docker. Отправьте /start в Telegram.
echo Логи: docker compose logs -f fitness-bot
) else (
echo Ошибка при сборке или запуске Docker Compose.
echo ! Ошибка запуска Docker.
)
goto END_LAUNCH
:PYTHON_LAUNCH
echo Создаем виртуальное окружение .venv и устанавливаем зависимости...
if not exist .venv (
python -m venv .venv
)
echo Подготовка...
if not exist .venv ( python -m venv .venv >nul 2>nul )
call .venv\Scripts\activate
echo Установка библиотек...
pip install -r requirements.txt -e mi-fitness-python
pip install -r requirements.txt -e mi-fitness-python > pip_install.log 2>&1
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 построчно (полностью без скобочных блоков)
echo @echo off > run_local.bat
echo chcp 65001 ^> nul >> run_local.bat
echo cd /d "%%~dp0" >> run_local.bat
echo if not exist .venv ^( >> run_local.bat
echo echo Ошибка: Виртуальное окружение .venv не найдено. Запустите setup.bat сначала. >> run_local.bat
echo pause >> run_local.bat
echo exit /b 1 >> run_local.bat
echo ^) >> run_local.bat
echo. >> run_local.bat
echo :: Загрузка переменных из secrets.env >> run_local.bat
echo if exist secrets.env ^( >> run_local.bat
echo for /f "usebackq delims=" %%%%x in ^("secrets.env"^) do ^( >> run_local.bat
echo echo %%%%x ^| findstr /r "^#" ^>nul >> run_local.bat
echo if errorlevel 1 ^( >> run_local.bat
echo set %%%%x >> run_local.bat
echo ^) >> run_local.bat
echo ^) >> run_local.bat
echo ^) >> run_local.bat
echo. >> run_local.bat
echo :: Настройка локальных путей к папке data >> run_local.bat
echo set DATA_DIR=.\data >> run_local.bat
echo set DB_PATH=.\data\miband_%%TELEGRAM_ALLOWED_USER_ID%%.db >> run_local.bat
echo set STATUS_PATH=.\data\status_%%TELEGRAM_ALLOWED_USER_ID%%.json >> run_local.bat
echo set BOT_STATE_DB_PATH=.\data\fitness_bot_state.db >> run_local.bat
echo. >> run_local.bat
echo call .venv\Scripts\activate >> run_local.bat
echo === Запуск miband-bot локально ^(без Docker^) === >> run_local.bat
echo Для завершения работы закройте это окно консоли. >> run_local.bat
echo. >> run_local.bat
echo Запуск синхронизатора и Telegram-бота... >> run_local.bat
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
>run_local.bat echo @echo off
>>run_local.bat echo chcp 65001 ^> nul
>>run_local.bat echo cd /d "%%~dp0"
>>run_local.bat echo if not exist .venv ^(
>>run_local.bat echo echo ! .venv не найдено. Запустите setup.bat заново.
>>run_local.bat echo pause
>>run_local.bat echo exit /b 1
>>run_local.bat echo ^)
>>run_local.bat echo if not exist run.py ^(
>>run_local.bat echo echo ! run.py не найден. Обновите проект и запустите setup.bat заново.
>>run_local.bat echo pause
>>run_local.bat echo exit /b 1
>>run_local.bat echo ^)
>>run_local.bat echo call .venv\Scripts\activate
>>run_local.bat echo set PYTHONUTF8=1
>>run_local.bat echo set PYTHONIOENCODING=utf-8
>>run_local.bat echo md data 2^>nul
>>run_local.bat echo cls
>>run_local.bat echo echo.
>>run_local.bat echo echo miband-bot
>>run_local.bat echo echo.
>>run_local.bat echo echo OK Бот запущен. Не закрывайте это окно.
>>run_local.bat echo echo Логи: data\bot.log / data\sync.log
>>run_local.bat echo echo.
>>run_local.bat echo python -u run.py
>>run_local.bat echo echo.
>>run_local.bat echo echo Бот остановлен. Можно закрыть окно.
>>run_local.bat echo echo.
>>run_local.bat echo pause
if "%AUTO_START%"=="1" goto RUN_LOCAL_NOW
echo ✓ Создан удобный скрипт запуска: run_local.bat
echo.
set launch_now=y
set /p launch_now="Хотите запустить бота локально прямо сейчас? [Y/n]: "
if /i "%launch_now%"=="y" (
call run_local.bat
)
set /p launch_now=" Запустить бота сейчас? [Y/n]: "
if /i "%launch_now%"=="y" ( call run_local.bat )
goto END_LAUNCH
:RUN_LOCAL_NOW
call run_local.bat
:END_LAUNCH
echo.
+135 -283
View File
@@ -1,319 +1,171 @@
#!/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'
# Насыщенный желтый
YELLOW='\033[1;33m'
# Глубокий синий
BLUE='\033[0;34m'
# Пурпурный
MAGENTA='\033[0;35m'
# Серый / обычный
NC='\033[0;37m'
BOLD='\033[1m'
RED='\033[0;31m'
NC='\033[0m'
ok() { echo -e " ${GREEN}${NC} $1"; }
err() { echo -e " ${RED}${NC} $1"; }
clear
echo -e "${BLUE}${BOLD}======================================================================"
echo -e " 🚀 УМНЫЙ ЗАПУСК И НАСТРОЙКА 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 " miband-bot"
echo ""
# --- Выбор режима ---
while true; do
read -p "Выберите вариант (1 или 2) [1]: " install_mode
if [ -z "$install_mode" ]; then
install_mode="1"
fi
if [ "$install_mode" = "1" ] || [ "$install_mode" = "2" ]; then
break
else
echo -e "${RED}Неверный ввод. Пожалуйста, введите 1 или 2.${NC}"
fi
read -p " [1] Python (рек.) [2] Docker Выбор [1]: " install_mode
install_mode="${install_mode:-1}"
[ "$install_mode" = "1" ] || [ "$install_mode" = "2" ] && break
done
echo ""
# ------------------------------------------------------------------------------
# Шаг 2. Проверка требований выбранного режима
# ------------------------------------------------------------------------------
# --- Проверка требований ---
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
DOCKER_AVAILABLE=false
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."
err "Docker не найден. Установите Docker Desktop или Colima (brew install colima)."
exit 1
elif [ "$DOCKER_RUNNING" = false ]; then
echo -e "${RED}❌ Служба Docker установлена, но сейчас не запущена.${NC}"
echo -e "Пожалуйста, запустите Docker-демон (например, 'colima start' на Mac"
echo -e "или 'sudo systemctl start docker' на Linux) и перезапустите скрипт."
fi
if ! docker info &> /dev/null; then
err "Docker не запущен. Запустите демон (colima start / Docker Desktop) и повторите."
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
echo -e "${GREEN}✓ Движок Docker активен и готов к работе!${NC}"
if docker compose version &> /dev/null; then
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
err "Плагин docker compose не найден."
exit 1
fi
ok "Docker готов"
DOCKER_MODE=true
else
echo -e "${BOLD}[1/3] Проверка окружения Python...${NC}"
PYTHON_CMD=""
if command -v python3 &> /dev/null; then
PYTHON_CMD="python3"
elif command -v python &> /dev/null; then
# Проверяем версию python
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"
for cmd in python3 python; do
if command -v "$cmd" &> /dev/null; then
ver=$("$cmd" -c 'import sys; print(sys.version_info[0])' 2>/dev/null)
[ "$ver" = "3" ] && PYTHON_CMD="$cmd" && break
fi
done
echo ""
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}"
if [ -z "$PYTHON_CMD" ]; then
err "Python 3 не найден. Установите: https://www.python.org/downloads/"
exit 1
fi
# Создаем удобный скрипт для локального запуска
LAUNCHER_FILE="run_local.sh"
cat << 'EOF' > "$LAUNCHER_FILE"
#!/usr/bin/env bash
# ==============================================================================
# Скрипт локального запуска miband-bot (без Docker)
# ==============================================================================
cd "$(dirname "$0")"
ok "Python готов ($PYTHON_CMD)"
DOCKER_MODE=false
fi
# --- Конфигурация ---
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
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
fi
# Загружаем переменные из secrets.env в окружение текущего процесса
if [ -f secrets.env ]; then
while IFS= read -r line || [ -n "$line" ]; do
# Очищаем пробелы и переносы
line=$(echo "$line" | xargs 2>/dev/null || echo "$line")
# Игнорируем комментарии и пустые строки
if [[ ! "$line" =~ ^# ]] && [[ ! -z "$line" ]]; then
export "$line"
fi
done < secrets.env
# Генерируем run_local.sh — логи Python уходят в файлы
cat << 'EOF' > run_local.sh
#!/usr/bin/env bash
# Запуск miband-bot (Python читает secrets.env автоматически)
cd "$(dirname "$0")"
if [ ! -d ".venv" ]; then
echo " ❌ .venv не найдена. Запустите setup.sh заново."
exit 1
fi
# Настраиваем локальные пути к папке data (чтобы не писать в глобальные пути /opt)
export DATA_DIR="./data"
export DB_PATH="./data/miband_${TELEGRAM_ALLOWED_USER_ID}.db"
export STATUS_PATH="./data/status_${TELEGRAM_ALLOWED_USER_ID}.json"
export BOT_STATE_DB_PATH="./data/fitness_bot_state.db"
if [ ! -f "run.py" ]; then
echo " ❌ run.py не найден. Обновите проект и запустите setup.sh заново."
exit 1
fi
source .venv/bin/activate
echo "=== Запуск miband-bot локально на Python ==="
echo "Нажмите Ctrl+C для завершения работы обоих процессов."
mkdir -p data
clear
echo ""
echo " miband-bot"
echo ""
echo " ✓ Бот запущен. Не закрывайте это окно."
echo " Логи: data/bot.log / data/sync.log"
echo ""
# При завершении скрипта (Ctrl+C) убиваем все запущенные нами фоновые задачи
trap "kill 0" EXIT
# Запускаем фоновый синхронизатор и сам Telegram бот параллельно
python -u miband_sync.py &
python -u fitness_bot.py &
# Ожидаем завершения
wait
python -u run.py
echo ""
echo " Бот остановлен. Можно закрыть окно."
echo ""
EOF
chmod +x "$LAUNCHER_FILE"
echo -e "${GREEN}✓ Создан удобный скрипт запуска: ${BOLD}./run_local.sh${NC}"
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
chmod +x run_local.sh
ok "Создан скрипт запуска: ./run_local.sh"
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()