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 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: "Нет аккаунтов с ролью мониторинга." };
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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] || {};
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -202,6 +202,9 @@ function AccountsTab({
|
||||
<div>
|
||||
<div className="account-phone">{formatAccountLabel(account)}</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"}`}>
|
||||
{selected ? "В задаче" : "Свободен"}
|
||||
</div>
|
||||
@ -408,6 +411,9 @@ function AccountsTab({
|
||||
<div>
|
||||
<div className="account-phone">{formatAccountLabel(account)}</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">
|
||||
Занят
|
||||
<button
|
||||
|
||||
Loading…
Reference in New Issue
Block a user