This commit is contained in:
Ivan Neplokhov 2026-02-02 15:47:44 +04:00
parent f48cc365e1
commit 3728863b63
7 changed files with 138 additions and 40 deletions

View File

@ -1,6 +1,6 @@
{
"name": "telegram-invite-automation",
"version": "1.2.0",
"version": "1.3.0",
"private": true,
"description": "Automated user parsing and invites for Telegram groups",
"main": "src/main/index.js",
@ -8,16 +8,16 @@
"scripts": {
"dev": "concurrently -k \"vite\" \"wait-on http://127.0.0.1:5173 && electron .\"",
"start": "electron .",
"build": "vite build && electron-builder",
"build:win": "vite build && electron-builder --win",
"build:win:x64": "vite build && electron-builder --win --x64",
"build:win:x64:installer": "vite build && electron-builder --win --x64 --config.win.target=nsis",
"build:win:x64:portable": "vite build && electron-builder --win --x64 --config.win.target=portable",
"build": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder",
"build:win": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --win",
"build:win:x64": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --win --x64",
"build:win:x64:installer": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --win --x64 --config.win.target=nsis",
"build:win:x64:portable": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --win --x64 --config.win.target=portable",
"build:win:x64:portable:zip": "npm run build:win:x64:portable && zip -j -o dist/release/Telegram-Invite-Automation-win-portable-x64.zip dist/release/Telegram-Invite-Automation-win-x64-*.exe",
"build:mac": "vite build && electron-builder --mac",
"build:all": "vite build && electron-builder --win --mac",
"build:linux": "vite build && electron-builder --linux",
"dist": "vite build && electron-builder",
"build:mac": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --mac",
"build:all": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --win --mac",
"build:linux": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder --linux",
"dist": "NODE_OPTIONS=--no-warnings vite build && NODE_OPTIONS=--no-warnings electron-builder",
"build:converter:mac": "bash scripts/build-converter.sh",
"build:converter:win": "powershell -ExecutionPolicy Bypass -File scripts/build-converter.ps1"
},
@ -29,6 +29,7 @@
"react-dom": "^18.2.0"
},
"devDependencies": {
"@electron/notarize": "^2.2.0",
"@vitejs/plugin-react": "^4.2.1",
"concurrently": "^8.2.2",
"electron": "^29.1.0",
@ -69,12 +70,14 @@
"asarUnpack": [
"**/*.node"
],
"afterSign": "scripts/notarize.js",
"electronLanguages": [
"ru",
"en-US"
],
"mac": {
"category": "public.app-category.productivity",
"hardenedRuntime": true,
"target": [
"dmg"
],

28
scripts/notarize.js Normal file
View File

@ -0,0 +1,28 @@
const path = require("path");
const { notarize } = require("@electron/notarize");
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== "darwin") return;
const appleId = process.env.APPLE_ID;
const appleIdPassword = process.env.APPLE_ID_PASSWORD;
const teamId = process.env.APPLE_TEAM_ID;
if (!appleId || !appleIdPassword || !teamId) {
console.log("[notarize] Skipped: APPLE_ID / APPLE_ID_PASSWORD / APPLE_TEAM_ID not set.");
return;
}
const appName = context.packager.appInfo.productFilename;
const appPath = path.join(appOutDir, `${appName}.app`);
console.log(`[notarize] Notarizing ${appPath}`);
await notarize({
tool: "notarytool",
appPath,
appleId,
appleIdPassword,
teamId
});
};

View File

@ -235,8 +235,10 @@ export default function App() {
pushStep("Проверка прав инвайта", okCount > 0 ? "ok" : "warn", `OK: ${okCount} / ${inviteAccessStatus.length}`);
}
pushStep("Очередь", queueStats.total > 0 ? "ok" : "warn", `В очереди: ${queueStats.total}`);
const intervalOk = Number(taskForm.minInterval) <= Number(taskForm.maxInterval);
pushStep("Интервалы и лимиты", intervalOk ? "ok" : "warn", `Мин: ${taskForm.minInterval} · Макс: ${taskForm.maxInterval}`);
const minInterval = Number(taskForm.minIntervalMinutes || 0);
const maxInterval = Number(taskForm.maxIntervalMinutes || 0);
const intervalOk = minInterval > 0 && maxInterval > 0 && minInterval <= maxInterval;
pushStep("Интервалы и лимиты", intervalOk ? "ok" : "warn", `Мин: ${minInterval || "—"} · Макс: ${maxInterval || "—"}`);
if (taskForm.inviteViaAdmins) {
pushStep("Инвайт через админов", taskForm.inviteAdminMasterId ? "ok" : "warn", taskForm.inviteAdminMasterId ? "Мастер-админ выбран" : "Не выбран мастер-админ");
} else {
@ -552,7 +554,9 @@ export default function App() {
loadAccountAssignments,
showNotification,
setTaskNotice,
setAccounts
setAccounts,
membershipStatus,
refreshMembership
});
const { applyTaskPreset } = useTaskPresets({
hasSelectedTask,
@ -883,8 +887,6 @@ export default function App() {
accessStatus,
roleSummary,
mutualContactDiagnostics,
accountById,
formatAccountLabel,
accountEvents,
clearAccountEvents,
onSettingsChange,

View File

@ -11,7 +11,9 @@ export default function useAccountManagement({
loadAccountAssignments,
showNotification,
setTaskNotice,
setAccounts
setAccounts,
membershipStatus,
refreshMembership
}) {
const persistAccountRoles = async (next) => {
if (!window.api || selectedTaskId == null) return;
@ -76,7 +78,7 @@ export default function useAccountManagement({
persistAccountRoles(next);
};
const applyRolePreset = (type) => {
const applyRolePreset = async (type) => {
if (!hasSelectedTask) return;
const availableIds = selectedAccountIds.length
? selectedAccountIds
@ -116,13 +118,64 @@ export default function useAccountManagement({
const existing = taskAccountRoles[id] || {};
next[id] = { monitor: false, invite: false, confirm: true, inviteLimit: existing.inviteLimit || 0 };
});
if (monitorIds.length < monitorCount) {
if (typeof refreshMembership === "function") {
await refreshMembership("roles");
}
const monitorRoleIds = Object.entries(next)
.filter(([, roles]) => roles && roles.monitor)
.map(([id]) => Number(id));
const monitorInCompetitors = monitorRoleIds.filter((id) => {
const membership = membershipStatus && membershipStatus[id];
if (!membership) return false;
const total = Number(membership.competitorTotal || 0);
return total > 0 && Number(membership.competitorCount || 0) > 0;
});
if (!monitorRoleIds.length) {
showNotification("Нет бота мониторинга. Назначьте роль мониторинга.", "error");
} else if (!monitorInCompetitors.length) {
showNotification("Нет бота мониторинга в группах конкурентов. Введите бота в конкурентов или включите авто‑вступление.", "error");
} else {
showNotification("Не хватает аккаунтов для роли мониторинга.", "error");
}
}
if (inviteIds.length < inviteCount) {
if (typeof refreshMembership === "function") {
await refreshMembership("roles");
}
const inviteRoleIds = Object.entries(next)
.filter(([, roles]) => roles && roles.invite)
.map(([id]) => Number(id));
const inviteInOurGroup = inviteRoleIds.filter(
(id) => membershipStatus && membershipStatus[id] && membershipStatus[id].ourGroupMember
);
if (!inviteRoleIds.length) {
showNotification("Нет инвайт‑бота. Назначьте роль инвайта.", "error");
} else if (!inviteInOurGroup.length) {
showNotification("Нет инвайт‑бота в нашей группе. Введите бота в нашу группу или включите авто‑вступление.", "error");
} else {
showNotification("Не хватает аккаунтов для роли инвайта.", "error");
}
}
if (taskForm.separateConfirmRoles && confirmIds.length < confirmCount) {
if (typeof refreshMembership === "function") {
await refreshMembership("roles");
}
const confirmRoleIds = Object.entries(next)
.filter(([, roles]) => roles && roles.confirm)
.map(([id]) => Number(id));
const confirmInOurGroup = confirmRoleIds.filter(
(id) => membershipStatus && membershipStatus[id] && membershipStatus[id].ourGroupMember
);
if (!confirmRoleIds.length) {
showNotification("Нет подтверждающего бота. Назначьте роль подтверждения.", "error");
} else if (!confirmInOurGroup.length) {
showNotification("Нет подтверждающего бота в нашей группе. Введите бота в нашу группу или включите авто‑вступление.", "error");
} else {
showNotification("Не хватает аккаунтов для роли подтверждения.", "error");
}
}
}
const ids = Object.keys(next).map((id) => Number(id));
setTaskAccountRoles(next);
setSelectedAccountIds(ids);

View File

@ -108,8 +108,6 @@ export default function useAppTabGroups({
accessStatus,
roleSummary,
mutualContactDiagnostics,
accountById,
formatAccountLabel,
accountEvents,
clearAccountEvents,
onSettingsChange,

View File

@ -99,14 +99,6 @@ export default function useTabProps(
setConfirmPage,
confirmPageCount,
pagedConfirmQueue,
queueItems,
queueStats,
queueSearch,
setQueueSearch,
queuePage,
setQueuePage,
queuePageCount,
pagedQueue,
clearConfirmQueue,
auditSearch,
setAuditSearch,
@ -122,9 +114,7 @@ export default function useTabProps(
selectedTask,
accessStatus,
roleSummary,
mutualContactDiagnostics,
accountById,
formatAccountLabel
mutualContactDiagnostics
} = logsTab;
const {
queueStats,
@ -242,14 +232,6 @@ export default function useTabProps(
setConfirmPage,
confirmPageCount,
pagedConfirmQueue,
queueItems,
queueStats,
queueSearch,
setQueueSearch,
queuePage,
setQueuePage,
queuePageCount,
pagedQueue,
clearConfirmQueue,
auditSearch,
setAuditSearch,

View File

@ -1962,6 +1962,38 @@ label .hint {
margin-top: 6px;
}
.log-table {
display: grid;
gap: 8px;
margin-top: 10px;
}
.log-head,
.log-row {
display: grid;
grid-template-columns: 1.2fr 1.6fr 1.4fr 0.6fr 0.8fr;
gap: 12px;
align-items: center;
}
.log-head {
font-size: 12px;
color: #64748b;
font-weight: 600;
}
.log-row {
padding: 8px 0;
border-bottom: 1px solid #e2e8f0;
font-size: 13px;
}
.log-empty {
font-size: 13px;
color: #64748b;
padding: 8px 0;
}
.log-result {
font-size: 13px;
color: #1f2937;