some
This commit is contained in:
parent
59d46f4e00
commit
712d11d8d8
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "telegram-invite-automation",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"description": "Automated user parsing and invites for Telegram groups",
|
||||
"main": "src/main/index.js",
|
||||
@ -35,6 +35,7 @@
|
||||
"build": {
|
||||
"appId": "com.profi.telegram-invite-automation",
|
||||
"productName": "Telegram Invite Automation",
|
||||
"icon": "resources/icon.png",
|
||||
"directories": {
|
||||
"output": "dist/release"
|
||||
},
|
||||
|
||||
BIN
resources/icon.png
Normal file
BIN
resources/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@ -14,9 +14,11 @@ let scheduler;
|
||||
const taskRunners = new Map();
|
||||
|
||||
function createWindow() {
|
||||
const iconPath = path.join(__dirname, "..", "..", "resources", "icon.png");
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
icon: iconPath,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
contextIsolation: true,
|
||||
@ -67,6 +69,18 @@ ipcMain.handle("accounts:delete", async (_event, accountId) => {
|
||||
store.addAccountEvent(accountId, "", "delete", "Account deleted by user");
|
||||
return { ok: true };
|
||||
});
|
||||
ipcMain.handle("db:clear", async () => {
|
||||
for (const runner of taskRunners.values()) {
|
||||
runner.stop();
|
||||
}
|
||||
taskRunners.clear();
|
||||
const accounts = store.listAccounts();
|
||||
for (const account of accounts) {
|
||||
await telegram.removeAccount(account.id);
|
||||
}
|
||||
store.clearAllData();
|
||||
return { ok: true };
|
||||
});
|
||||
ipcMain.handle("accounts:startLogin", async (_event, payload) => {
|
||||
const result = await telegram.startLogin(payload);
|
||||
return result;
|
||||
|
||||
@ -11,6 +11,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
startLogin: (payload) => ipcRenderer.invoke("accounts:startLogin", payload),
|
||||
completeLogin: (payload) => ipcRenderer.invoke("accounts:completeLogin", payload),
|
||||
importTdata: (payload) => ipcRenderer.invoke("accounts:importTdata", payload),
|
||||
clearDatabase: () => ipcRenderer.invoke("db:clear"),
|
||||
listLogs: (payload) => ipcRenderer.invoke("logs:list", payload),
|
||||
listInvites: (payload) => ipcRenderer.invoke("invites:list", payload),
|
||||
clearLogs: (taskId) => ipcRenderer.invoke("logs:clear", taskId),
|
||||
|
||||
@ -203,6 +203,20 @@ function initStore(userDataPath) {
|
||||
return db.prepare("SELECT * FROM accounts ORDER BY id DESC").all();
|
||||
}
|
||||
|
||||
function clearAllData() {
|
||||
db.prepare("DELETE FROM task_accounts").run();
|
||||
db.prepare("DELETE FROM task_competitors").run();
|
||||
db.prepare("DELETE FROM tasks").run();
|
||||
db.prepare("DELETE FROM invite_queue").run();
|
||||
db.prepare("DELETE FROM invites").run();
|
||||
db.prepare("DELETE FROM logs").run();
|
||||
db.prepare("DELETE FROM account_events").run();
|
||||
db.prepare("DELETE FROM accounts").run();
|
||||
db.prepare("DELETE FROM settings").run();
|
||||
db.prepare("INSERT INTO settings (key, value) VALUES (?, ?)")
|
||||
.run("settings", JSON.stringify(DEFAULT_SETTINGS));
|
||||
}
|
||||
|
||||
function findAccountByIdentity({ userId, phone, session }) {
|
||||
return db.prepare(`
|
||||
SELECT * FROM accounts
|
||||
@ -576,6 +590,7 @@ function initStore(userDataPath) {
|
||||
saveSettings,
|
||||
listAccounts,
|
||||
findAccountByIdentity,
|
||||
clearAllData,
|
||||
listTasks,
|
||||
getTask,
|
||||
saveTask,
|
||||
|
||||
@ -873,29 +873,33 @@ class TelegramManager {
|
||||
groups,
|
||||
lastMessageAt: "",
|
||||
lastSource: "",
|
||||
lastErrorAt: new Map()
|
||||
lastErrorAt: new Map(),
|
||||
lastSkipAt: new Map()
|
||||
};
|
||||
this.store.addAccountEvent(
|
||||
monitorAccount.account.id,
|
||||
monitorAccount.account.phone,
|
||||
"monitor_started",
|
||||
`Групп: ${resolved.length}`
|
||||
);
|
||||
const timer = setInterval(async () => {
|
||||
for (const [key, st] of state.entries()) {
|
||||
try {
|
||||
const messages = await monitorAccount.client.getMessages(st.entity, { limit: 10 });
|
||||
let totalMessages = 0;
|
||||
let enqueued = 0;
|
||||
let skipped = 0;
|
||||
for (const message of messages.reverse()) {
|
||||
totalMessages += 1;
|
||||
if (st.lastId && message.id <= st.lastId) continue;
|
||||
st.lastId = Math.max(st.lastId || 0, message.id || 0);
|
||||
if (!message.senderId) continue;
|
||||
const senderId = message.senderId.toString();
|
||||
if (this._isOwnAccount(senderId)) continue;
|
||||
let username = "";
|
||||
let accessHash = "";
|
||||
try {
|
||||
const sender = await message.getSender();
|
||||
if (sender && sender.bot) continue;
|
||||
username = sender && sender.username ? sender.username : "";
|
||||
accessHash = sender && sender.accessHash ? sender.accessHash.toString() : "";
|
||||
} catch (error) {
|
||||
username = "";
|
||||
accessHash = "";
|
||||
const senderInfo = await this._getUserInfoFromMessage(message);
|
||||
if (!senderInfo) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const { userId: senderId, username, accessHash } = senderInfo;
|
||||
if (this._isOwnAccount(senderId)) continue;
|
||||
let messageDate = new Date();
|
||||
if (message.date instanceof Date) {
|
||||
messageDate = message.date;
|
||||
@ -904,7 +908,22 @@ class TelegramManager {
|
||||
}
|
||||
monitorEntry.lastMessageAt = messageDate.toISOString();
|
||||
monitorEntry.lastSource = st.source;
|
||||
this.store.enqueueInvite(task.id, senderId, username, st.source, accessHash);
|
||||
if (this.store.enqueueInvite(task.id, senderId, username, st.source, accessHash)) {
|
||||
enqueued += 1;
|
||||
}
|
||||
}
|
||||
if (totalMessages > 0 && enqueued === 0 && skipped > 0) {
|
||||
const now = Date.now();
|
||||
const lastSkip = monitorEntry.lastSkipAt.get(key) || 0;
|
||||
if (now - lastSkip > 60000) {
|
||||
monitorEntry.lastSkipAt.set(key, now);
|
||||
this.store.addAccountEvent(
|
||||
monitorAccount.account.id,
|
||||
monitorAccount.account.phone,
|
||||
"monitor_skip",
|
||||
`${st.source}: сообщения есть, но пользователей нет (пропущено: ${skipped})`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const now = Date.now();
|
||||
@ -937,6 +956,8 @@ class TelegramManager {
|
||||
if (task.auto_join_competitors) {
|
||||
await this._autoJoinGroups(entry.client, groups, true, entry.account);
|
||||
}
|
||||
const summaryLines = [];
|
||||
let totalEnqueued = 0;
|
||||
const errors = [];
|
||||
for (const group of groups) {
|
||||
const resolved = await this._resolveGroupEntity(entry.client, group, Boolean(task.auto_join_competitors), entry.account);
|
||||
@ -945,28 +966,59 @@ class TelegramManager {
|
||||
continue;
|
||||
}
|
||||
const messages = await entry.client.getMessages(resolved.entity, { limit: perGroupLimit });
|
||||
let total = 0;
|
||||
let enqueued = 0;
|
||||
let skipped = 0;
|
||||
for (const message of messages) {
|
||||
const senderId = message.senderId;
|
||||
if (!senderId) continue;
|
||||
const senderStr = senderId.toString();
|
||||
if (this._isOwnAccount(senderStr)) continue;
|
||||
let username = "";
|
||||
let accessHash = "";
|
||||
try {
|
||||
const sender = await message.getSender();
|
||||
if (sender && sender.bot) continue;
|
||||
username = sender && sender.username ? sender.username : "";
|
||||
accessHash = sender && sender.accessHash ? sender.accessHash.toString() : "";
|
||||
} catch (error) {
|
||||
username = "";
|
||||
accessHash = "";
|
||||
total += 1;
|
||||
const senderInfo = await this._getUserInfoFromMessage(message);
|
||||
if (!senderInfo) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
this.store.enqueueInvite(task.id, senderStr, username, group, accessHash);
|
||||
const { userId: senderId, username, accessHash } = senderInfo;
|
||||
if (this._isOwnAccount(senderId)) continue;
|
||||
if (this.store.enqueueInvite(task.id, senderId, username, group, accessHash)) {
|
||||
enqueued += 1;
|
||||
totalEnqueued += 1;
|
||||
}
|
||||
}
|
||||
summaryLines.push(`${group}: сообщений ${total}, добавлено ${enqueued}, пропущено ${skipped}`);
|
||||
}
|
||||
if (summaryLines.length) {
|
||||
this.store.addAccountEvent(
|
||||
entry.account.id,
|
||||
entry.account.phone,
|
||||
"history_summary",
|
||||
summaryLines.join(" | ")
|
||||
);
|
||||
}
|
||||
if (totalEnqueued === 0 && errors.length === 0) {
|
||||
this.store.addAccountEvent(
|
||||
entry.account.id,
|
||||
entry.account.phone,
|
||||
"history_empty",
|
||||
"История собрана, но пользователей для очереди нет"
|
||||
);
|
||||
}
|
||||
return { ok: true, errors };
|
||||
}
|
||||
|
||||
async _getUserInfoFromMessage(message) {
|
||||
try {
|
||||
const sender = await message.getSender();
|
||||
if (!sender || sender.className !== "User") return null;
|
||||
if (sender.bot) return null;
|
||||
const userId = sender.id != null ? sender.id.toString() : "";
|
||||
if (!userId) return null;
|
||||
const username = sender.username ? sender.username : "";
|
||||
const accessHash = sender.accessHash ? sender.accessHash.toString() : "";
|
||||
return { userId, username, accessHash };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
stopTaskMonitor(taskId) {
|
||||
const entry = this.taskMonitors.get(taskId);
|
||||
if (!entry) return;
|
||||
|
||||
@ -794,6 +794,37 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const clearDatabase = async () => {
|
||||
if (!window.api) {
|
||||
showNotification("Electron API недоступен. Откройте приложение в Electron.", "error");
|
||||
return;
|
||||
}
|
||||
if (!window.confirm("Удалить все данные из базы? Это действие нельзя отменить.")) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await window.api.clearDatabase();
|
||||
showNotification("База очищена.", "info");
|
||||
setSelectedTaskId(null);
|
||||
setTaskForm(emptyTaskForm);
|
||||
setCompetitorText("");
|
||||
setSelectedAccountIds([]);
|
||||
setLogs([]);
|
||||
setInvites([]);
|
||||
setTaskStatus({
|
||||
running: false,
|
||||
queueCount: 0,
|
||||
dailyRemaining: 0,
|
||||
dailyUsed: 0,
|
||||
dailyLimit: 0,
|
||||
monitorInfo: { monitoring: false, groups: [], lastMessageAt: "", lastSource: "" }
|
||||
});
|
||||
await loadBase();
|
||||
} catch (error) {
|
||||
showNotification(error.message || String(error), "error");
|
||||
}
|
||||
};
|
||||
|
||||
const toggleAccountSelection = (accountId) => {
|
||||
setSelectedAccountIds((prev) => {
|
||||
if (prev.includes(accountId)) {
|
||||
@ -998,6 +1029,9 @@ export default function App() {
|
||||
Остановить все
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="danger" onClick={clearDatabase}>
|
||||
Очистить БД
|
||||
</button>
|
||||
<div className="notification-bell" ref={bellRef}>
|
||||
<button
|
||||
type="button"
|
||||
@ -1079,6 +1113,7 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="top-row">
|
||||
<section className="card task-accounts">
|
||||
<div className="row-header">
|
||||
<h2>Аккаунты задачи</h2>
|
||||
@ -1152,7 +1187,6 @@ export default function App() {
|
||||
<h3>Импорт из tdata</h3>
|
||||
<div className="hint">
|
||||
Можно выбрать сразу несколько папок. Значения по умолчанию — API Telegram Desktop.
|
||||
{hasSelectedTask ? ` Импорт в задачу: ${selectedTaskName}.` : " Выберите задачу для импорта."}
|
||||
</div>
|
||||
<div className="row">
|
||||
<label>
|
||||
@ -1223,6 +1257,7 @@ export default function App() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{notification && (
|
||||
<div className={`notice ${notification.tone}`}>
|
||||
|
||||
@ -368,6 +368,13 @@ body {
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -898,6 +905,10 @@ button:disabled {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sticky {
|
||||
position: static;
|
||||
height: auto;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user