some
This commit is contained in:
parent
303755f221
commit
d5d2a84a2e
@ -802,6 +802,63 @@ const toCsv = (rows, headers) => {
|
|||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const explainInviteError = (error) => {
|
||||||
|
if (!error) return "";
|
||||||
|
if (error === "USER_ID_INVALID") {
|
||||||
|
return "Пользователь удален/скрыт; access_hash невалиден для этой сессии; приглашение в канал/чат без валидной сущности.";
|
||||||
|
}
|
||||||
|
if (error === "CHAT_WRITE_FORBIDDEN") {
|
||||||
|
return "Аккаунт не может приглашать: нет прав или он не участник группы.";
|
||||||
|
}
|
||||||
|
if (error === "USER_NOT_MUTUAL_CONTACT") {
|
||||||
|
return "Пользователь не взаимный контакт для добавляющего аккаунта. Обычно это происходит, когда в группе/канале включена опция «добавлять могут только контакты» или у пользователя закрыт приём инвайтов.";
|
||||||
|
}
|
||||||
|
if (error === "USER_PRIVACY_RESTRICTED") {
|
||||||
|
return "Приглашение запрещено пользователем: приватность не позволяет добавлять в группы.";
|
||||||
|
}
|
||||||
|
if (error === "USER_NOT_PARTICIPANT") {
|
||||||
|
return "Аккаунт не состоит в целевой группе или канал приватный.";
|
||||||
|
}
|
||||||
|
if (error === "USER_BANNED_IN_CHANNEL") {
|
||||||
|
return "Пользователь заблокирован в группе или канале назначения.";
|
||||||
|
}
|
||||||
|
if (error === "USER_BOT") {
|
||||||
|
return "Бота нельзя приглашать как обычного пользователя.";
|
||||||
|
}
|
||||||
|
if (error === "USER_KICKED") {
|
||||||
|
return "Пользователь был удален из группы ранее.";
|
||||||
|
}
|
||||||
|
if (error === "CHAT_ADMIN_REQUIRED") {
|
||||||
|
return "Для добавления участников нужны права администратора.";
|
||||||
|
}
|
||||||
|
if (error === "USER_ALREADY_PARTICIPANT") {
|
||||||
|
return "Пользователь уже состоит в целевой группе.";
|
||||||
|
}
|
||||||
|
if (error === "CHAT_MEMBER_ADD_FAILED") {
|
||||||
|
return "Telegram отклонил добавление участника (ограничения приватности, антиспам или запрет инвайтов).";
|
||||||
|
}
|
||||||
|
if (error === "INVITE_HASH_EXPIRED" || error === "INVITE_HASH_INVALID") {
|
||||||
|
return "Инвайт-ссылка недействительна или истекла.";
|
||||||
|
}
|
||||||
|
if (error === "CHANNEL_PRIVATE") {
|
||||||
|
return "Целевая группа/канал приватные и недоступны по ссылке.";
|
||||||
|
}
|
||||||
|
if (error === "AUTH_KEY_DUPLICATED") {
|
||||||
|
return "Сессия используется в другом месте, Telegram отозвал ключ.";
|
||||||
|
}
|
||||||
|
if (error.includes("FLOOD") || error.includes("PEER_FLOOD")) {
|
||||||
|
return "Ограничение Telegram по частоте действий.";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractErrorCode = (value) => {
|
||||||
|
if (!value) return "";
|
||||||
|
const text = String(value).trim();
|
||||||
|
const split = text.split(/[:(]/, 1);
|
||||||
|
return split && split[0] ? split[0].trim() : text;
|
||||||
|
};
|
||||||
|
|
||||||
ipcMain.handle("logs:export", async (_event, taskId) => {
|
ipcMain.handle("logs:export", async (_event, taskId) => {
|
||||||
const { canceled, filePath } = await dialog.showSaveDialog({
|
const { canceled, filePath } = await dialog.showSaveDialog({
|
||||||
title: "Выгрузить логи",
|
title: "Выгрузить логи",
|
||||||
@ -819,9 +876,14 @@ ipcMain.handle("logs:export", async (_event, taskId) => {
|
|||||||
queueCount: log.meta && log.meta.queueCount != null ? log.meta.queueCount : "",
|
queueCount: log.meta && log.meta.queueCount != null ? log.meta.queueCount : "",
|
||||||
batchSize: log.meta && log.meta.batchSize != null ? log.meta.batchSize : "",
|
batchSize: log.meta && log.meta.batchSize != null ? log.meta.batchSize : "",
|
||||||
successIds: JSON.stringify(log.successIds || []),
|
successIds: JSON.stringify(log.successIds || []),
|
||||||
errors: JSON.stringify(log.errors || [])
|
errors: JSON.stringify(log.errors || []),
|
||||||
|
errorsHuman: JSON.stringify((log.errors || []).map((value) => {
|
||||||
|
const code = extractErrorCode(value);
|
||||||
|
const explanation = explainInviteError(code);
|
||||||
|
return explanation ? `${value} (${explanation})` : value;
|
||||||
|
}))
|
||||||
}));
|
}));
|
||||||
const csv = toCsv(logs, ["taskId", "startedAt", "finishedAt", "invitedCount", "unconfirmedCount", "cycleLimit", "queueCount", "batchSize", "successIds", "errors"]);
|
const csv = toCsv(logs, ["taskId", "startedAt", "finishedAt", "invitedCount", "unconfirmedCount", "cycleLimit", "queueCount", "batchSize", "successIds", "errors", "errorsHuman"]);
|
||||||
fs.writeFileSync(filePath, csv, "utf8");
|
fs.writeFileSync(filePath, csv, "utf8");
|
||||||
return { ok: true, filePath };
|
return { ok: true, filePath };
|
||||||
});
|
});
|
||||||
@ -834,22 +896,37 @@ ipcMain.handle("invites:export", async (_event, taskId) => {
|
|||||||
if (canceled || !filePath) return { ok: false, canceled: true };
|
if (canceled || !filePath) return { ok: false, canceled: true };
|
||||||
|
|
||||||
const invites = store.listInvites(2000, taskId);
|
const invites = store.listInvites(2000, taskId);
|
||||||
const csv = toCsv(invites, [
|
const enriched = invites.map((invite) => {
|
||||||
|
const errorCode = extractErrorCode(invite.error);
|
||||||
|
const skippedCode = extractErrorCode(invite.skippedReason);
|
||||||
|
const confirmCode = extractErrorCode(invite.confirmError);
|
||||||
|
return {
|
||||||
|
...invite,
|
||||||
|
errorHuman: explainInviteError(errorCode),
|
||||||
|
skippedReasonHuman: explainInviteError(skippedCode),
|
||||||
|
confirmErrorHuman: explainInviteError(confirmCode)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const csv = toCsv(enriched, [
|
||||||
"taskId",
|
"taskId",
|
||||||
"invitedAt",
|
"invitedAt",
|
||||||
"userId",
|
"userId",
|
||||||
"username",
|
"username",
|
||||||
"status",
|
"status",
|
||||||
"error",
|
"error",
|
||||||
|
"errorHuman",
|
||||||
"confirmed",
|
"confirmed",
|
||||||
"confirmError",
|
"confirmError",
|
||||||
|
"confirmErrorHuman",
|
||||||
"accountId",
|
"accountId",
|
||||||
"accountPhone",
|
"accountPhone",
|
||||||
"watcherAccountId",
|
"watcherAccountId",
|
||||||
"watcherPhone",
|
"watcherPhone",
|
||||||
"strategy",
|
"strategy",
|
||||||
"strategyMeta",
|
"strategyMeta",
|
||||||
"sourceChat"
|
"sourceChat",
|
||||||
|
"skippedReason",
|
||||||
|
"skippedReasonHuman"
|
||||||
]);
|
]);
|
||||||
fs.writeFileSync(filePath, csv, "utf8");
|
fs.writeFileSync(filePath, csv, "utf8");
|
||||||
return { ok: true, filePath };
|
return { ok: true, filePath };
|
||||||
@ -875,9 +952,12 @@ ipcMain.handle("invites:exportProblems", async (_event, taskId) => {
|
|||||||
username: invite.username ? `@${invite.username}` : "",
|
username: invite.username ? `@${invite.username}` : "",
|
||||||
status: invite.status,
|
status: invite.status,
|
||||||
error: invite.error || "",
|
error: invite.error || "",
|
||||||
|
errorHuman: explainInviteError(extractErrorCode(invite.error)),
|
||||||
skippedReason: invite.skippedReason || "",
|
skippedReason: invite.skippedReason || "",
|
||||||
|
skippedReasonHuman: explainInviteError(extractErrorCode(invite.skippedReason)),
|
||||||
confirmed: invite.confirmed,
|
confirmed: invite.confirmed,
|
||||||
confirmError: invite.confirmError || "",
|
confirmError: invite.confirmError || "",
|
||||||
|
confirmErrorHuman: explainInviteError(extractErrorCode(invite.confirmError)),
|
||||||
invitedAt: invite.invitedAt,
|
invitedAt: invite.invitedAt,
|
||||||
sourceChat: invite.sourceChat,
|
sourceChat: invite.sourceChat,
|
||||||
targetChat: invite.targetChat
|
targetChat: invite.targetChat
|
||||||
@ -888,9 +968,12 @@ ipcMain.handle("invites:exportProblems", async (_event, taskId) => {
|
|||||||
"username",
|
"username",
|
||||||
"status",
|
"status",
|
||||||
"error",
|
"error",
|
||||||
|
"errorHuman",
|
||||||
"skippedReason",
|
"skippedReason",
|
||||||
|
"skippedReasonHuman",
|
||||||
"confirmed",
|
"confirmed",
|
||||||
"confirmError",
|
"confirmError",
|
||||||
|
"confirmErrorHuman",
|
||||||
"invitedAt",
|
"invitedAt",
|
||||||
"sourceChat",
|
"sourceChat",
|
||||||
"targetChat"
|
"targetChat"
|
||||||
|
|||||||
@ -137,11 +137,11 @@ function initStore(userDataPath) {
|
|||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
our_group TEXT NOT NULL,
|
our_group TEXT NOT NULL,
|
||||||
min_interval_minutes INTEGER NOT NULL,
|
min_interval_minutes INTEGER NOT NULL DEFAULT 1,
|
||||||
max_interval_minutes INTEGER NOT NULL,
|
max_interval_minutes INTEGER NOT NULL DEFAULT 3,
|
||||||
daily_limit INTEGER NOT NULL,
|
daily_limit INTEGER NOT NULL DEFAULT 15,
|
||||||
history_limit INTEGER NOT NULL,
|
history_limit INTEGER NOT NULL DEFAULT 35,
|
||||||
max_invites_per_cycle INTEGER NOT NULL DEFAULT 20,
|
max_invites_per_cycle INTEGER NOT NULL DEFAULT 1,
|
||||||
max_competitor_bots INTEGER NOT NULL,
|
max_competitor_bots INTEGER NOT NULL,
|
||||||
max_our_bots INTEGER NOT NULL,
|
max_our_bots INTEGER NOT NULL,
|
||||||
random_accounts INTEGER NOT NULL DEFAULT 0,
|
random_accounts INTEGER NOT NULL DEFAULT 0,
|
||||||
@ -160,7 +160,7 @@ function initStore(userDataPath) {
|
|||||||
invite_via_admins INTEGER NOT NULL DEFAULT 0,
|
invite_via_admins INTEGER NOT NULL DEFAULT 0,
|
||||||
invite_admin_master_id INTEGER NOT NULL DEFAULT 0,
|
invite_admin_master_id INTEGER NOT NULL DEFAULT 0,
|
||||||
invite_admin_allow_flood INTEGER NOT NULL DEFAULT 0,
|
invite_admin_allow_flood INTEGER NOT NULL DEFAULT 0,
|
||||||
warmup_enabled INTEGER NOT NULL DEFAULT 0,
|
warmup_enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
warmup_start_limit INTEGER NOT NULL DEFAULT 3,
|
warmup_start_limit INTEGER NOT NULL DEFAULT 3,
|
||||||
warmup_daily_increase INTEGER NOT NULL DEFAULT 2,
|
warmup_daily_increase INTEGER NOT NULL DEFAULT 2,
|
||||||
cycle_competitors INTEGER NOT NULL DEFAULT 0,
|
cycle_competitors INTEGER NOT NULL DEFAULT 0,
|
||||||
@ -558,10 +558,18 @@ function initStore(userDataPath) {
|
|||||||
if (!task.warmup_enabled) return baseLimit;
|
if (!task.warmup_enabled) return baseLimit;
|
||||||
const createdAt = task.created_at ? new Date(task.created_at).getTime() : Date.now();
|
const createdAt = task.created_at ? new Date(task.created_at).getTime() : Date.now();
|
||||||
const days = Math.max(0, Math.floor((Date.now() - createdAt) / (24 * 60 * 60 * 1000)));
|
const days = Math.max(0, Math.floor((Date.now() - createdAt) / (24 * 60 * 60 * 1000)));
|
||||||
const startLimit = Math.max(1, Number(task.warmup_start_limit || 1));
|
const dayIndex = days + 1;
|
||||||
const step = Math.max(0, Number(task.warmup_daily_increase || 0));
|
let warmed = 7;
|
||||||
const warmed = startLimit + days * step;
|
if (dayIndex <= 3) warmed = 1;
|
||||||
return Math.min(baseLimit || warmed, warmed);
|
else if (dayIndex <= 7) warmed = 2;
|
||||||
|
else if (dayIndex <= 12) warmed = 3;
|
||||||
|
else if (dayIndex <= 18) warmed = 4;
|
||||||
|
else if (dayIndex <= 25) warmed = 5;
|
||||||
|
else if (dayIndex <= 33) warmed = 6;
|
||||||
|
if (baseLimit > 0) {
|
||||||
|
return Math.min(baseLimit, warmed);
|
||||||
|
}
|
||||||
|
return warmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveTask(task) {
|
function saveTask(task) {
|
||||||
@ -584,7 +592,7 @@ function initStore(userDataPath) {
|
|||||||
task.maxIntervalMinutes,
|
task.maxIntervalMinutes,
|
||||||
task.dailyLimit,
|
task.dailyLimit,
|
||||||
task.historyLimit,
|
task.historyLimit,
|
||||||
task.maxInvitesPerCycle || 20,
|
task.maxInvitesPerCycle || 1,
|
||||||
task.maxCompetitorBots,
|
task.maxCompetitorBots,
|
||||||
task.maxOurBots,
|
task.maxOurBots,
|
||||||
task.randomAccounts ? 1 : 0,
|
task.randomAccounts ? 1 : 0,
|
||||||
@ -630,7 +638,7 @@ function initStore(userDataPath) {
|
|||||||
task.maxIntervalMinutes,
|
task.maxIntervalMinutes,
|
||||||
task.dailyLimit,
|
task.dailyLimit,
|
||||||
task.historyLimit,
|
task.historyLimit,
|
||||||
task.maxInvitesPerCycle || 20,
|
task.maxInvitesPerCycle || 1,
|
||||||
task.maxCompetitorBots,
|
task.maxCompetitorBots,
|
||||||
task.maxOurBots,
|
task.maxOurBots,
|
||||||
task.randomAccounts ? 1 : 0,
|
task.randomAccounts ? 1 : 0,
|
||||||
|
|||||||
@ -22,11 +22,11 @@ const emptySettings = {
|
|||||||
id: null,
|
id: null,
|
||||||
name: "",
|
name: "",
|
||||||
ourGroup: "",
|
ourGroup: "",
|
||||||
minIntervalMinutes: 5,
|
minIntervalMinutes: 1,
|
||||||
maxIntervalMinutes: 10,
|
maxIntervalMinutes: 3,
|
||||||
dailyLimit: 100,
|
dailyLimit: 15,
|
||||||
historyLimit: 100,
|
historyLimit: 35,
|
||||||
maxInvitesPerCycle: 20,
|
maxInvitesPerCycle: 1,
|
||||||
maxCompetitorBots: 1,
|
maxCompetitorBots: 1,
|
||||||
maxOurBots: 1,
|
maxOurBots: 1,
|
||||||
randomAccounts: false,
|
randomAccounts: false,
|
||||||
@ -40,7 +40,7 @@ const emptySettings = {
|
|||||||
inviteViaAdmins: false,
|
inviteViaAdmins: false,
|
||||||
inviteAdminMasterId: 0,
|
inviteAdminMasterId: 0,
|
||||||
inviteAdminAllowFlood: false,
|
inviteAdminAllowFlood: false,
|
||||||
warmupEnabled: false,
|
warmupEnabled: true,
|
||||||
warmupStartLimit: 3,
|
warmupStartLimit: 3,
|
||||||
warmupDailyIncrease: 2,
|
warmupDailyIncrease: 2,
|
||||||
cycleCompetitors: false,
|
cycleCompetitors: false,
|
||||||
@ -58,11 +58,11 @@ const emptySettings = {
|
|||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name || "",
|
name: row.name || "",
|
||||||
ourGroup: row.our_group || "",
|
ourGroup: row.our_group || "",
|
||||||
minIntervalMinutes: Number(row.min_interval_minutes || 5),
|
minIntervalMinutes: Number(row.min_interval_minutes || 1),
|
||||||
maxIntervalMinutes: Number(row.max_interval_minutes || 10),
|
maxIntervalMinutes: Number(row.max_interval_minutes || 3),
|
||||||
dailyLimit: Number(row.daily_limit || 100),
|
dailyLimit: Number(row.daily_limit || 15),
|
||||||
historyLimit: Number(row.history_limit || 200),
|
historyLimit: Number(row.history_limit || 35),
|
||||||
maxInvitesPerCycle: Number(row.max_invites_per_cycle || 20),
|
maxInvitesPerCycle: Number(row.max_invites_per_cycle || 1),
|
||||||
maxCompetitorBots: Number(row.max_competitor_bots || 1),
|
maxCompetitorBots: Number(row.max_competitor_bots || 1),
|
||||||
maxOurBots: Number(row.max_our_bots || 1),
|
maxOurBots: Number(row.max_our_bots || 1),
|
||||||
randomAccounts: Boolean(row.random_accounts),
|
randomAccounts: Boolean(row.random_accounts),
|
||||||
@ -76,7 +76,7 @@ const emptySettings = {
|
|||||||
inviteViaAdmins: Boolean(row.invite_via_admins),
|
inviteViaAdmins: Boolean(row.invite_via_admins),
|
||||||
inviteAdminMasterId: Number(row.invite_admin_master_id || 0),
|
inviteAdminMasterId: Number(row.invite_admin_master_id || 0),
|
||||||
inviteAdminAllowFlood: Boolean(row.invite_admin_allow_flood),
|
inviteAdminAllowFlood: Boolean(row.invite_admin_allow_flood),
|
||||||
warmupEnabled: Boolean(row.warmup_enabled),
|
warmupEnabled: row.warmup_enabled == null ? true : Boolean(row.warmup_enabled),
|
||||||
warmupStartLimit: Number(row.warmup_start_limit || 3),
|
warmupStartLimit: Number(row.warmup_start_limit || 3),
|
||||||
warmupDailyIncrease: Number(row.warmup_daily_increase || 2),
|
warmupDailyIncrease: Number(row.warmup_daily_increase || 2),
|
||||||
cycleCompetitors: Boolean(row.cycle_competitors),
|
cycleCompetitors: Boolean(row.cycle_competitors),
|
||||||
@ -723,6 +723,9 @@ export default function App() {
|
|||||||
if (error === "USER_ALREADY_PARTICIPANT") {
|
if (error === "USER_ALREADY_PARTICIPANT") {
|
||||||
return "Пользователь уже состоит в целевой группе.";
|
return "Пользователь уже состоит в целевой группе.";
|
||||||
}
|
}
|
||||||
|
if (error === "CHAT_MEMBER_ADD_FAILED") {
|
||||||
|
return "Telegram отклонил добавление участника (ограничения приватности, антиспам или запрет инвайтов).";
|
||||||
|
}
|
||||||
if (error === "INVITE_HASH_EXPIRED" || error === "INVITE_HASH_INVALID") {
|
if (error === "INVITE_HASH_EXPIRED" || error === "INVITE_HASH_INVALID") {
|
||||||
return "Инвайт-ссылка недействительна или истекла.";
|
return "Инвайт-ссылка недействительна или истекла.";
|
||||||
}
|
}
|
||||||
@ -2764,42 +2767,51 @@ export default function App() {
|
|||||||
onChange={(event) => setTaskForm({ ...taskForm, warmupEnabled: event.target.checked })}
|
onChange={(event) => setTaskForm({ ...taskForm, warmupEnabled: event.target.checked })}
|
||||||
/>
|
/>
|
||||||
Разогрев лимита
|
Разогрев лимита
|
||||||
<span className="hint">Плавно увеличивает дневной лимит по дням.</span>
|
<span
|
||||||
</label>
|
className="hint"
|
||||||
<label>
|
title="График: дни 1–3 — 1/д; 4–7 — 2/д; 8–12 — 3/д; 13–18 — 4/д; 19–25 — 5/д; 26–33 — 6/д; с 34-го — 7/д. Итоговый лимит не превышает дневной лимит задачи."
|
||||||
<span className="label-line">Стартовый лимит</span>
|
>
|
||||||
<input
|
Плавно увеличивает дневной лимит по дням.
|
||||||
type="number"
|
</span>
|
||||||
min="1"
|
<span className="hint">Нужен для “прогрева” новых аккаунтов и снижения риска флуда.</span>
|
||||||
value={taskForm.warmupStartLimit === "" ? "" : taskForm.warmupStartLimit}
|
<span className="hint">График: 1/д ×3, 2/д ×4, 3/д ×5, 4/д ×6, 5/д ×7, 6/д ×8, далее 7/д.</span>
|
||||||
onChange={(event) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
setTaskForm({ ...taskForm, warmupStartLimit: value === "" ? "" : Number(value) });
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
const value = Number(taskForm.warmupStartLimit);
|
|
||||||
setTaskForm({ ...taskForm, warmupStartLimit: Number.isFinite(value) && value > 0 ? value : 1 });
|
|
||||||
}}
|
|
||||||
disabled={!taskForm.warmupEnabled}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<span className="label-line">Прирост в день</span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
value={taskForm.warmupDailyIncrease === "" ? "" : taskForm.warmupDailyIncrease}
|
|
||||||
onChange={(event) => {
|
|
||||||
const value = event.target.value;
|
|
||||||
setTaskForm({ ...taskForm, warmupDailyIncrease: value === "" ? "" : Number(value) });
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
const value = Number(taskForm.warmupDailyIncrease);
|
|
||||||
setTaskForm({ ...taskForm, warmupDailyIncrease: Number.isFinite(value) && value >= 0 ? value : 0 });
|
|
||||||
}}
|
|
||||||
disabled={!taskForm.warmupEnabled}
|
|
||||||
/>
|
|
||||||
</label>
|
</label>
|
||||||
|
{!taskForm.warmupEnabled && (
|
||||||
|
<>
|
||||||
|
<label>
|
||||||
|
<span className="label-line">Стартовый лимит</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
value={taskForm.warmupStartLimit === "" ? "" : taskForm.warmupStartLimit}
|
||||||
|
onChange={(event) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
setTaskForm({ ...taskForm, warmupStartLimit: value === "" ? "" : Number(value) });
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
const value = Number(taskForm.warmupStartLimit);
|
||||||
|
setTaskForm({ ...taskForm, warmupStartLimit: Number.isFinite(value) && value > 0 ? value : 1 });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span className="label-line">Прирост в день</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
value={taskForm.warmupDailyIncrease === "" ? "" : taskForm.warmupDailyIncrease}
|
||||||
|
onChange={(event) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
setTaskForm({ ...taskForm, warmupDailyIncrease: value === "" ? "" : Number(value) });
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
const value = Number(taskForm.warmupDailyIncrease);
|
||||||
|
setTaskForm({ ...taskForm, warmupDailyIncrease: Number.isFinite(value) && value >= 0 ? value : 0 });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details className="section">
|
<details className="section">
|
||||||
|
|||||||
@ -516,7 +516,7 @@ body {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(15, 23, 42, 0.4);
|
background: rgba(15, 23, 42, 0.4);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@ -527,6 +527,8 @@ body {
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
width: min(640px, 100%);
|
width: min(640px, 100%);
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
@ -607,7 +609,7 @@ body {
|
|||||||
|
|
||||||
.task-columns {
|
.task-columns {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,6 +101,19 @@ function LogsTab({
|
|||||||
if (value === "group") return "группа";
|
if (value === "group") return "группа";
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
const formatErrorWithExplain = (value) => {
|
||||||
|
if (!value) return "—";
|
||||||
|
const code = String(value);
|
||||||
|
const explanation = explainInviteError(code.split(/[:(]/, 1)[0].trim());
|
||||||
|
if (!explanation) return code;
|
||||||
|
if (code.includes("(")) return code;
|
||||||
|
return `${code} (${explanation})`;
|
||||||
|
};
|
||||||
|
const explainRawError = (value) => {
|
||||||
|
if (!value) return "";
|
||||||
|
const code = String(value).split(/[:(]/, 1)[0].trim();
|
||||||
|
return explainInviteError(code);
|
||||||
|
};
|
||||||
|
|
||||||
const getDurationMs = (start, finish) => {
|
const getDurationMs = (start, finish) => {
|
||||||
const startMs = new Date(start).getTime();
|
const startMs = new Date(start).getTime();
|
||||||
@ -434,19 +447,14 @@ function LogsTab({
|
|||||||
return account ? formatAccountLabel(account) : (invite.watcherPhone || "—");
|
return account ? formatAccountLabel(account) : (invite.watcherPhone || "—");
|
||||||
})()}</div>
|
})()}</div>
|
||||||
{invite.skippedReason && invite.skippedReason !== "" && (
|
{invite.skippedReason && invite.skippedReason !== "" && (
|
||||||
<div className="log-errors">Пропуск: {invite.skippedReason}</div>
|
<div className="log-errors">Результат: {formatErrorWithExplain(invite.skippedReason)}</div>
|
||||||
)}
|
)}
|
||||||
{invite.error && invite.error !== "" && (
|
{invite.error && invite.error !== "" && (
|
||||||
<div className="log-errors">Причина: {invite.error}</div>
|
<div className="log-errors">Ошибка: {formatErrorWithExplain(invite.error)}</div>
|
||||||
)}
|
|
||||||
{invite.error && (
|
|
||||||
<div className="log-users">
|
|
||||||
Вероятная причина: {explainInviteError(invite.error) || "Причина не определена"}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{invite.confirmError && (
|
{invite.confirmError && (
|
||||||
<div className="log-errors">
|
<div className="log-errors">
|
||||||
Подтверждение: {invite.confirmError}
|
Проверка участия: {formatErrorWithExplain(invite.confirmError)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{invite.strategy && (
|
{invite.strategy && (
|
||||||
@ -478,14 +486,14 @@ function LogsTab({
|
|||||||
<div>Тип цели: {formatTargetType(invite.targetType)}</div>
|
<div>Тип цели: {formatTargetType(invite.targetType)}</div>
|
||||||
<div>Действие: {invite.action || "invite"}</div>
|
<div>Действие: {invite.action || "invite"}</div>
|
||||||
<div>Статус: {formatInviteStatus(invite.status)}</div>
|
<div>Статус: {formatInviteStatus(invite.status)}</div>
|
||||||
<div>Пропуск: {invite.skippedReason || "—"}</div>
|
<div>Результат: {formatErrorWithExplain(invite.skippedReason)}</div>
|
||||||
<div>Ошибка: {invite.error || "—"}</div>
|
<div>Ошибка: {formatErrorWithExplain(invite.error)}</div>
|
||||||
<div>Подтверждение: {invite.confirmError || "—"}</div>
|
<div>Проверка участия: {formatErrorWithExplain(invite.confirmError)}</div>
|
||||||
{invite.watcherAccountId && invite.accountId && invite.watcherAccountId !== invite.accountId
|
{invite.watcherAccountId && invite.accountId && invite.watcherAccountId !== invite.accountId
|
||||||
&& selectedTask && selectedTask.randomAccounts && hasBothRoles(invite.watcherAccountId) && (
|
&& selectedTask && selectedTask.randomAccounts && hasBothRoles(invite.watcherAccountId) && (
|
||||||
<div>Примечание: у наблюдателя стоят обе роли, но включен случайный выбор — инвайт выполнен другим аккаунтом.</div>
|
<div>Примечание: у наблюдателя стоят обе роли, но включен случайный выбор — инвайт выполнен другим аккаунтом.</div>
|
||||||
)}
|
)}
|
||||||
<div>Вероятная причина: {explainInviteError(invite.error) || "Причина не определена"}</div>
|
<div>Пояснение: {explainRawError(invite.error) || explainRawError(invite.confirmError) || "Причина не определена"}</div>
|
||||||
<div>Стратегия: {invite.strategy || "—"}</div>
|
<div>Стратегия: {invite.strategy || "—"}</div>
|
||||||
<div className="pre-line">
|
<div className="pre-line">
|
||||||
{invite.strategyMeta ? `Стратегии:\n${formatStrategies(invite.strategyMeta)}` : "Стратегии: —"}
|
{invite.strategyMeta ? `Стратегии:\n${formatStrategies(invite.strategyMeta)}` : "Стратегии: —"}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user