589 lines
21 KiB
JavaScript
589 lines
21 KiB
JavaScript
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
|
||
};
|
||
}
|