mirror of
https://github.com/alexgetmancom/miband-bot.git
synced 2026-07-22 13:40:14 +03:00
feat: add dynamic owner binding
This commit is contained in:
@@ -64,7 +64,20 @@ STEP_GOAL = 10_000 # можно вынести в env при желании
|
||||
# Auth helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
def is_allowed(update: Update) -> bool:
|
||||
global ALLOWED_USER_ID
|
||||
uid = update.effective_user.id if update.effective_user else None
|
||||
if uid is None:
|
||||
return False
|
||||
if ALLOWED_USER_ID is None:
|
||||
ALLOWED_USER_ID = uid
|
||||
try:
|
||||
allowed_user_file = SETTINGS.data_dir / "allowed_user.id"
|
||||
SETTINGS.data_dir.mkdir(parents=True, exist_ok=True)
|
||||
allowed_user_file.write_text(str(uid), encoding="utf-8")
|
||||
logger.info("🎉 Бот успешно привязан к первому пользователю (ID: %s)!", uid)
|
||||
except Exception as e:
|
||||
logger.error("Не удалось сохранить ID владельца в файл: %s", e)
|
||||
return True
|
||||
return uid == ALLOWED_USER_ID
|
||||
|
||||
|
||||
@@ -1483,11 +1496,15 @@ def main() -> None:
|
||||
logger.error("%s", exc)
|
||||
sys.exit(1)
|
||||
BOT_TOKEN = SETTINGS.telegram_bot_token
|
||||
ALLOWED_USER_ID = SETTINGS.require_user_id()
|
||||
DB_PATH = str(SETTINGS.user_db_path(ALLOWED_USER_ID))
|
||||
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)
|
||||
else:
|
||||
DB_PATH = str(SETTINGS.db_path)
|
||||
logger.info("Стартую fitness-bot в режиме ожидания привязки владельца (первое входящее сообщение привяжет бота)...")
|
||||
|
||||
init_state_db()
|
||||
logger.info("Стартую fitness-bot для пользователя: %s", ALLOWED_USER_ID)
|
||||
app = Application.builder().token(BOT_TOKEN).build()
|
||||
app.add_handler(CommandHandler("start", cmd_start))
|
||||
app.add_handler(CommandHandler("status", cmd_status))
|
||||
|
||||
@@ -46,8 +46,16 @@ class Settings:
|
||||
def from_env(cls, *, require_bot: bool = False) -> Settings:
|
||||
data_dir = Path(os.environ.get("DATA_DIR", "/opt/miband-tracker/data"))
|
||||
allowed_user_id = parse_single_user_id(
|
||||
os.environ.get("TELEGRAM_ALLOWED_USER_ID", ""), required=require_bot
|
||||
os.environ.get("TELEGRAM_ALLOWED_USER_ID", ""), required=False
|
||||
)
|
||||
# Если ID не задан в env, пробуем загрузить из файла allowed_user.id
|
||||
allowed_user_file = data_dir / "allowed_user.id"
|
||||
if allowed_user_id is None and allowed_user_file.exists():
|
||||
try:
|
||||
allowed_user_id = int(allowed_user_file.read_text(encoding="utf-8").strip())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
bot_token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
|
||||
if require_bot and not bot_token:
|
||||
raise ConfigError("TELEGRAM_BOT_TOKEN не задан")
|
||||
|
||||
+40
-6
@@ -3,13 +3,24 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fcntl
|
||||
import os
|
||||
import time
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
|
||||
# Попытка импорта fcntl (для Unix-систем) или msvcrt (для Windows)
|
||||
try:
|
||||
import fcntl
|
||||
_HAS_FCNTL = True
|
||||
except ImportError:
|
||||
_HAS_FCNTL = False
|
||||
try:
|
||||
import msvcrt
|
||||
_HAS_MSVCRT = True
|
||||
except ImportError:
|
||||
_HAS_MSVCRT = False
|
||||
|
||||
|
||||
class LockUnavailable(RuntimeError):
|
||||
"""Raised when another process already owns the sync lock."""
|
||||
@@ -19,10 +30,23 @@ class LockUnavailable(RuntimeError):
|
||||
def exclusive_file_lock(path: Path) -> Iterator[None]:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("a+", encoding="utf-8") as lock_file:
|
||||
try:
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError as exc:
|
||||
raise LockUnavailable(f"Lock is already held: {path}") from exc
|
||||
fd = lock_file.fileno()
|
||||
|
||||
if _HAS_FCNTL:
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except BlockingIOError as exc:
|
||||
raise LockUnavailable(f"Lock is already held: {path}") from exc
|
||||
elif _HAS_MSVCRT:
|
||||
try:
|
||||
lock_file.seek(0)
|
||||
# Блокируем первые 64 байта файла без ожидания (non-blocking)
|
||||
msvcrt.locking(fd, msvcrt.LK_NBLCK, 64)
|
||||
except (OSError, IOError) as exc:
|
||||
raise LockUnavailable(f"Lock is already held: {path}") from exc
|
||||
else:
|
||||
# Резервный вариант, если блокировки недоступны на платформе
|
||||
pass
|
||||
|
||||
try:
|
||||
lock_file.seek(0)
|
||||
@@ -31,4 +55,14 @@ def exclusive_file_lock(path: Path) -> Iterator[None]:
|
||||
lock_file.flush()
|
||||
yield
|
||||
finally:
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
||||
if _HAS_FCNTL:
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||
except Exception:
|
||||
pass
|
||||
elif _HAS_MSVCRT:
|
||||
try:
|
||||
lock_file.seek(0)
|
||||
msvcrt.locking(fd, msvcrt.LK_UNLCK, 64)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
+12
-3
@@ -371,8 +371,17 @@ async def daemon_main(settings: Settings | None = None) -> int:
|
||||
log(f"Running in daemon mode. Sync interval: {settings.sync_interval} seconds.")
|
||||
while True:
|
||||
try:
|
||||
await run_sync(settings=settings)
|
||||
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...")
|
||||
await asyncio.sleep(5)
|
||||
continue
|
||||
|
||||
await run_sync(settings=current_settings)
|
||||
except Exception as exc:
|
||||
log(f"Unhandled error in main loop: {exc}")
|
||||
log(f"Sleeping for {settings.sync_interval} seconds...")
|
||||
await asyncio.sleep(settings.sync_interval)
|
||||
|
||||
# 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)
|
||||
|
||||
Reference in New Issue
Block a user