some
This commit is contained in:
parent
a3aac40a73
commit
3e812b2835
@ -58,12 +58,23 @@ const startTaskWithChecks = async (id) => {
|
|||||||
const filteredResult = filterTaskRolesByAccounts(id, taskAccounts, existingAccounts);
|
const filteredResult = filterTaskRolesByAccounts(id, taskAccounts, existingAccounts);
|
||||||
const filteredRoles = filteredResult.filtered;
|
const filteredRoles = filteredResult.filtered;
|
||||||
let adminPrepPartialWarning = "";
|
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
|
const inviteIds = filteredRoles
|
||||||
.filter((row) => row.roleInvite && Number(row.inviteLimit || 0) > 0)
|
.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);
|
const monitorIds = filteredRoles.filter((row) => row.roleMonitor).map((row) => row.accountId);
|
||||||
if (!inviteIds.length) {
|
if (!inviteIds.length) {
|
||||||
return { ok: false, error: "Нет аккаунтов с ролью инвайта." };
|
return { ok: false, error: "Нет доступных аккаунтов с ролью инвайта (все в ограничении/спаме)." };
|
||||||
}
|
}
|
||||||
if (!monitorIds.length) {
|
if (!monitorIds.length) {
|
||||||
return { ok: false, error: "Нет аккаунтов с ролью мониторинга." };
|
return { ok: false, error: "Нет аккаунтов с ролью мониторинга." };
|
||||||
|
|||||||
@ -1048,8 +1048,13 @@ function initStore(userDataPath) {
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
`).get(taskId || 0, String(userId || ""));
|
`).get(taskId || 0, String(userId || ""));
|
||||||
if (!row || !row.id) return;
|
if (!row || !row.id) return;
|
||||||
db.prepare("UPDATE invites SET confirmed = ?, confirm_error = ? WHERE id = ?")
|
if (confirmed) {
|
||||||
.run(confirmed ? 1 : 0, confirmError || "", row.id);
|
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) {
|
function listFallback(limit, taskId) {
|
||||||
|
|||||||
@ -2161,6 +2161,19 @@ class TelegramManager {
|
|||||||
this.store.addAccountEvent(masterAccountId, masterAccount.phone || "", "admin_grant_detail", `${diagParts.join(" | ")} | error=session_not_connected`);
|
this.store.addAccountEvent(masterAccountId, masterAccount.phone || "", "admin_grant_detail", `${diagParts.join(" | ")} | error=session_not_connected`);
|
||||||
continue;
|
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(
|
const targetAccess = await this._resolveGroupEntity(
|
||||||
targetEntry.client,
|
targetEntry.client,
|
||||||
task.our_group,
|
task.our_group,
|
||||||
@ -2539,7 +2552,9 @@ class TelegramManager {
|
|||||||
|
|
||||||
async joinGroupsForTask(task, competitorGroups, accountIds, roleIds = {}, options = {}) {
|
async joinGroupsForTask(task, competitorGroups, accountIds, roleIds = {}, options = {}) {
|
||||||
const forceJoin = Boolean(options.forceJoin);
|
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 competitorBots = Math.max(1, Number(task.max_competitor_bots || 1));
|
||||||
const ourBots = Math.max(1, Number(task.max_our_bots || 1));
|
const ourBots = Math.max(1, Number(task.max_our_bots || 1));
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
export default function useAccountManagement({
|
export default function useAccountManagement({
|
||||||
selectedTaskId,
|
selectedTaskId,
|
||||||
taskAccountRoles,
|
taskAccountRoles,
|
||||||
@ -16,6 +18,17 @@ export default function useAccountManagement({
|
|||||||
refreshMembership
|
refreshMembership
|
||||||
}) {
|
}) {
|
||||||
const DEFAULT_INVITE_LIMIT = 7;
|
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) => {
|
const persistAccountRoles = async (next) => {
|
||||||
if (!window.api || selectedTaskId == null) return;
|
if (!window.api || selectedTaskId == null) return;
|
||||||
@ -97,9 +110,11 @@ export default function useAccountManagement({
|
|||||||
|
|
||||||
const applyRolePreset = async (type) => {
|
const applyRolePreset = async (type) => {
|
||||||
if (!hasSelectedTask) return;
|
if (!hasSelectedTask) return;
|
||||||
const availableIds = selectedAccountIds.length
|
const accountMap = new Map((accounts || []).map((account) => [account.id, account]));
|
||||||
|
const baseIds = selectedAccountIds.length
|
||||||
? selectedAccountIds
|
? selectedAccountIds
|
||||||
: accountBuckets.freeOrSelected.map((account) => account.id);
|
: accountBuckets.freeOrSelected.map((account) => account.id);
|
||||||
|
const availableIds = baseIds.filter((id) => isAccountAvailable(accountMap.get(id)));
|
||||||
if (!availableIds.length) {
|
if (!availableIds.length) {
|
||||||
showNotification("Нет доступных аккаунтов для назначения.", "error");
|
showNotification("Нет доступных аккаунтов для назначения.", "error");
|
||||||
return;
|
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) => {
|
const moveAccountToTask = async (accountId) => {
|
||||||
if (!window.api || selectedTaskId == null) return;
|
if (!window.api || selectedTaskId == null) return;
|
||||||
await assignAccountsToTask([accountId]);
|
await assignAccountsToTask([accountId]);
|
||||||
|
|||||||
@ -82,11 +82,26 @@ export default function useTaskActions({
|
|||||||
let accountRolesMap = { ...taskAccountRoles };
|
let accountRolesMap = { ...taskAccountRoles };
|
||||||
let accountIds = Object.keys(accountRolesMap).map((id) => Number(id));
|
let accountIds = Object.keys(accountRolesMap).map((id) => Number(id));
|
||||||
const autoRoleMode = nextForm.rolesMode === "auto";
|
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) {
|
if (autoRoleMode && nextForm.requireSameBotInBoth) {
|
||||||
const required = Math.max(1, Number(nextForm.maxCompetitorBots || 1));
|
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));
|
.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 = {};
|
accountRolesMap = {};
|
||||||
chosen.forEach((accountId) => {
|
chosen.forEach((accountId) => {
|
||||||
const existing = taskAccountRoles[accountId] || {};
|
const existing = taskAccountRoles[accountId] || {};
|
||||||
@ -97,7 +112,11 @@ export default function useTaskActions({
|
|||||||
setSelectedAccountIds(chosen);
|
setSelectedAccountIds(chosen);
|
||||||
}
|
}
|
||||||
if (autoRoleMode && nextForm.autoAssignAccounts && (!accountIds || accountIds.length === 0)) {
|
if (autoRoleMode && nextForm.autoAssignAccounts && (!accountIds || accountIds.length === 0)) {
|
||||||
accountIds = accounts.map((account) => account.id);
|
accountIds = eligibleIds;
|
||||||
|
if (!accountIds.length) {
|
||||||
|
showNotification("Нет доступных аккаунтов (все в ограничении).", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
accountRolesMap = {};
|
accountRolesMap = {};
|
||||||
accountIds.forEach((accountId) => {
|
accountIds.forEach((accountId) => {
|
||||||
const existing = taskAccountRoles[accountId] || {};
|
const existing = taskAccountRoles[accountId] || {};
|
||||||
|
|||||||
@ -26,11 +26,24 @@ export default function useTaskPresets({
|
|||||||
showNotification("Нет доступных аккаунтов.", "error");
|
showNotification("Нет доступных аккаунтов.", "error");
|
||||||
return;
|
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 requiredCount = 3;
|
||||||
const baseIds = selectedAccountIds.length >= requiredCount
|
const baseIds = selectedAccountIds.length >= requiredCount
|
||||||
? selectedAccountIds.slice()
|
? selectedAccountIds.slice()
|
||||||
: accounts.map((account) => account.id);
|
: eligibleAccounts.map((account) => account.id);
|
||||||
if (baseIds.length < 3) {
|
if (baseIds.length < 3) {
|
||||||
showNotification("Для раздельных ролей желательно минимум 3 аккаунта (мониторинг/инвайт/подтверждение).", "info");
|
showNotification("Для раздельных ролей желательно минимум 3 аккаунта (мониторинг/инвайт/подтверждение).", "info");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,6 +202,9 @@ function AccountsTab({
|
|||||||
<div>
|
<div>
|
||||||
<div className="account-phone">{formatAccountLabel(account)}</div>
|
<div className="account-phone">{formatAccountLabel(account)}</div>
|
||||||
<div className={`status ${account.status}`}>{formatAccountStatus(account.status)}</div>
|
<div className={`status ${account.status}`}>{formatAccountStatus(account.status)}</div>
|
||||||
|
{account.status && account.status !== "ok" && (
|
||||||
|
<span className="task-badge warn pending-badge">в спаме</span>
|
||||||
|
)}
|
||||||
<div className={`account-status-pill ${selected ? "busy" : "free"}`}>
|
<div className={`account-status-pill ${selected ? "busy" : "free"}`}>
|
||||||
{selected ? "В задаче" : "Свободен"}
|
{selected ? "В задаче" : "Свободен"}
|
||||||
</div>
|
</div>
|
||||||
@ -408,6 +411,9 @@ function AccountsTab({
|
|||||||
<div>
|
<div>
|
||||||
<div className="account-phone">{formatAccountLabel(account)}</div>
|
<div className="account-phone">{formatAccountLabel(account)}</div>
|
||||||
<div className={`status ${account.status}`}>{formatAccountStatus(account.status)}</div>
|
<div className={`status ${account.status}`}>{formatAccountStatus(account.status)}</div>
|
||||||
|
{account.status && account.status !== "ok" && (
|
||||||
|
<span className="task-badge warn pending-badge">в спаме</span>
|
||||||
|
)}
|
||||||
<div className="account-status-pill busy">
|
<div className="account-status-pill busy">
|
||||||
Занят
|
Занят
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user