telegram-invite-automation/src/renderer/hooks/useTaskActions.js
2026-02-01 14:39:30 +04:00

589 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
};
}