import { useRef } from "react"; export default function useTaskActions({ taskForm, setTaskForm, sanitizeTaskForm, taskAccountRoles, setTaskAccountRoles, selectedAccountIds, setSelectedAccountIds, accounts, selectedTaskId, selectedTaskName, competitorGroups, hasSelectedTask, setTaskNotice, showNotification, setAutosaveNote, autosaveNoteTimer, loadTasks, loadAccountAssignments, loadTaskStatuses, refreshMembership, checkInviteAccess, checkAccess, setLogs, setInvites, setTaskStatus, setSelectedTaskId, resetTaskForm, setCompetitorText, resetSelectedAccountIds, resetTaskAccountRoles, setFallbackList, setConfirmQueue, setAccountEvents, setTaskActionLoading, taskActionLoading, loadBase, createTask, setActiveTab }) { const withTimeout = (promise, ms) => ( Promise.race([ promise, new Promise((_, reject) => setTimeout(() => reject(new Error("TIMEOUT")), ms)) ]) ); const saveTask = async (source = "editor", options = {}) => { const silent = Boolean(options.silent); if (!window.api) { if (!silent) { showNotification("Electron API недоступен. Откройте приложение в Electron.", "error"); } return; } try { if (!silent) { showNotification("Сохраняем задачу...", "info"); } const nextForm = sanitizeTaskForm(taskForm); setTaskForm(nextForm); const validateLink = (value) => { const trimmed = String(value || "").trim(); if (!trimmed) return false; if (trimmed.startsWith("@")) return true; if (trimmed.startsWith("https://t.me/")) return true; if (trimmed.startsWith("http://t.me/")) return true; return false; }; const invalidCompetitors = competitorGroups.filter((link) => !validateLink(link)); if (!validateLink(nextForm.ourGroup)) { showNotification("Наша группа должна быть ссылкой t.me или @username.", "error"); return; } if (invalidCompetitors.length) { showNotification(`Некорректные ссылки конкурентов: ${invalidCompetitors.join(", ")}`, "error"); return; } let accountRolesMap = { ...taskAccountRoles }; let accountIds = Object.keys(accountRolesMap).map((id) => Number(id)); if (nextForm.requireSameBotInBoth) { const required = Math.max(1, Number(nextForm.maxCompetitorBots || 1)); const pool = (selectedAccountIds && selectedAccountIds.length ? selectedAccountIds : accounts.map((account) => account.id)) .filter((id) => Number.isFinite(id)); const chosen = pool.slice(0, required); accountRolesMap = {}; chosen.forEach((accountId) => { const existing = accountRolesMap[accountId] || {}; accountRolesMap[accountId] = { monitor: true, invite: true, confirm: true, inviteLimit: existing.inviteLimit || 1 }; }); accountIds = chosen; setTaskAccountRoles(accountRolesMap); setSelectedAccountIds(chosen); } if (nextForm.autoAssignAccounts && (!accountIds || accountIds.length === 0)) { accountIds = accounts.map((account) => account.id); accountRolesMap = {}; accountIds.forEach((accountId) => { const existing = accountRolesMap[accountId] || {}; accountRolesMap[accountId] = { monitor: true, invite: true, confirm: true, inviteLimit: existing.inviteLimit || 1 }; }); setTaskAccountRoles(accountRolesMap); setSelectedAccountIds(accountIds); if (accountIds.length && !silent) { setTaskNotice({ text: `Автоназначены аккаунты: ${accountIds.length}`, tone: "success", source }); } } if (!accountIds.length) { if (!silent) { showNotification("Нет аккаунтов для этой задачи.", "error"); } return; } const roleEntries = Object.values(accountRolesMap); if (roleEntries.length) { const hasMonitor = roleEntries.some((item) => item.monitor); const hasInvite = roleEntries.some((item) => item.invite); const hasConfirm = roleEntries.some((item) => item.confirm); if (!hasMonitor) { if (!silent) { showNotification("Нужен хотя бы один аккаунт с ролью мониторинга.", "error"); } return; } if (!hasInvite) { if (!silent) { showNotification("Нужен хотя бы один аккаунт с ролью инвайта.", "error"); } return; } if (nextForm.separateConfirmRoles && !hasConfirm) { if (!silent) { showNotification("Нужен хотя бы один аккаунт с ролью подтверждения.", "error"); } return; } } else { const requiredAccounts = nextForm.requireSameBotInBoth ? Math.max(1, Number(nextForm.maxCompetitorBots || 1)) : nextForm.separateBotRoles ? Math.max(1, Number(nextForm.maxCompetitorBots || 1)) + Math.max(1, Number(nextForm.maxOurBots || 1)) + (nextForm.separateConfirmRoles ? Math.max(1, Number(nextForm.maxConfirmBots || 1)) : 0) : 1; if (accountIds.length < requiredAccounts) { if (!silent) { showNotification(`Нужно минимум ${requiredAccounts} аккаунтов для выбранного режима.`, "error"); } return; } } const accountRoles = Object.entries(accountRolesMap).map(([id, roles]) => ({ accountId: Number(id), roleMonitor: Boolean(roles.monitor), roleInvite: Boolean(roles.invite), roleConfirm: Boolean(roles.confirm != null ? roles.confirm : roles.invite), inviteLimit: Number(roles.inviteLimit || 0) })); const result = await window.api.saveTask({ task: nextForm, competitors: competitorGroups, accountIds, accountRoles }); if (result.ok) { if (!silent) { setTaskNotice({ text: "Задача сохранена.", tone: "success", source }); } else { setAutosaveNote("Автосохранено"); if (autosaveNoteTimer.current) { clearTimeout(autosaveNoteTimer.current); } autosaveNoteTimer.current = setTimeout(() => { setAutosaveNote(""); }, 1500); } await loadTasks(); await loadAccountAssignments(); setSelectedTaskId(result.taskId); } else { if (!silent) { showNotification(result.error || "Не удалось сохранить задачу", "error"); } } } catch (error) { if (!silent) { showNotification(error.message || String(error), "error"); } } }; const deleteTask = async () => { if (!window.api || selectedTaskId == null) { return; } try { await window.api.deleteTask(selectedTaskId); setTaskNotice({ text: "Задача удалена.", tone: "success", source: "tasks" }); const tasksData = await loadTasks(); await loadAccountAssignments(); if (!tasksData.length) { createTask(); setActiveTab("task"); } } catch (error) { showNotification(error.message || String(error), "error"); } }; const startTask = async (source = "sidebar") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } if (taskActionLoading) return; setTaskActionLoading(true); showNotification("Запуск...", "info"); try { const result = await withTimeout(window.api.startTaskById(selectedTaskId), 15000); if (result && result.ok) { setTaskNotice({ text: "Запущено.", tone: "success", source }); if (result.warnings && result.warnings.length) { showNotification(`Предупреждения: ${result.warnings.join(" | ")}`, "info"); } await refreshMembership("start_task"); checkInviteAccess("auto", true); } else { showNotification(result.error || "Не удалось запустить", "error"); } } catch (error) { const message = error.message === "TIMEOUT" ? "Запуск не ответил за 15 секунд. Проверьте логи/события и попробуйте снова." : (error.message || String(error)); setTaskNotice({ text: message, tone: "error", source }); showNotification(message, "error"); } finally { setTaskActionLoading(false); } }; const startAllTasks = async () => { if (!window.api) { showNotification("Electron API недоступен. Откройте приложение в Electron.", "error"); return; } showNotification("Запускаем все задачи...", "info"); try { const result = await window.api.startAllTasks(); if (result && result.errors && result.errors.length) { const errorText = result.errors.map((item) => `${item.id}: ${item.error}`).join(" | "); showNotification(`Ошибки запуска: ${errorText}`, "error"); } const tasksData = await loadTasks(); await loadTaskStatuses(tasksData); } catch (error) { showNotification(error.message || String(error), "error"); } }; const stopTask = async (source = "sidebar") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } if (taskActionLoading) return; if (!window.confirm(`Остановить задачу: ${selectedTaskName}?`)) { return; } setTaskActionLoading(true); showNotification("Остановка...", "info"); try { await withTimeout(window.api.stopTaskById(selectedTaskId), 15000); setTaskNotice({ text: "Остановлено.", tone: "success", source }); } catch (error) { const message = error.message === "TIMEOUT" ? "Остановка не ответила за 15 секунд. Проверьте логи/события и попробуйте снова." : (error.message || String(error)); setTaskNotice({ text: message, tone: "error", source }); showNotification(message, "error"); } finally { setTaskActionLoading(false); } }; const stopAllTasks = async () => { if (!window.api) { showNotification("Electron API недоступен. Откройте приложение в Electron.", "error"); return; } if (!window.confirm("Остановить все задачи?")) { return; } showNotification("Останавливаем все задачи...", "info"); try { await window.api.stopAllTasks(); const tasksData = await loadTasks(); await loadTaskStatuses(tasksData); } catch (error) { showNotification(error.message || String(error), "error"); } }; const parseHistory = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } showNotification("Собираем историю...", "info"); try { const result = await window.api.parseHistoryByTask(selectedTaskId); if (result && result.ok) { setTaskNotice({ text: "История добавлена в очередь.", tone: "success", source }); if (result.errors && result.errors.length) { showNotification(`Ошибки истории: ${result.errors.join(" | ")}`, "error"); } setLogs(await window.api.listLogs({ limit: 100, taskId: selectedTaskId })); setInvites(await window.api.listInvites({ limit: 200, taskId: selectedTaskId })); return; } const message = result.error || "Ошибка при сборе истории"; setTaskNotice({ text: message, tone: "error", source }); showNotification(message, "error"); } catch (error) { const message = error.message || String(error); setTaskNotice({ text: message, tone: "error", source }); showNotification(message, "error"); } }; const checkAll = async (source = "bar") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } showNotification("Проверяем всё: доступ, права, участие...", "info"); await checkAccess(source, true); await checkInviteAccess(source, true); await refreshMembership(source, true); setTaskNotice({ text: "Проверка завершена.", tone: "success", source }); }; const joinGroupsForTask = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { showNotification("Отправляем заявки на вступление...", "info"); const result = await window.api.joinGroupsByTask(selectedTaskId); if (!result || !result.ok) { showNotification(result?.error || "Не удалось отправить заявки", "error"); return; } setTaskNotice({ text: "Заявки на вступление отправлены.", tone: "success", source }); await refreshMembership("join_groups"); } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearLogs = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { await window.api.clearLogs(selectedTaskId); setLogs([]); setTaskNotice({ text: "Логи очищены.", tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearInvites = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { await window.api.clearInvites(selectedTaskId); setInvites([]); setTaskNotice({ text: "История инвайтов очищена.", tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearAccountEvents = async () => { if (!window.api) { showNotification("Electron API недоступен. Откройте приложение в Electron.", "error"); return; } try { await window.api.clearAccountEvents(); setAccountEvents([]); showNotification("События очищены.", "success"); } catch (error) { showNotification(error.message || String(error), "error"); } }; const exportLogs = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { const result = await window.api.exportLogs(selectedTaskId); if (result && result.canceled) return; setTaskNotice({ text: `Логи выгружены: ${result.filePath}`, tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const exportInvites = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { const result = await window.api.exportInvites(selectedTaskId); if (result && result.canceled) return; setTaskNotice({ text: `История инвайтов выгружена: ${result.filePath}`, tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const exportProblemInvites = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { const result = await window.api.exportProblemInvites(selectedTaskId); if (result.canceled) return; setTaskNotice({ text: `Проблемные инвайты выгружены: ${result.filePath}`, tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const exportFallback = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { const result = await window.api.exportFallback(selectedTaskId); if (result.canceled) return; setTaskNotice({ text: `Fallback выгружен: ${result.filePath}`, tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const updateFallbackStatus = async (id, status) => { if (!window.api) return; try { await window.api.updateFallback({ id, status }); if (selectedTaskId != null) { setFallbackList(await window.api.listFallback({ limit: 500, taskId: selectedTaskId })); } } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearFallback = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { await window.api.clearFallback(selectedTaskId); setFallbackList(await window.api.listFallback({ limit: 500, taskId: selectedTaskId })); setTaskNotice({ text: "Fallback очищен.", tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearConfirmQueue = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { await window.api.clearConfirmQueue(selectedTaskId); setConfirmQueue(await window.api.listConfirmQueue({ limit: 500, taskId: selectedTaskId })); setTaskNotice({ text: "Очередь подтверждений очищена.", tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearQueue = async (source = "editor") => { if (!window.api || selectedTaskId == null) { showNotification("Сначала выберите задачу.", "error"); return; } try { await window.api.clearQueue(selectedTaskId); const data = await window.api.taskStatus(selectedTaskId); setTaskStatus(data); setTaskNotice({ text: "Очередь очищена.", tone: "success", source }); } catch (error) { showNotification(error.message || String(error), "error"); } }; const clearDatabase = async () => { if (!window.api) { showNotification("Electron API недоступен. Откройте приложение в Electron.", "error"); return; } if (!window.confirm("Удалить все данные из базы? Это действие нельзя отменить.")) { return; } try { await window.api.clearDatabase(); showNotification("База очищена.", "info"); setSelectedTaskId(null); resetTaskForm(); setCompetitorText(""); resetSelectedAccountIds([]); resetTaskAccountRoles({}); setLogs([]); setInvites([]); setTaskStatus({ running: false, queueCount: 0, dailyRemaining: 0, dailyUsed: 0, dailyLimit: 0, monitorInfo: { monitoring: false, accountId: 0, accountIds: [], groups: [], lastMessageAt: "", lastSource: "" } }); await loadBase(); } catch (error) { showNotification(error.message || String(error), "error"); } }; const resetSessions = async () => { if (!window.api) { showNotification("Electron API недоступен. Откройте приложение в Electron.", "error"); return; } try { await window.api.resetSessions(); showNotification("Сессии сброшены.", "info"); resetSelectedAccountIds([]); resetTaskAccountRoles({}); await loadBase(); } catch (error) { showNotification(error.message || String(error), "error"); } }; return { saveTask, deleteTask, startTask, stopTask, startAllTasks, stopAllTasks, parseHistory, checkAll, joinGroupsForTask, clearLogs, clearInvites, clearAccountEvents, exportLogs, exportInvites, exportProblemInvites, exportFallback, updateFallbackStatus, clearFallback, clearConfirmQueue, clearQueue, clearDatabase, resetSessions }; }