diff --git a/src/main/index.js b/src/main/index.js index 0005bc5..365fa89 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -58,12 +58,23 @@ const startTaskWithChecks = async (id) => { const filteredResult = filterTaskRolesByAccounts(id, taskAccounts, existingAccounts); const filteredRoles = filteredResult.filtered; let adminPrepPartialWarning = ""; + const accountsById = new Map(existingAccounts.map((acc) => [acc.id, acc])); + const isAccountAvailable = (account) => { + if (!account || account.status !== "ok") return false; + if (!account.cooldown_until) return true; + try { + return new Date(account.cooldown_until).getTime() <= Date.now(); + } catch { + return true; + } + }; const inviteIds = filteredRoles .filter((row) => row.roleInvite && Number(row.inviteLimit || 0) > 0) - .map((row) => row.accountId); + .map((row) => row.accountId) + .filter((id) => isAccountAvailable(accountsById.get(id))); const monitorIds = filteredRoles.filter((row) => row.roleMonitor).map((row) => row.accountId); if (!inviteIds.length) { - return { ok: false, error: "Нет аккаунтов с ролью инвайта." }; + return { ok: false, error: "Нет доступных аккаунтов с ролью инвайта (все в ограничении/спаме)." }; } if (!monitorIds.length) { return { ok: false, error: "Нет аккаунтов с ролью мониторинга." }; diff --git a/src/main/store.js b/src/main/store.js index 44b0089..e13ccd1 100644 --- a/src/main/store.js +++ b/src/main/store.js @@ -1048,8 +1048,13 @@ function initStore(userDataPath) { LIMIT 1 `).get(taskId || 0, String(userId || "")); if (!row || !row.id) return; - db.prepare("UPDATE invites SET confirmed = ?, confirm_error = ? WHERE id = ?") - .run(confirmed ? 1 : 0, confirmError || "", row.id); + if (confirmed) { + db.prepare("UPDATE invites SET confirmed = 1, confirm_error = '', status = 'success' WHERE id = ?") + .run(row.id); + return; + } + db.prepare("UPDATE invites SET confirmed = 0, confirm_error = ? WHERE id = ?") + .run(confirmError || "", row.id); } function listFallback(limit, taskId) { diff --git a/src/main/telegram.js b/src/main/telegram.js index 2654706..b87762d 100644 --- a/src/main/telegram.js +++ b/src/main/telegram.js @@ -2161,6 +2161,19 @@ class TelegramManager { this.store.addAccountEvent(masterAccountId, masterAccount.phone || "", "admin_grant_detail", `${diagParts.join(" | ")} | error=session_not_connected`); continue; } + if (targetEntry.account && (targetEntry.account.status !== "ok" || this._isInCooldown(targetEntry.account))) { + const reason = targetEntry.account.status !== "ok" + ? "Аккаунт в спаме/ограничении" + : "Аккаунт в FLOOD‑кулдауне"; + results.push({ accountId, ok: false, reason }); + this.store.addAccountEvent( + masterAccountId, + masterAccount.phone || "", + "admin_grant_detail", + `${diagParts.join(" | ")} | skip=${reason}` + ); + continue; + } const targetAccess = await this._resolveGroupEntity( targetEntry.client, task.our_group, @@ -2539,7 +2552,9 @@ class TelegramManager { async joinGroupsForTask(task, competitorGroups, accountIds, roleIds = {}, options = {}) { const forceJoin = Boolean(options.forceJoin); - const accounts = Array.from(this.clients.values()).filter((entry) => accountIds.includes(entry.account.id)); + const accounts = Array.from(this.clients.values()) + .filter((entry) => accountIds.includes(entry.account.id)) + .filter((entry) => entry.account.status === "ok" && !this._isInCooldown(entry.account)); const competitorBots = Math.max(1, Number(task.max_competitor_bots || 1)); const ourBots = Math.max(1, Number(task.max_our_bots || 1)); diff --git a/src/renderer/hooks/useAccountManagement.js b/src/renderer/hooks/useAccountManagement.js index 07caa69..4d6be51 100644 --- a/src/renderer/hooks/useAccountManagement.js +++ b/src/renderer/hooks/useAccountManagement.js @@ -1,3 +1,5 @@ +import { useEffect, useRef } from "react"; + export default function useAccountManagement({ selectedTaskId, taskAccountRoles, @@ -16,6 +18,17 @@ export default function useAccountManagement({ refreshMembership }) { const DEFAULT_INVITE_LIMIT = 7; + const lastAutoRedistributeRef = useRef(0); + + const isAccountAvailable = (account) => { + if (!account || account.status !== "ok") return false; + if (!account.cooldown_until) return true; + try { + return new Date(account.cooldown_until).getTime() <= Date.now(); + } catch (error) { + return true; + } + }; const persistAccountRoles = async (next) => { if (!window.api || selectedTaskId == null) return; @@ -97,9 +110,11 @@ export default function useAccountManagement({ const applyRolePreset = async (type) => { if (!hasSelectedTask) return; - const availableIds = selectedAccountIds.length + const accountMap = new Map((accounts || []).map((account) => [account.id, account])); + const baseIds = selectedAccountIds.length ? selectedAccountIds : accountBuckets.freeOrSelected.map((account) => account.id); + const availableIds = baseIds.filter((id) => isAccountAvailable(accountMap.get(id))); if (!availableIds.length) { showNotification("Нет доступных аккаунтов для назначения.", "error"); return; @@ -274,6 +289,42 @@ export default function useAccountManagement({ } }; + useEffect(() => { + if (!hasSelectedTask || taskForm.rolesMode !== "auto") return; + const accountMap = new Map((accounts || []).map((account) => [account.id, account])); + const badAssigned = Object.keys(taskAccountRoles).filter((id) => { + const account = accountMap.get(Number(id)); + return account && !isAccountAvailable(account); + }); + if (!badAssigned.length) return; + const now = Date.now(); + if (now - lastAutoRedistributeRef.current < 5000) return; + lastAutoRedistributeRef.current = now; + showNotification("Обнаружены аккаунты в ограничении. Выполняем перераспределение ролей.", "warn"); + if (window.api) { + const labels = badAssigned + .map((id) => { + const account = accountMap.get(Number(id)); + if (!account) return String(id); + return account.phone || account.username || account.id; + }) + .join(", "); + window.api.addAccountEvent({ + accountId: 0, + phone: "", + action: "roles_autoredistribute", + details: `задача ${selectedTaskId}: авто‑перераспределение из‑за статуса/лимита (исключены: ${labels || "—"})` + }); + } + applyRolePreset(taskForm.requireSameBotInBoth ? "one" : "split"); + }, [ + accounts, + taskAccountRoles, + hasSelectedTask, + taskForm.rolesMode, + taskForm.requireSameBotInBoth + ]); + const moveAccountToTask = async (accountId) => { if (!window.api || selectedTaskId == null) return; await assignAccountsToTask([accountId]); diff --git a/src/renderer/hooks/useTaskActions.js b/src/renderer/hooks/useTaskActions.js index 9ab2e55..5bc1406 100644 --- a/src/renderer/hooks/useTaskActions.js +++ b/src/renderer/hooks/useTaskActions.js @@ -82,11 +82,26 @@ export default function useTaskActions({ let accountRolesMap = { ...taskAccountRoles }; let accountIds = Object.keys(accountRolesMap).map((id) => Number(id)); const autoRoleMode = nextForm.rolesMode === "auto"; + const eligibleAccounts = (accounts || []).filter((account) => { + if (!account || account.status !== "ok") return false; + if (!account.cooldown_until) return true; + try { + return new Date(account.cooldown_until).getTime() <= Date.now(); + } catch { + return true; + } + }); + const eligibleIds = eligibleAccounts.map((account) => account.id); if (autoRoleMode && nextForm.requireSameBotInBoth) { const required = Math.max(1, Number(nextForm.maxCompetitorBots || 1)); - const pool = (selectedAccountIds && selectedAccountIds.length ? selectedAccountIds : accounts.map((account) => account.id)) + const pool = (selectedAccountIds && selectedAccountIds.length ? selectedAccountIds : eligibleIds) .filter((id) => Number.isFinite(id)); - const chosen = pool.slice(0, required); + const filteredPool = pool.filter((id) => eligibleIds.includes(id)); + if (!filteredPool.length) { + showNotification("Нет доступных аккаунтов (все в ограничении).", "error"); + return; + } + const chosen = filteredPool.slice(0, required); accountRolesMap = {}; chosen.forEach((accountId) => { const existing = taskAccountRoles[accountId] || {}; @@ -97,7 +112,11 @@ export default function useTaskActions({ setSelectedAccountIds(chosen); } if (autoRoleMode && nextForm.autoAssignAccounts && (!accountIds || accountIds.length === 0)) { - accountIds = accounts.map((account) => account.id); + accountIds = eligibleIds; + if (!accountIds.length) { + showNotification("Нет доступных аккаунтов (все в ограничении).", "error"); + return; + } accountRolesMap = {}; accountIds.forEach((accountId) => { const existing = taskAccountRoles[accountId] || {}; diff --git a/src/renderer/hooks/useTaskPresets.js b/src/renderer/hooks/useTaskPresets.js index fdf5b41..59d2d44 100644 --- a/src/renderer/hooks/useTaskPresets.js +++ b/src/renderer/hooks/useTaskPresets.js @@ -26,11 +26,24 @@ export default function useTaskPresets({ showNotification("Нет доступных аккаунтов.", "error"); return; } - const masterId = accounts[0].id; + const eligibleAccounts = accounts.filter((account) => { + if (!account || account.status !== "ok") return false; + if (!account.cooldown_until) return true; + try { + return new Date(account.cooldown_until).getTime() <= Date.now(); + } catch { + return true; + } + }); + if (!eligibleAccounts.length) { + showNotification("Нет доступных аккаунтов (все в ограничении).", "error"); + return; + } + const masterId = eligibleAccounts[0].id; const requiredCount = 3; const baseIds = selectedAccountIds.length >= requiredCount ? selectedAccountIds.slice() - : accounts.map((account) => account.id); + : eligibleAccounts.map((account) => account.id); if (baseIds.length < 3) { showNotification("Для раздельных ролей желательно минимум 3 аккаунта (мониторинг/инвайт/подтверждение).", "info"); } diff --git a/src/renderer/tabs/AccountsTab.jsx b/src/renderer/tabs/AccountsTab.jsx index 862231f..f628738 100644 --- a/src/renderer/tabs/AccountsTab.jsx +++ b/src/renderer/tabs/AccountsTab.jsx @@ -202,6 +202,9 @@ function AccountsTab({
{formatAccountLabel(account)}
{formatAccountStatus(account.status)}
+ {account.status && account.status !== "ok" && ( + в спаме + )}
{selected ? "В задаче" : "Свободен"}
@@ -408,6 +411,9 @@ function AccountsTab({
{formatAccountLabel(account)}
{formatAccountStatus(account.status)}
+ {account.status && account.status !== "ok" && ( + в спаме + )}
Занят