feat: backend orkestrasyonunu ve arac entegrasyonlarini genislet
This commit is contained in:
73
backend/app/automation/scheduler.py
Normal file
73
backend/app/automation/scheduler.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from typing import Callable
|
||||
|
||||
from app.automation.store import AutomationService
|
||||
from app.db import AutomationORM, session_scope
|
||||
from app.orchestrator import WiseClawOrchestrator
|
||||
from app.telegram.bot import TelegramBotService
|
||||
|
||||
|
||||
class AutomationScheduler:
|
||||
def __init__(self, orchestrator_factory: Callable[[], object], telegram_bot: TelegramBotService) -> None:
|
||||
self.orchestrator_factory = orchestrator_factory
|
||||
self.telegram_bot = telegram_bot
|
||||
self._task: asyncio.Task[None] | None = None
|
||||
self._running = False
|
||||
|
||||
async def start(self) -> None:
|
||||
if self._task is not None:
|
||||
return
|
||||
self._running = True
|
||||
self._task = asyncio.create_task(self._loop())
|
||||
|
||||
async def stop(self) -> None:
|
||||
self._running = False
|
||||
if self._task is not None:
|
||||
self._task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await self._task
|
||||
self._task = None
|
||||
|
||||
async def _loop(self) -> None:
|
||||
while self._running:
|
||||
try:
|
||||
await self._tick()
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(30)
|
||||
|
||||
async def _tick(self) -> None:
|
||||
with session_scope() as session:
|
||||
service = AutomationService(session)
|
||||
due_items = service.due_automations()
|
||||
due_ids = [item.id for item in due_items]
|
||||
|
||||
for automation_id in due_ids:
|
||||
await self._run_automation(automation_id)
|
||||
|
||||
async def _run_automation(self, automation_id: int) -> None:
|
||||
with session_scope() as session:
|
||||
service = AutomationService(session)
|
||||
item = session.get(AutomationORM, automation_id)
|
||||
if item is None or item.status != "active":
|
||||
return
|
||||
prompt = item.prompt
|
||||
user_id = item.telegram_user_id
|
||||
|
||||
try:
|
||||
with self.orchestrator_factory() as session:
|
||||
orchestrator = WiseClawOrchestrator(session)
|
||||
result = await orchestrator.handle_text_message(user_id, prompt)
|
||||
await self.telegram_bot.send_message(user_id, f"⏰ Otomasyon sonucu: {result}")
|
||||
with session_scope() as session:
|
||||
service = AutomationService(session)
|
||||
item = session.get(AutomationORM, automation_id)
|
||||
if item is not None:
|
||||
service.mark_run_result(item, result)
|
||||
except Exception as exc:
|
||||
with session_scope() as session:
|
||||
service = AutomationService(session)
|
||||
item = session.get(AutomationORM, automation_id)
|
||||
if item is not None:
|
||||
service.mark_run_error(item, str(exc))
|
||||
455
backend/app/automation/store.py
Normal file
455
backend/app/automation/store.py
Normal file
@@ -0,0 +1,455 @@
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.db import AuditLogORM, AutomationORM, AutomationWizardORM
|
||||
from app.models import AutomationRecord
|
||||
|
||||
|
||||
LOCAL_TZ = ZoneInfo("Europe/Istanbul")
|
||||
WEEKDAY_MAP = {
|
||||
"pzt": 0,
|
||||
"pazartesi": 0,
|
||||
"sal": 1,
|
||||
"sali": 1,
|
||||
"çar": 2,
|
||||
"cars": 2,
|
||||
"çarşamba": 2,
|
||||
"carsamba": 2,
|
||||
"per": 3,
|
||||
"persembe": 3,
|
||||
"perşembe": 3,
|
||||
"cum": 4,
|
||||
"cuma": 4,
|
||||
"cts": 5,
|
||||
"cumartesi": 5,
|
||||
"paz": 6,
|
||||
"pazar": 6,
|
||||
}
|
||||
WEEKDAY_NAMES = ["Pzt", "Sal", "Cars", "Per", "Cum", "Cts", "Paz"]
|
||||
|
||||
|
||||
class AutomationService:
|
||||
def __init__(self, session: Session) -> None:
|
||||
self.session = session
|
||||
|
||||
def list_automations(self, telegram_user_id: int | None = None) -> list[AutomationRecord]:
|
||||
stmt = select(AutomationORM).order_by(AutomationORM.created_at.desc(), AutomationORM.id.desc())
|
||||
if telegram_user_id is not None:
|
||||
stmt = stmt.where(AutomationORM.telegram_user_id == telegram_user_id)
|
||||
return [self._to_record(item) for item in self.session.scalars(stmt)]
|
||||
|
||||
def start_wizard(self, telegram_user_id: int) -> str:
|
||||
record = self._get_or_create_wizard(telegram_user_id)
|
||||
record.step = 0
|
||||
record.draft_json = "{}"
|
||||
record.updated_at = datetime.utcnow()
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:wizard-start:{telegram_user_id}"))
|
||||
self.session.flush()
|
||||
return (
|
||||
"Yeni otomasyon olusturalim. Istersen herhangi bir adimda /iptal yazabilirsin.\n\n"
|
||||
"1/6 Otomasyon adi ne olsun?"
|
||||
)
|
||||
|
||||
def is_wizard_active(self, telegram_user_id: int) -> bool:
|
||||
wizard = self.session.get(AutomationWizardORM, telegram_user_id)
|
||||
return wizard is not None and wizard.step < 6
|
||||
|
||||
def cancel_wizard(self, telegram_user_id: int) -> str:
|
||||
wizard = self.session.get(AutomationWizardORM, telegram_user_id)
|
||||
if wizard is not None:
|
||||
self.session.delete(wizard)
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:wizard-cancel:{telegram_user_id}"))
|
||||
self.session.flush()
|
||||
return "Otomasyon olusturma akisini iptal ettim."
|
||||
|
||||
def answer_wizard(self, telegram_user_id: int, text: str) -> tuple[str, bool]:
|
||||
wizard = self._get_or_create_wizard(telegram_user_id)
|
||||
draft = self._load_draft(wizard)
|
||||
cleaned = text.strip()
|
||||
|
||||
if wizard.step == 0:
|
||||
draft["name"] = cleaned
|
||||
wizard.step = 1
|
||||
return self._persist_wizard(wizard, draft, "2/6 Bu otomasyon ne yapsin?")
|
||||
|
||||
if wizard.step == 1:
|
||||
draft["prompt"] = cleaned
|
||||
wizard.step = 2
|
||||
return self._persist_wizard(
|
||||
wizard,
|
||||
draft,
|
||||
"3/6 Hangi siklikla calissin? Su seceneklerden birini yaz: gunluk, haftaici, haftalik, saatlik",
|
||||
)
|
||||
|
||||
if wizard.step == 2:
|
||||
schedule_type = self._parse_schedule_type(cleaned)
|
||||
if schedule_type is None:
|
||||
return ("Gecerli bir secim gormedim. Lutfen gunluk, haftaici, haftalik veya saatlik yaz.", False)
|
||||
draft["schedule_type"] = schedule_type
|
||||
wizard.step = 3
|
||||
if schedule_type == "hourly":
|
||||
prompt = "4/6 Kac saatte bir calissin? Ornek: 1, 2, 4, 6"
|
||||
elif schedule_type == "weekly":
|
||||
prompt = "4/6 Hangi gunlerde calissin? Ornek: Pzt,Cars,Cum"
|
||||
else:
|
||||
prompt = "4/6 Saat kacta calissin? 24 saat formatinda yaz. Ornek: 09:00"
|
||||
return self._persist_wizard(wizard, draft, prompt)
|
||||
|
||||
if wizard.step == 3:
|
||||
schedule_type = str(draft.get("schedule_type", "daily"))
|
||||
if schedule_type == "hourly":
|
||||
interval_hours = self._parse_interval_hours(cleaned)
|
||||
if interval_hours is None:
|
||||
return ("Gecerli bir saat araligi gormedim. Lutfen 1 ile 24 arasinda bir sayi yaz.", False)
|
||||
draft["interval_hours"] = interval_hours
|
||||
wizard.step = 4
|
||||
return self._persist_wizard(wizard, draft, "5/6 Aktif olarak kaydedeyim mi? evet/hayir")
|
||||
if schedule_type == "weekly":
|
||||
weekdays = self._parse_weekdays(cleaned)
|
||||
if not weekdays:
|
||||
return ("Gunleri anlayamadim. Ornek olarak Pzt,Cars,Cum yazabilirsin.", False)
|
||||
draft["days_of_week"] = weekdays
|
||||
wizard.step = 4
|
||||
return self._persist_wizard(wizard, draft, "5/6 Saat kacta calissin? 24 saat formatinda yaz. Ornek: 09:00")
|
||||
|
||||
time_of_day = self._parse_time(cleaned)
|
||||
if time_of_day is None:
|
||||
return ("Saat formatini anlayamadim. Lutfen 24 saat formatinda HH:MM yaz.", False)
|
||||
draft["time_of_day"] = time_of_day
|
||||
wizard.step = 4
|
||||
return self._persist_wizard(wizard, draft, "5/6 Aktif olarak kaydedeyim mi? evet/hayir")
|
||||
|
||||
if wizard.step == 4:
|
||||
schedule_type = str(draft.get("schedule_type", "daily"))
|
||||
if schedule_type == "weekly" and "time_of_day" not in draft:
|
||||
time_of_day = self._parse_time(cleaned)
|
||||
if time_of_day is None:
|
||||
return ("Saat formatini anlayamadim. Lutfen 24 saat formatinda HH:MM yaz.", False)
|
||||
draft["time_of_day"] = time_of_day
|
||||
wizard.step = 5
|
||||
summary = self._render_wizard_summary(draft)
|
||||
return self._persist_wizard(wizard, draft, f"{summary}\n\n6/6 Aktif olarak kaydedeyim mi? evet/hayir")
|
||||
|
||||
active = self._parse_yes_no(cleaned)
|
||||
if active is None:
|
||||
return ("Lutfen evet veya hayir yaz.", False)
|
||||
draft["status"] = "active" if active else "paused"
|
||||
created = self._create_automation(telegram_user_id, draft)
|
||||
self.session.delete(wizard)
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:created:{created.id}"))
|
||||
self.session.flush()
|
||||
return (self._render_created_message(created), True)
|
||||
|
||||
if wizard.step == 5:
|
||||
active = self._parse_yes_no(cleaned)
|
||||
if active is None:
|
||||
return ("Lutfen evet veya hayir yaz.", False)
|
||||
draft["status"] = "active" if active else "paused"
|
||||
created = self._create_automation(telegram_user_id, draft)
|
||||
self.session.delete(wizard)
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:created:{created.id}"))
|
||||
self.session.flush()
|
||||
return (self._render_created_message(created), True)
|
||||
|
||||
return ("Otomasyon wizard durumu gecersiz.", False)
|
||||
|
||||
def render_automation_list(self, telegram_user_id: int) -> str:
|
||||
automations = self.list_automations(telegram_user_id)
|
||||
if not automations:
|
||||
return "Henuz otomasyonun yok. /otomasyon_ekle ile baslayabiliriz."
|
||||
lines = ["Otomasyonlarin:"]
|
||||
for item in automations:
|
||||
next_run = self._format_display_time(item.next_run_at)
|
||||
lines.append(f"- #{item.id} {item.name} [{item.status}] -> siradaki: {next_run}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def pause_automation(self, telegram_user_id: int, automation_id: int) -> str:
|
||||
item = self._get_owned_automation(telegram_user_id, automation_id)
|
||||
if item is None:
|
||||
return "Bu ID ile bir otomasyon bulamadim."
|
||||
item.status = "paused"
|
||||
item.updated_at = datetime.utcnow()
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:paused:{item.id}"))
|
||||
self.session.flush()
|
||||
return f"Otomasyon durduruldu: #{item.id} {item.name}"
|
||||
|
||||
def resume_automation(self, telegram_user_id: int, automation_id: int) -> str:
|
||||
item = self._get_owned_automation(telegram_user_id, automation_id)
|
||||
if item is None:
|
||||
return "Bu ID ile bir otomasyon bulamadim."
|
||||
item.status = "active"
|
||||
item.next_run_at = self._compute_next_run(item, from_time=datetime.utcnow())
|
||||
item.updated_at = datetime.utcnow()
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:resumed:{item.id}"))
|
||||
self.session.flush()
|
||||
return f"Otomasyon tekrar aktif edildi: #{item.id} {item.name}"
|
||||
|
||||
def delete_automation(self, telegram_user_id: int, automation_id: int) -> str:
|
||||
item = self._get_owned_automation(telegram_user_id, automation_id)
|
||||
if item is None:
|
||||
return "Bu ID ile bir otomasyon bulamadim."
|
||||
name = item.name
|
||||
self.session.delete(item)
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:deleted:{automation_id}"))
|
||||
self.session.flush()
|
||||
return f"Otomasyon silindi: #{automation_id} {name}"
|
||||
|
||||
def due_automations(self, now: datetime | None = None) -> list[AutomationORM]:
|
||||
current = now or datetime.utcnow()
|
||||
stmt = (
|
||||
select(AutomationORM)
|
||||
.where(AutomationORM.status == "active")
|
||||
.where(AutomationORM.next_run_at.is_not(None))
|
||||
.where(AutomationORM.next_run_at <= current)
|
||||
.order_by(AutomationORM.next_run_at.asc(), AutomationORM.id.asc())
|
||||
)
|
||||
return list(self.session.scalars(stmt))
|
||||
|
||||
def mark_run_result(self, item: AutomationORM, result: str, ran_at: datetime | None = None) -> None:
|
||||
run_time = ran_at or datetime.utcnow()
|
||||
item.last_run_at = run_time
|
||||
item.last_result = result[:2000]
|
||||
item.next_run_at = self._compute_next_run(item, from_time=run_time + timedelta(seconds=1))
|
||||
item.updated_at = datetime.utcnow()
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:ran:{item.id}"))
|
||||
self.session.flush()
|
||||
|
||||
def mark_run_error(self, item: AutomationORM, error: str) -> None:
|
||||
item.last_result = f"ERROR: {error[:1800]}"
|
||||
item.next_run_at = self._compute_next_run(item, from_time=datetime.utcnow() + timedelta(minutes=5))
|
||||
item.updated_at = datetime.utcnow()
|
||||
self.session.add(AuditLogORM(category="automation", message=f"automation:error:{item.id}:{error[:120]}"))
|
||||
self.session.flush()
|
||||
|
||||
def _persist_wizard(self, wizard: AutomationWizardORM, draft: dict[str, object], reply: str) -> tuple[str, bool]:
|
||||
wizard.draft_json = json.dumps(draft, ensure_ascii=False)
|
||||
wizard.updated_at = datetime.utcnow()
|
||||
self.session.flush()
|
||||
return reply, False
|
||||
|
||||
def _get_or_create_wizard(self, telegram_user_id: int) -> AutomationWizardORM:
|
||||
wizard = self.session.get(AutomationWizardORM, telegram_user_id)
|
||||
if wizard is None:
|
||||
wizard = AutomationWizardORM(
|
||||
telegram_user_id=telegram_user_id,
|
||||
step=0,
|
||||
draft_json="{}",
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow(),
|
||||
)
|
||||
self.session.add(wizard)
|
||||
self.session.flush()
|
||||
return wizard
|
||||
|
||||
def _load_draft(self, wizard: AutomationWizardORM) -> dict[str, object]:
|
||||
try:
|
||||
payload = json.loads(wizard.draft_json)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
def _parse_schedule_type(self, text: str) -> str | None:
|
||||
lowered = text.strip().lower()
|
||||
mapping = {
|
||||
"gunluk": "daily",
|
||||
"daily": "daily",
|
||||
"her gun": "daily",
|
||||
"haftaici": "weekdays",
|
||||
"hafta içi": "weekdays",
|
||||
"weekdays": "weekdays",
|
||||
"haftalik": "weekly",
|
||||
"haftalık": "weekly",
|
||||
"weekly": "weekly",
|
||||
"saatlik": "hourly",
|
||||
"hourly": "hourly",
|
||||
}
|
||||
return mapping.get(lowered)
|
||||
|
||||
def _parse_interval_hours(self, text: str) -> int | None:
|
||||
try:
|
||||
value = int(text.strip())
|
||||
except ValueError:
|
||||
return None
|
||||
if 1 <= value <= 24:
|
||||
return value
|
||||
return None
|
||||
|
||||
def _parse_time(self, text: str) -> str | None:
|
||||
cleaned = text.strip()
|
||||
if len(cleaned) != 5 or ":" not in cleaned:
|
||||
return None
|
||||
hour_text, minute_text = cleaned.split(":", 1)
|
||||
try:
|
||||
hour = int(hour_text)
|
||||
minute = int(minute_text)
|
||||
except ValueError:
|
||||
return None
|
||||
if not (0 <= hour <= 23 and 0 <= minute <= 59):
|
||||
return None
|
||||
return f"{hour:02d}:{minute:02d}"
|
||||
|
||||
def _parse_weekdays(self, text: str) -> list[str]:
|
||||
parts = [part.strip().lower() for part in text.replace("\n", ",").split(",")]
|
||||
seen: list[int] = []
|
||||
for part in parts:
|
||||
day = WEEKDAY_MAP.get(part)
|
||||
if day is not None and day not in seen:
|
||||
seen.append(day)
|
||||
return [WEEKDAY_NAMES[day] for day in sorted(seen)]
|
||||
|
||||
def _parse_yes_no(self, text: str) -> bool | None:
|
||||
lowered = text.strip().lower()
|
||||
if lowered in {"evet", "e", "yes", "y"}:
|
||||
return True
|
||||
if lowered in {"hayir", "hayır", "h", "no", "n"}:
|
||||
return False
|
||||
return None
|
||||
|
||||
def _render_wizard_summary(self, draft: dict[str, object]) -> str:
|
||||
schedule_type = str(draft.get("schedule_type", "daily"))
|
||||
label = {
|
||||
"daily": "gunluk",
|
||||
"weekdays": "haftaici",
|
||||
"weekly": "haftalik",
|
||||
"hourly": "saatlik",
|
||||
}.get(schedule_type, schedule_type)
|
||||
lines = [
|
||||
"Ozet:",
|
||||
f"- Ad: {draft.get('name', '-')}",
|
||||
f"- Gorev: {draft.get('prompt', '-')}",
|
||||
f"- Siklik: {label}",
|
||||
]
|
||||
if schedule_type == "hourly":
|
||||
lines.append(f"- Aralik: {draft.get('interval_hours', '-')} saat")
|
||||
else:
|
||||
lines.append(f"- Saat: {draft.get('time_of_day', '-')}")
|
||||
if schedule_type == "weekly":
|
||||
days = draft.get("days_of_week", [])
|
||||
if isinstance(days, list):
|
||||
lines.append(f"- Gunler: {', '.join(str(item) for item in days)}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _render_created_message(self, item: AutomationORM) -> str:
|
||||
next_run = self._format_display_time(item.next_run_at)
|
||||
return (
|
||||
f"Otomasyon kaydedildi: #{item.id} {item.name}\n"
|
||||
f"- Durum: {item.status}\n"
|
||||
f"- Siradaki calisma: {next_run}"
|
||||
)
|
||||
|
||||
def _create_automation(self, telegram_user_id: int, draft: dict[str, object]) -> AutomationORM:
|
||||
schedule_type = str(draft["schedule_type"])
|
||||
item = AutomationORM(
|
||||
telegram_user_id=telegram_user_id,
|
||||
name=str(draft["name"]),
|
||||
prompt=str(draft["prompt"]),
|
||||
schedule_type=schedule_type,
|
||||
interval_hours=int(draft["interval_hours"]) if draft.get("interval_hours") is not None else None,
|
||||
time_of_day=str(draft["time_of_day"]) if draft.get("time_of_day") is not None else None,
|
||||
days_of_week=json.dumps(draft.get("days_of_week", []), ensure_ascii=False),
|
||||
status=str(draft.get("status", "active")),
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow(),
|
||||
)
|
||||
if item.status == "active":
|
||||
item.next_run_at = self._compute_next_run(item, from_time=datetime.utcnow())
|
||||
self.session.add(item)
|
||||
self.session.flush()
|
||||
return item
|
||||
|
||||
def _compute_next_run(self, item: AutomationORM, from_time: datetime) -> datetime:
|
||||
if item.schedule_type == "hourly":
|
||||
interval = max(item.interval_hours or 1, 1)
|
||||
return from_time + timedelta(hours=interval)
|
||||
|
||||
local_now = from_time.replace(tzinfo=UTC).astimezone(LOCAL_TZ)
|
||||
hour, minute = self._parse_hour_minute(item.time_of_day or "09:00")
|
||||
|
||||
if item.schedule_type == "daily":
|
||||
return self._to_utc_naive(self._next_local_time(local_now, hour, minute))
|
||||
|
||||
if item.schedule_type == "weekdays":
|
||||
candidate = self._next_local_time(local_now, hour, minute)
|
||||
while candidate.weekday() >= 5:
|
||||
candidate = candidate + timedelta(days=1)
|
||||
candidate = candidate.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||
return self._to_utc_naive(candidate)
|
||||
|
||||
days = self._decode_days(item.days_of_week)
|
||||
if not days:
|
||||
days = [0]
|
||||
candidate = self._next_local_time(local_now, hour, minute)
|
||||
for _ in range(8):
|
||||
if candidate.weekday() in days:
|
||||
return self._to_utc_naive(candidate)
|
||||
candidate = candidate + timedelta(days=1)
|
||||
candidate = candidate.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||
|
||||
return self._to_utc_naive(candidate)
|
||||
|
||||
def _next_local_time(self, local_now: datetime, hour: int, minute: int) -> datetime:
|
||||
candidate = local_now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
||||
if candidate <= local_now:
|
||||
candidate = candidate + timedelta(days=1)
|
||||
return candidate
|
||||
|
||||
def _parse_hour_minute(self, value: str) -> tuple[int, int]:
|
||||
hour_text, minute_text = value.split(":", 1)
|
||||
return int(hour_text), int(minute_text)
|
||||
|
||||
def _decode_days(self, value: str) -> list[int]:
|
||||
try:
|
||||
payload = json.loads(value)
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
result: list[int] = []
|
||||
if not isinstance(payload, list):
|
||||
return result
|
||||
for item in payload:
|
||||
label = str(item)
|
||||
if label in WEEKDAY_NAMES:
|
||||
result.append(WEEKDAY_NAMES.index(label))
|
||||
return result
|
||||
|
||||
def _to_utc_naive(self, local_dt: datetime) -> datetime:
|
||||
return local_dt.astimezone(UTC).replace(tzinfo=None)
|
||||
|
||||
def _format_display_time(self, value: datetime | None) -> str:
|
||||
if value is None:
|
||||
return "hesaplanmadi"
|
||||
return value.replace(tzinfo=UTC).astimezone(LOCAL_TZ).strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
def _to_record(self, item: AutomationORM) -> AutomationRecord:
|
||||
days = []
|
||||
try:
|
||||
payload = json.loads(item.days_of_week)
|
||||
if isinstance(payload, list):
|
||||
days = [str(day) for day in payload]
|
||||
except json.JSONDecodeError:
|
||||
days = []
|
||||
return AutomationRecord(
|
||||
id=item.id,
|
||||
telegram_user_id=item.telegram_user_id,
|
||||
name=item.name,
|
||||
prompt=item.prompt,
|
||||
schedule_type=item.schedule_type, # type: ignore[arg-type]
|
||||
interval_hours=item.interval_hours,
|
||||
time_of_day=item.time_of_day,
|
||||
days_of_week=days,
|
||||
status=item.status, # type: ignore[arg-type]
|
||||
last_run_at=item.last_run_at,
|
||||
next_run_at=item.next_run_at,
|
||||
last_result=item.last_result,
|
||||
created_at=item.created_at,
|
||||
updated_at=item.updated_at,
|
||||
)
|
||||
|
||||
def _get_owned_automation(self, telegram_user_id: int, automation_id: int) -> AutomationORM | None:
|
||||
item = self.session.get(AutomationORM, automation_id)
|
||||
if item is None or item.telegram_user_id != telegram_user_id:
|
||||
return None
|
||||
return item
|
||||
Reference in New Issue
Block a user