added RAG, Multiuser, TG bot
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import httpx
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.config import get_settings
|
||||
from app.db.base import SessionLocal
|
||||
from app.db.models import User
|
||||
from app.homelab.comfyui import ComfyUIClient
|
||||
from app.homelab.context import resolve_timezone
|
||||
from app.homelab.digest import build_morning_digest
|
||||
from app.homelab.monitoring import check_netdata_alerts
|
||||
from app.homelab_scoped.notices import post_chat_notice
|
||||
from app.homelab.state import get_state, set_state
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WATCH_INTERVAL_SEC = 60
|
||||
_netdata_tick = 0
|
||||
|
||||
|
||||
async def homelab_watcher_loop() -> None:
|
||||
global _netdata_tick
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(WATCH_INTERVAL_SEC)
|
||||
await _tick_morning_digest()
|
||||
await _tick_rofl()
|
||||
settings = get_settings()
|
||||
_netdata_tick += WATCH_INTERVAL_SEC
|
||||
if _netdata_tick >= settings.netdata_poll_interval_sec:
|
||||
_netdata_tick = 0
|
||||
await _tick_netdata()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception:
|
||||
logger.exception("Homelab watcher error")
|
||||
|
||||
|
||||
async def _tick_morning_digest() -> None:
|
||||
settings = get_settings()
|
||||
if not settings.morning_digest_enabled:
|
||||
return
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
users = db.scalars(select(User).where(User.is_active.is_(True))).all()
|
||||
digest = build_morning_digest(db, include_news=True)
|
||||
for user in users:
|
||||
tz_name = resolve_timezone(db, user.id)
|
||||
try:
|
||||
tz = ZoneInfo(tz_name)
|
||||
except Exception:
|
||||
tz = ZoneInfo("Europe/Moscow")
|
||||
|
||||
now = datetime.now(tz)
|
||||
target_min = settings.morning_digest_hour * 60 + settings.morning_digest_minute
|
||||
current_min = now.hour * 60 + now.minute
|
||||
if current_min < target_min or current_min >= target_min + 3:
|
||||
continue
|
||||
|
||||
today = now.date().isoformat()
|
||||
state_key = f"last_morning_digest_date:{user.id}"
|
||||
if get_state(db, state_key) == today:
|
||||
continue
|
||||
|
||||
post_chat_notice(digest, user.id)
|
||||
set_state(db, state_key, today)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
async def _tick_netdata() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
notices = check_netdata_alerts(db)
|
||||
if not notices:
|
||||
return
|
||||
users = db.scalars(select(User).where(User.is_active.is_(True))).all()
|
||||
for user in users:
|
||||
for notice in notices:
|
||||
post_chat_notice(notice, user.id)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
async def _comfyui_reachable(base_url: str) -> bool:
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=httpx.Timeout(3.0, connect=2.0)) as client:
|
||||
response = await client.get(f"{base_url.rstrip('/')}/system_stats")
|
||||
return response.status_code < 500
|
||||
except (httpx.TimeoutException, httpx.ConnectError, httpx.NetworkError):
|
||||
return False
|
||||
|
||||
|
||||
async def _tick_rofl() -> None:
|
||||
settings = get_settings()
|
||||
if not settings.comfyui_enabled or not settings.comfyui_rofl_enabled:
|
||||
return
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
users = db.scalars(select(User).where(User.is_active.is_(True))).all()
|
||||
for user in users:
|
||||
tz_name = resolve_timezone(db, user.id)
|
||||
try:
|
||||
tz = ZoneInfo(tz_name)
|
||||
except Exception:
|
||||
tz = ZoneInfo("Europe/Moscow")
|
||||
now = datetime.now(tz)
|
||||
last_raw = get_state(db, f"last_comfy_rofl_at:{user.id}")
|
||||
if last_raw:
|
||||
try:
|
||||
last_at = datetime.fromisoformat(last_raw)
|
||||
if last_at.tzinfo is None:
|
||||
last_at = last_at.replace(tzinfo=tz)
|
||||
if (now - last_at).total_seconds() < settings.comfyui_rofl_min_interval_hours * 3600:
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if random.random() > settings.comfyui_rofl_probability:
|
||||
continue
|
||||
|
||||
today = now.date().isoformat()
|
||||
count_key = f"comfy_rofl_count_{today}:{user.id}"
|
||||
count_raw = get_state(db, count_key) or "0"
|
||||
try:
|
||||
count = int(count_raw)
|
||||
except ValueError:
|
||||
count = 0
|
||||
if count >= settings.comfyui_rofl_max_per_day:
|
||||
continue
|
||||
|
||||
client = ComfyUIClient()
|
||||
if not await _comfyui_reachable(client.base_url):
|
||||
continue
|
||||
|
||||
prompt = client.random_rofl_prompt()
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
client.generate_image(prompt),
|
||||
timeout=settings.comfyui_timeout_sec + 15,
|
||||
)
|
||||
except (asyncio.TimeoutError, httpx.TimeoutException, httpx.ConnectError) as exc:
|
||||
logger.warning("Rofl image skipped (ComfyUI): %s", exc)
|
||||
continue
|
||||
if not result.get("ok"):
|
||||
logger.warning("Rofl image failed: %s", result.get("error"))
|
||||
continue
|
||||
|
||||
url = result.get("url", "")
|
||||
post_chat_notice(
|
||||
f"🎨 **Рофл дня**\n\n\n\n_{prompt}_",
|
||||
user.id,
|
||||
)
|
||||
set_state(db, count_key, str(count + 1))
|
||||
set_state(db, f"last_comfy_rofl_at:{user.id}", now.isoformat())
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user