stella-coder 5.3.1 → 5.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMMANDS.md +47 -0
- package/install-av.bat +17 -0
- package/install-stella-pkg.bat +21 -0
- package/oneline-av.ps1 +7 -0
- package/oneline-stella.ps1 +2 -0
- package/package.json +1 -1
- package/releases/stella-antivirus/database.mjs +871 -0
- package/releases/stella-antivirus/index.mjs +8 -0
- package/releases/stella-antivirus/install-av.bat +12 -0
- package/releases/stella-antivirus/scanner.mjs +591 -0
- package/releases/stella-antivirus/ui.mjs +570 -0
- package/releases/stella-antivirus.zip +0 -0
- package/releases/stella-coder/README.md +67 -0
- package/releases/stella-coder/adb.mjs +200 -0
- package/releases/stella-coder/autonomous-agent.mjs +550 -0
- package/releases/stella-coder/banner.mjs +46 -0
- package/releases/stella-coder/browser-control.mjs +274 -0
- package/releases/stella-coder/build.mjs +151 -0
- package/releases/stella-coder/charts.mjs +411 -0
- package/releases/stella-coder/coding-brain.mjs +753 -0
- package/releases/stella-coder/game-engine.mjs +708 -0
- package/releases/stella-coder/gdrive-backup.mjs +338 -0
- package/releases/stella-coder/git-api.mjs +407 -0
- package/releases/stella-coder/gmail.mjs +415 -0
- package/releases/stella-coder/home-assistant.mjs +168 -0
- package/releases/stella-coder/index.mjs +5810 -0
- package/releases/stella-coder/install-stella.bat +12 -0
- package/releases/stella-coder/markdown.mjs +100 -0
- package/releases/stella-coder/mcp.mjs +296 -0
- package/releases/stella-coder/package.json +67 -0
- package/releases/stella-coder/presentations.mjs +1106 -0
- package/releases/stella-coder/protect.mjs +182 -0
- package/releases/stella-coder/screen-monitor.mjs +334 -0
- package/releases/stella-coder/sea-config.json +5 -0
- package/releases/stella-coder/security.mjs +237 -0
- package/releases/stella-coder/subagents.mjs +142 -0
- package/releases/stella-coder/telegram-bot.mjs +824 -0
- package/releases/stella-coder/tg-server.mjs +116 -0
- package/releases/stella-coder/theme.mjs +91 -0
- package/releases/stella-coder/tools.mjs +3143 -0
- package/releases/stella-coder/web-parser.mjs +229 -0
- package/releases/stella-coder/yandex-maps.mjs +426 -0
- package/releases/stella-coder.zip +0 -0
- package/run-antivirus.bat +4 -0
- package/setup-antivirus.bat +64 -0
- package/setup-full.bat +65 -0
- package/setup-stella.bat +46 -0
- package/stella-cli/index.mjs +11 -1
- package/stella-cli/protect.mjs +182 -0
- package/stella.bat +3 -0
- package/tests/test-modules.mjs +101 -0
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import os from "node:os"
|
|
4
|
+
import crypto from "node:crypto"
|
|
5
|
+
import { execSync } from "node:child_process"
|
|
6
|
+
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
8
|
+
// STELLA TELEGRAM BOT — удалённое управление + сессии + premium
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
const BOT_TOKEN = "8923551485:AAFw4wG8ZwOtp5rzFsnguxhu4AH-2_ebSi0"
|
|
12
|
+
const API_URL = `https://api.telegram.org/bot${BOT_TOKEN}`
|
|
13
|
+
const PREMIUM_CODE = "10102013"
|
|
14
|
+
const PREMIUM_DURATION = 30 * 24 * 60 * 60 * 1000 // 30 дней
|
|
15
|
+
|
|
16
|
+
const CONFIG_DIR = path.join(os.homedir(), ".stella")
|
|
17
|
+
const BOT_CONFIG_PATH = path.join(CONFIG_DIR, "telegram-bot.json")
|
|
18
|
+
const SESSIONS_PATH = path.join(CONFIG_DIR, "sessions.json")
|
|
19
|
+
const AUTH_PATH = path.join(CONFIG_DIR, "telegram-auth.json")
|
|
20
|
+
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
22
|
+
// 1. TELEGRAM API — прямые вызовы без зависимостей
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
async function tg(method, body = {}) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`${API_URL}/${method}`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: { "Content-Type": "application/json" },
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
signal: AbortSignal.timeout(30000),
|
|
32
|
+
})
|
|
33
|
+
const data = await res.json()
|
|
34
|
+
if (!data.ok) console.error(`[TG] ${method} error:`, data.description)
|
|
35
|
+
return data
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(`[TG] ${method} fetch error:`, e.message)
|
|
38
|
+
return { ok: false, error: e.message }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function sendMessage(chatId, text, options = {}) {
|
|
43
|
+
return tg("sendMessage", {
|
|
44
|
+
chat_id: chatId,
|
|
45
|
+
text,
|
|
46
|
+
parse_mode: "HTML",
|
|
47
|
+
...options,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function editMessage(chatId, messageId, text, options = {}) {
|
|
52
|
+
return tg("editMessageText", {
|
|
53
|
+
chat_id: chatId,
|
|
54
|
+
message_id: messageId,
|
|
55
|
+
text,
|
|
56
|
+
parse_mode: "HTML",
|
|
57
|
+
...options,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function answerCallback(callbackQueryId, text = "") {
|
|
62
|
+
return tg("answerCallbackQuery", { callback_query_id: callbackQueryId, text })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
66
|
+
// 2. HARDWARE-BOUND AUTH — привязка к железу
|
|
67
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
68
|
+
|
|
69
|
+
function getHardwareFingerprint() {
|
|
70
|
+
const components = [
|
|
71
|
+
os.hostname(),
|
|
72
|
+
os.platform(),
|
|
73
|
+
os.arch(),
|
|
74
|
+
os.cpus()[0]?.model || "unknown",
|
|
75
|
+
os.totalmem().toString(),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
// Try to get MAC address
|
|
79
|
+
try {
|
|
80
|
+
const netInterfaces = os.networkInterfaces()
|
|
81
|
+
for (const name of Object.keys(netInterfaces)) {
|
|
82
|
+
for (const iface of netInterfaces[name]) {
|
|
83
|
+
if (!iface.internal && iface.mac && iface.mac !== "00:00:00:00:00:00") {
|
|
84
|
+
components.push(iface.mac)
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
|
|
91
|
+
// Try to get disk serial (using PowerShell instead of wmic)
|
|
92
|
+
try {
|
|
93
|
+
if (os.platform() === "win32") {
|
|
94
|
+
const diskInfo = execSync('powershell -Command "Get-PhysicalDisk | Select-Object -ExpandProperty SerialNumber"', { encoding: "utf8", timeout: 5000 })
|
|
95
|
+
const serial = diskInfo.trim().split("\n")[0]?.trim()
|
|
96
|
+
if (serial && serial !== "") components.push(serial)
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
99
|
+
|
|
100
|
+
return crypto.createHash("sha256").update(components.join("|")).digest("hex").slice(0, 32)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function generateAuthCode(telegramId) {
|
|
104
|
+
// Random 4-разрядный код для каждого пользователя
|
|
105
|
+
const code = String(Math.floor(1000 + Math.random() * 9000))
|
|
106
|
+
return code
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
110
|
+
// 10. ADMIN CODE — каждый Stella генерирует свой код на своём ПК
|
|
111
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
112
|
+
|
|
113
|
+
export function generateAdminCode() {
|
|
114
|
+
const code = String(Math.floor(1000 + Math.random() * 9000))
|
|
115
|
+
const auth = loadAuth()
|
|
116
|
+
|
|
117
|
+
// Store admin code with hardware fingerprint of THIS computer
|
|
118
|
+
auth.adminCode = {
|
|
119
|
+
code,
|
|
120
|
+
hardwareId: getHardwareFingerprint(),
|
|
121
|
+
hostname: os.hostname(),
|
|
122
|
+
createdAt: Date.now(),
|
|
123
|
+
used: false,
|
|
124
|
+
}
|
|
125
|
+
saveAuth(auth)
|
|
126
|
+
|
|
127
|
+
return code
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function verifyAdminCode(code, telegramId, username, firstName, chatId) {
|
|
131
|
+
const auth = loadAuth()
|
|
132
|
+
|
|
133
|
+
if (!auth.adminCode) {
|
|
134
|
+
return { success: false, error: "Код не сгенерирован. Запустите /tg в Stella на вашем компьютере." }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (auth.adminCode.used) {
|
|
138
|
+
return { success: false, error: "Код уже использован. Сгенерируйте новый: /tg-code" }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (auth.adminCode.code !== code) {
|
|
142
|
+
return { success: false, error: "Неверный код." }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Link user to THIS computer
|
|
146
|
+
auth.users[telegramId] = {
|
|
147
|
+
username,
|
|
148
|
+
firstName,
|
|
149
|
+
chatId,
|
|
150
|
+
hardwareId: auth.adminCode.hardwareId,
|
|
151
|
+
hostname: auth.adminCode.hostname,
|
|
152
|
+
authenticatedAt: Date.now(),
|
|
153
|
+
role: "user",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Mark code as used
|
|
157
|
+
auth.adminCode.used = true
|
|
158
|
+
auth.adminCode.usedBy = telegramId
|
|
159
|
+
auth.adminCode.usedAt = Date.now()
|
|
160
|
+
|
|
161
|
+
saveAuth(auth)
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
success: true,
|
|
165
|
+
userId: telegramId,
|
|
166
|
+
username,
|
|
167
|
+
firstName,
|
|
168
|
+
hostname: auth.adminCode.hostname,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function listAuthorizedUsers() {
|
|
173
|
+
const auth = loadAuth()
|
|
174
|
+
return Object.entries(auth.users).map(([id, u]) => ({
|
|
175
|
+
id,
|
|
176
|
+
username: u.username,
|
|
177
|
+
firstName: u.firstName,
|
|
178
|
+
hostname: u.hostname,
|
|
179
|
+
authenticatedAt: new Date(u.authenticatedAt).toLocaleString("ru-RU"),
|
|
180
|
+
}))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function loadAuth() {
|
|
184
|
+
try {
|
|
185
|
+
return JSON.parse(fs.readFileSync(AUTH_PATH, "utf8"))
|
|
186
|
+
} catch {
|
|
187
|
+
return { users: {}, pendingCodes: {} }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function saveAuth(auth) {
|
|
192
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
193
|
+
fs.writeFileSync(AUTH_PATH, JSON.stringify(auth, null, 2))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
197
|
+
// 3. SESSION TRACKER — отслеживание сессий
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
199
|
+
|
|
200
|
+
function loadSessions() {
|
|
201
|
+
try {
|
|
202
|
+
return JSON.parse(fs.readFileSync(SESSIONS_PATH, "utf8"))
|
|
203
|
+
} catch {
|
|
204
|
+
return { sessions: [], activeSession: null }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function saveSessions(data) {
|
|
209
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
210
|
+
fs.writeFileSync(SESSIONS_PATH, JSON.stringify(data, null, 2))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function createSession(name, model = "MiMo V2.5 Free") {
|
|
214
|
+
const data = loadSessions()
|
|
215
|
+
const session = {
|
|
216
|
+
id: `session-${Date.now()}`,
|
|
217
|
+
name,
|
|
218
|
+
model,
|
|
219
|
+
provider: "OpenCode Zen",
|
|
220
|
+
contextLimit: "200K",
|
|
221
|
+
createdAt: new Date().toISOString(),
|
|
222
|
+
lastActivity: new Date().toISOString(),
|
|
223
|
+
stats: {
|
|
224
|
+
totalTokens: 0,
|
|
225
|
+
inputTokens: 0,
|
|
226
|
+
outputTokens: 0,
|
|
227
|
+
cacheRead: 0,
|
|
228
|
+
cacheWrite: 0,
|
|
229
|
+
reasoningTokens: 0,
|
|
230
|
+
userMessages: 0,
|
|
231
|
+
assistantMessages: 0,
|
|
232
|
+
toolCalls: 0,
|
|
233
|
+
cost: 0,
|
|
234
|
+
contextBreakdown: {
|
|
235
|
+
user: 0,
|
|
236
|
+
assistant: 0,
|
|
237
|
+
tools: 0,
|
|
238
|
+
other: 0,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
data.sessions.push(session)
|
|
243
|
+
data.activeSession = session.id
|
|
244
|
+
saveSessions(data)
|
|
245
|
+
return session
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function updateSessionStats(sessionId, updates) {
|
|
249
|
+
const data = loadSessions()
|
|
250
|
+
const session = data.sessions.find(s => s.id === sessionId)
|
|
251
|
+
if (!session) return null
|
|
252
|
+
|
|
253
|
+
Object.assign(session.stats, updates)
|
|
254
|
+
session.lastActivity = new Date().toISOString()
|
|
255
|
+
saveSessions(data)
|
|
256
|
+
return session
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getActiveSession() {
|
|
260
|
+
const data = loadSessions()
|
|
261
|
+
return data.sessions.find(s => s.id === data.activeSession) || null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
265
|
+
// 4. PREMIUM SYSTEM — подписка
|
|
266
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
267
|
+
|
|
268
|
+
function loadPremium() {
|
|
269
|
+
try {
|
|
270
|
+
return JSON.parse(fs.readFileSync(path.join(CONFIG_DIR, "premium.json"), "utf8"))
|
|
271
|
+
} catch {
|
|
272
|
+
return { users: {} }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function savePremium(data) {
|
|
277
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
278
|
+
fs.writeFileSync(path.join(CONFIG_DIR, "premium.json"), JSON.stringify(data, null, 2))
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isPremium(telegramId) {
|
|
282
|
+
const data = loadPremium()
|
|
283
|
+
const user = data.users[telegramId]
|
|
284
|
+
if (!user) return false
|
|
285
|
+
return Date.now() < user.expiresAt
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function activatePremium(telegramId, duration = PREMIUM_DURATION) {
|
|
289
|
+
const data = loadPremium()
|
|
290
|
+
data.users[telegramId] = {
|
|
291
|
+
activatedAt: Date.now(),
|
|
292
|
+
expiresAt: Date.now() + duration,
|
|
293
|
+
}
|
|
294
|
+
savePremium(data)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function getPremiumExpiry(telegramId) {
|
|
298
|
+
const data = loadPremium()
|
|
299
|
+
const user = data.users[telegramId]
|
|
300
|
+
if (!user) return null
|
|
301
|
+
return new Date(user.expiresAt).toLocaleDateString("ru-RU")
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
305
|
+
// 5. BOT HANDLERS — обработка команд
|
|
306
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
307
|
+
|
|
308
|
+
const userStates = new Map()
|
|
309
|
+
|
|
310
|
+
function getMainMenu(telegramId) {
|
|
311
|
+
const premium = isPremium(telegramId)
|
|
312
|
+
return {
|
|
313
|
+
text: `<b>Stella Coder Bot</b>
|
|
314
|
+
|
|
315
|
+
Добро пожаловать! ${premium ? "⭐ Premium" : ""}
|
|
316
|
+
|
|
317
|
+
<b>Команды:</b>
|
|
318
|
+
/start — Начать
|
|
319
|
+
/sessions — Мои сессии
|
|
320
|
+
/new — Новая сессия
|
|
321
|
+
/status — Статус сессии
|
|
322
|
+
/premium — Premium функции
|
|
323
|
+
/help — Помощь
|
|
324
|
+
|
|
325
|
+
<b>Удалённое управление:</b>
|
|
326
|
+
Просто напишите команду, и Stella выполнит её на компьютере.
|
|
327
|
+
Пример: "выключи телевизор", "сделай скриншот"`,
|
|
328
|
+
reply_markup: {
|
|
329
|
+
inline_keyboard: [
|
|
330
|
+
[{ text: "📋 Сессии", callback_data: "sessions" }, { text: "➕ Новая", callback_data: "new_session" }],
|
|
331
|
+
[{ text: "📊 Статус", callback_data: "status" }, { text: "⭐ Premium", callback_data: "premium" }],
|
|
332
|
+
[{ text: "❓ Помощь", callback_data: "help" }],
|
|
333
|
+
],
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function getSessionsList(telegramId) {
|
|
339
|
+
const data = loadSessions()
|
|
340
|
+
if (data.sessions.length === 0) {
|
|
341
|
+
return {
|
|
342
|
+
text: "📋 <b>Нет сохранённых сессий</b>\n\nСоздайте новую сессию кнопкой ниже.",
|
|
343
|
+
reply_markup: {
|
|
344
|
+
inline_keyboard: [[{ text: "➕ Создать сессию", callback_data: "new_session" }], [{ text: "◀️ Назад", callback_data: "main_menu" }]],
|
|
345
|
+
},
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const lines = data.sessions.map((s, i) => {
|
|
350
|
+
const isActive = s.id === data.activeSession
|
|
351
|
+
const date = new Date(s.createdAt).toLocaleDateString("ru-RU")
|
|
352
|
+
const msgs = s.stats.userMessages + s.stats.assistantMessages
|
|
353
|
+
return `${isActive ? "🟢" : "⚪"} <b>${i + 1}. ${s.name}</b>\n ${s.model} · ${msgs} сообщений · ${date}`
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
text: `<b>📋 Ваши сессии:</b>\n\n${lines.join("\n\n")}`,
|
|
358
|
+
reply_markup: {
|
|
359
|
+
inline_keyboard: [
|
|
360
|
+
...data.sessions.map((s, i) => [
|
|
361
|
+
{ text: `${s.id === data.activeSession ? "🟢" : "⚪"} ${s.name}`, callback_data: `session_${i}` },
|
|
362
|
+
]),
|
|
363
|
+
[{ text: "➕ Новая сессия", callback_data: "new_session" }],
|
|
364
|
+
[{ text: "◀️ Назад", callback_data: "main_menu" }],
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function getSessionStats(session) {
|
|
371
|
+
const stats = session.stats
|
|
372
|
+
const ctxBreakdown = stats.contextBreakdown
|
|
373
|
+
const totalCtx = ctxBreakdown.user + ctxBreakdown.assistant + ctxBreakdown.tools + ctxBreakdown.other
|
|
374
|
+
|
|
375
|
+
const userPct = totalCtx > 0 ? ((ctxBreakdown.user / totalCtx) * 100).toFixed(1) : "0"
|
|
376
|
+
const asstPct = totalCtx > 0 ? ((ctxBreakdown.assistant / totalCtx) * 100).toFixed(1) : "0"
|
|
377
|
+
const toolsPct = totalCtx > 0 ? ((ctxBreakdown.tools / totalCtx) * 100).toFixed(1) : "0"
|
|
378
|
+
const otherPct = totalCtx > 0 ? ((ctxBreakdown.other / totalCtx) * 100).toFixed(1) : "0"
|
|
379
|
+
|
|
380
|
+
const usage = session.contextLimit ? Math.min(100, Math.round((stats.totalTokens / 200000) * 100)) : 0
|
|
381
|
+
|
|
382
|
+
return `<b>📊 Сессия: ${session.name}</b>
|
|
383
|
+
|
|
384
|
+
<b>Провайдер:</b> ${session.provider}
|
|
385
|
+
<b>Лимит контекста:</b> ${session.contextLimit}
|
|
386
|
+
<b>Использование:</b> ${usage}%
|
|
387
|
+
<b>Модель:</b> ${session.model}
|
|
388
|
+
|
|
389
|
+
<b>Статистика:</b>
|
|
390
|
+
├ Входные токены: ${stats.inputTokens.toLocaleString()}
|
|
391
|
+
├ Выходные токены: ${stats.outputTokens.toLocaleString()}
|
|
392
|
+
├ Токены кэша: ${stats.cacheRead.toLocaleString()}/${stats.cacheWrite.toLocaleString()}
|
|
393
|
+
├ Токены рассуждения: ${stats.reasoningTokens.toLocaleString()}
|
|
394
|
+
└ Всего токенов: ${stats.totalTokens.toLocaleString()}
|
|
395
|
+
|
|
396
|
+
<b>Сообщения:</b>
|
|
397
|
+
├ Пользователя: ${stats.userMessages}
|
|
398
|
+
├ Ассистента: ${stats.assistantMessages}
|
|
399
|
+
├ Всего: ${stats.userMessages + stats.assistantMessages}
|
|
400
|
+
└ Вызовы инструментов: ${stats.toolCalls}
|
|
401
|
+
|
|
402
|
+
<b>Разбивка контекста:</b>
|
|
403
|
+
├ 🟢 Пользователь ${userPct}%
|
|
404
|
+
├ 🟣 Ассистент ${asstPct}%
|
|
405
|
+
├ 🟤 Вызовы инструментов ${toolsPct}%
|
|
406
|
+
└ ⚪ Другое ${otherPct}%
|
|
407
|
+
|
|
408
|
+
<b>Стоимость:</b> $${stats.cost.toFixed(2)}
|
|
409
|
+
<b>Создана:</b> ${new Date(session.createdAt).toLocaleString("ru-RU")}
|
|
410
|
+
<b>Последняя активность:</b> ${new Date(session.lastActivity).toLocaleString("ru-RU")}`
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
414
|
+
// 6. WEBHOOK / POLLING — запуск бота
|
|
415
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
416
|
+
|
|
417
|
+
let pollingOffset = 0
|
|
418
|
+
let botRunning = false
|
|
419
|
+
|
|
420
|
+
export async function startBot() {
|
|
421
|
+
if (botRunning) {
|
|
422
|
+
console.log("[TG Bot] Already running")
|
|
423
|
+
return
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Check bot token
|
|
427
|
+
const me = await tg("getMe")
|
|
428
|
+
if (!me.ok) {
|
|
429
|
+
console.error("[TG Bot] Failed to start:", me.description || "Invalid token")
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
botRunning = true
|
|
434
|
+
console.log(`[TG Bot] Started: @${me.result.username} (${me.result.first_name})`)
|
|
435
|
+
|
|
436
|
+
// Start polling
|
|
437
|
+
pollUpdates()
|
|
438
|
+
return true
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export async function stopBot() {
|
|
442
|
+
botRunning = false
|
|
443
|
+
console.log("[TG Bot] Stopped")
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function pollUpdates() {
|
|
447
|
+
while (botRunning) {
|
|
448
|
+
try {
|
|
449
|
+
const res = await tg("getUpdates", {
|
|
450
|
+
offset: pollingOffset,
|
|
451
|
+
timeout: 30,
|
|
452
|
+
allowed_updates: ["message", "callback_query"],
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
if (res.ok && res.result) {
|
|
456
|
+
for (const update of res.result) {
|
|
457
|
+
pollingOffset = update.update_id + 1
|
|
458
|
+
await handleUpdate(update)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} catch (e) {
|
|
462
|
+
if (botRunning) {
|
|
463
|
+
console.error("[TG Bot] Poll error:", e.message)
|
|
464
|
+
await sleep(5000)
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function sleep(ms) {
|
|
471
|
+
return new Promise(r => setTimeout(r, ms))
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function handleUpdate(update) {
|
|
475
|
+
if (update.message) {
|
|
476
|
+
await handleMessage(update.message)
|
|
477
|
+
} else if (update.callback_query) {
|
|
478
|
+
await handleCallback(update.callback_query)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function handleMessage(msg) {
|
|
483
|
+
const chatId = msg.chat.id
|
|
484
|
+
const userId = msg.from?.id
|
|
485
|
+
const text = msg.text || ""
|
|
486
|
+
|
|
487
|
+
// /start or /menu
|
|
488
|
+
if (text === "/start" || text === "/menu") {
|
|
489
|
+
const auth = loadAuth()
|
|
490
|
+
|
|
491
|
+
// Check if already authenticated
|
|
492
|
+
if (auth.users[userId]) {
|
|
493
|
+
await sendMessage(chatId, getMainMenu(userId).text, getMainMenu(userId))
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Not authenticated - check for admin code
|
|
498
|
+
if (text === "/start") {
|
|
499
|
+
// Check if there's an active admin code
|
|
500
|
+
if (auth.adminCode && !auth.adminCode.used) {
|
|
501
|
+
await sendMessage(chatId, `<b>Добро пожаловать, ${msg.from.first_name}!</b>
|
|
502
|
+
|
|
503
|
+
Для доступа к Stella введите код, который вам дали.
|
|
504
|
+
|
|
505
|
+
<b>Введите код:</b>`, {
|
|
506
|
+
reply_markup: {
|
|
507
|
+
inline_keyboard: [[{ text: "◀️ Отмена", callback_data: "cancel_auth" }]],
|
|
508
|
+
},
|
|
509
|
+
})
|
|
510
|
+
userStates.set(userId, { awaiting: "admin_code" })
|
|
511
|
+
} else {
|
|
512
|
+
await sendMessage(chatId, `<b>Добро пожаловать, ${msg.from.first_name}!</b>
|
|
513
|
+
|
|
514
|
+
Для получения доступа к Stella:
|
|
515
|
+
1. Купите доступ у администратора
|
|
516
|
+
2. Получите код
|
|
517
|
+
3. Введите его здесь
|
|
518
|
+
|
|
519
|
+
<b>Нет кода?</b> Свяжитесь с администратором.`, {
|
|
520
|
+
reply_markup: {
|
|
521
|
+
inline_keyboard: [[{ text: "◀️ Назад", callback_data: "main_menu" }]],
|
|
522
|
+
},
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Check user state for admin code input
|
|
530
|
+
const userState = userStates.get(userId)
|
|
531
|
+
if (userState?.awaiting === "admin_code") {
|
|
532
|
+
userStates.delete(userId)
|
|
533
|
+
|
|
534
|
+
const auth = loadAuth()
|
|
535
|
+
const result = verifyAdminCode(
|
|
536
|
+
text.trim(),
|
|
537
|
+
userId,
|
|
538
|
+
msg.from.username || msg.from.first_name,
|
|
539
|
+
msg.from.first_name,
|
|
540
|
+
chatId
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
if (result.success) {
|
|
544
|
+
await sendMessage(chatId, `✅ <b>Доступ получен!</b>
|
|
545
|
+
|
|
546
|
+
Добро пожаловать, ${result.firstName}!
|
|
547
|
+
|
|
548
|
+
Теперь вы можете:
|
|
549
|
+
• Управлять компьютером через Telegram
|
|
550
|
+
• Получать уведомления
|
|
551
|
+
• Просматривать сессии
|
|
552
|
+
|
|
553
|
+
Нажмите /menu для главного меню.`, getMainMenu(userId))
|
|
554
|
+
} else {
|
|
555
|
+
await sendMessage(chatId, `❌ <b>${result.error}</b>
|
|
556
|
+
|
|
557
|
+
Попробуйте ещё раз или нажмите /start.`)
|
|
558
|
+
}
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Check if user is authenticated
|
|
563
|
+
if (!auth.users[userId]) {
|
|
564
|
+
await sendMessage(chatId, "❌ Вы не авторизованы. Нажмите /start для начала.")
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Authenticated user — process as Stella command
|
|
569
|
+
const session = getActiveSession()
|
|
570
|
+
if (session) {
|
|
571
|
+
// Update session stats
|
|
572
|
+
updateSessionStats(session.id, {
|
|
573
|
+
userMessages: session.stats.userMessages + 1,
|
|
574
|
+
totalTokens: session.stats.totalTokens + Math.floor(text.length / 4),
|
|
575
|
+
inputTokens: session.stats.inputTokens + Math.floor(text.length / 4),
|
|
576
|
+
})
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Send "processing" message
|
|
580
|
+
const processingMsg = await sendMessage(chatId, `⚙️ <b>Выполняю:</b> ${text.slice(0, 100)}...`)
|
|
581
|
+
|
|
582
|
+
// Execute command via Stella
|
|
583
|
+
try {
|
|
584
|
+
const result = execSync(`node "${path.join(process.cwd(), "stella-cli", "index.mjs")}" -p "${text.replace(/"/g, '\\"')}"`, {
|
|
585
|
+
encoding: "utf8",
|
|
586
|
+
timeout: 120000,
|
|
587
|
+
cwd: process.cwd(),
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
// Update stats
|
|
591
|
+
if (session) {
|
|
592
|
+
updateSessionStats(session.id, {
|
|
593
|
+
assistantMessages: session.stats.assistantMessages + 1,
|
|
594
|
+
outputTokens: session.stats.outputTokens + Math.floor(result.length / 4),
|
|
595
|
+
totalTokens: session.stats.totalTokens + Math.floor(result.length / 4),
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Send result
|
|
600
|
+
const output = result.trim().slice(0, 4000) // Telegram limit
|
|
601
|
+
await editMessage(chatId, processingMsg.result.message_id, `<b>✅ Результат:</b>\n\n<pre>${escapeHtml(output)}</pre>`)
|
|
602
|
+
} catch (e) {
|
|
603
|
+
await editMessage(chatId, processingMsg.result.message_id, `<b>❌ Ошибка:</b>\n<pre>${escapeHtml(String(e.message).slice(0, 1000))}</pre>`)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function escapeHtml(text) {
|
|
608
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function handleCallback(callback) {
|
|
612
|
+
const chatId = callback.message.chat.id
|
|
613
|
+
const userId = callback.from.id
|
|
614
|
+
const data = callback.data
|
|
615
|
+
|
|
616
|
+
await answerCallback(callback.id)
|
|
617
|
+
|
|
618
|
+
// Handle cancel_auth
|
|
619
|
+
if (data === "cancel_auth") {
|
|
620
|
+
const auth = loadAuth()
|
|
621
|
+
delete auth.pendingCodes[userId]
|
|
622
|
+
saveAuth(auth)
|
|
623
|
+
await editMessage(chatId, callback.message.message_id, "❌ Авторизация отменена.\n\nНажмите /start для начала.")
|
|
624
|
+
return
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Check auth
|
|
628
|
+
const auth = loadAuth()
|
|
629
|
+
if (!auth.users[userId]) {
|
|
630
|
+
await sendMessage(chatId, "❌ Нажмите /start для авторизации.")
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
switch (data) {
|
|
635
|
+
case "main_menu":
|
|
636
|
+
await editMessage(chatId, callback.message.message_id, getMainMenu(userId).text, getMainMenu(userId))
|
|
637
|
+
break
|
|
638
|
+
|
|
639
|
+
case "sessions":
|
|
640
|
+
await editMessage(chatId, callback.message.message_id, getSessionsList(userId).text, getSessionsList(userId))
|
|
641
|
+
break
|
|
642
|
+
|
|
643
|
+
case "new_session": {
|
|
644
|
+
userStates.set(userId, { awaiting: "session_name" })
|
|
645
|
+
await sendMessage(chatId, "📝 Введите название новой сессии:", {
|
|
646
|
+
reply_markup: { inline_keyboard: [[{ text: "◀️ Отмена", callback_data: "main_menu" }]] },
|
|
647
|
+
})
|
|
648
|
+
break
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
case "status": {
|
|
652
|
+
const session = getActiveSession()
|
|
653
|
+
if (!session) {
|
|
654
|
+
await sendMessage(chatId, "❌ Нет активной сессии. Создайте новую.", {
|
|
655
|
+
reply_markup: { inline_keyboard: [[{ text: "➕ Создать", callback_data: "new_session" }]] },
|
|
656
|
+
})
|
|
657
|
+
return
|
|
658
|
+
}
|
|
659
|
+
await sendMessage(chatId, getSessionStats(session), {
|
|
660
|
+
reply_markup: {
|
|
661
|
+
inline_keyboard: [
|
|
662
|
+
[{ text: "🔄 Обновить", callback_data: "status" }],
|
|
663
|
+
[{ text: "◀️ Назад", callback_data: "main_menu" }],
|
|
664
|
+
],
|
|
665
|
+
},
|
|
666
|
+
})
|
|
667
|
+
break
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
case "premium": {
|
|
671
|
+
const premium = isPremium(userId)
|
|
672
|
+
const expiry = getPremiumExpiry(userId)
|
|
673
|
+
|
|
674
|
+
let text = ""
|
|
675
|
+
if (premium) {
|
|
676
|
+
text = `<b>⭐ Premium активен</b>\n\nДействует до: ${expiry}\n\nДоступные функции:\n• Удалённое управление компьютером\n• Выполнение команд\n• Мониторинг сессий\n• Приоритетная поддержка`
|
|
677
|
+
} else {
|
|
678
|
+
text = `<b>⭐ Premium</b>\n\nФункции Premium стоят 50 звёзд Telegram.\n\nНо вы можете получить бесплатно, зная код!\n\nВведите код: <code>${PREMIUM_CODE}</code>\n(активация на 1 месяц)`
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
await sendMessage(chatId, text, {
|
|
682
|
+
reply_markup: {
|
|
683
|
+
inline_keyboard: premium
|
|
684
|
+
? [[{ text: "◀️ Назад", callback_data: "main_menu" }]]
|
|
685
|
+
: [
|
|
686
|
+
[{ text: "🔑 Активировать код", callback_data: "activate_premium" }],
|
|
687
|
+
[{ text: "◀️ Назад", callback_data: "main_menu" }],
|
|
688
|
+
],
|
|
689
|
+
},
|
|
690
|
+
})
|
|
691
|
+
break
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
case "activate_premium": {
|
|
695
|
+
userStates.set(userId, { awaiting: "premium_code" })
|
|
696
|
+
await sendMessage(chatId, "🔑 Введите код активации:", {
|
|
697
|
+
reply_markup: { inline_keyboard: [[{ text: "◀️ Отмена", callback_data: "premium" }]] },
|
|
698
|
+
})
|
|
699
|
+
break
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
case "help":
|
|
703
|
+
await sendMessage(chatId, `<b>Помощь</b>
|
|
704
|
+
|
|
705
|
+
<b>Команды:</b>
|
|
706
|
+
/start — Меню
|
|
707
|
+
/sessions — Мои сессии
|
|
708
|
+
/new — Новая сессия
|
|
709
|
+
/status — Статус сессии
|
|
710
|
+
/premium — Premium функции
|
|
711
|
+
|
|
712
|
+
<b>Удалённое управление:</b>
|
|
713
|
+
Напишите любую команду, и Stella выполнит её на вашем компьютере.
|
|
714
|
+
|
|
715
|
+
<b>Примеры:</b>
|
|
716
|
+
• "выключи телевизор"
|
|
717
|
+
• "сделай скриншот"
|
|
718
|
+
• "открой браузер"
|
|
719
|
+
• "покажи систему"
|
|
720
|
+
• "обнови код"
|
|
721
|
+
|
|
722
|
+
<b>Код активации Premium:</b>
|
|
723
|
+
<code>${PREMIUM_CODE}</code> (1 месяц бесплатно)`, {
|
|
724
|
+
reply_markup: { inline_keyboard: [[{ text: "◀️ Назад", callback_data: "main_menu" }]] },
|
|
725
|
+
})
|
|
726
|
+
break
|
|
727
|
+
|
|
728
|
+
default:
|
|
729
|
+
// Handle session selection
|
|
730
|
+
if (data.startsWith("session_")) {
|
|
731
|
+
const idx = parseInt(data.replace("session_", ""))
|
|
732
|
+
const sessions = loadSessions()
|
|
733
|
+
if (sessions.sessions[idx]) {
|
|
734
|
+
sessions.activeSession = sessions.sessions[idx].id
|
|
735
|
+
saveSessions(sessions)
|
|
736
|
+
await sendMessage(chatId, getSessionStats(sessions.sessions[idx]), {
|
|
737
|
+
reply_markup: {
|
|
738
|
+
inline_keyboard: [
|
|
739
|
+
[{ text: "🔄 Обновить", callback_data: `session_${idx}` }],
|
|
740
|
+
[{ text: "◀️ К сессиям", callback_data: "sessions" }],
|
|
741
|
+
],
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
break
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
751
|
+
// 7. NOTIFICATIONS — уведомления о выполнении
|
|
752
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
753
|
+
|
|
754
|
+
export async function notifyUser(telegramId, message) {
|
|
755
|
+
const auth = loadAuth()
|
|
756
|
+
if (!auth.users[telegramId]) return false
|
|
757
|
+
|
|
758
|
+
return sendMessage(telegramId, `📬 <b>Уведомление:</b>\n\n${message}`)
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export async function notifyAll(message) {
|
|
762
|
+
const auth = loadAuth()
|
|
763
|
+
for (const userId of Object.keys(auth.users)) {
|
|
764
|
+
await sendMessage(auth.users[userId].chatId, `📬 <b>Уведомление:</b>\n\n${message}`)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
769
|
+
// 8. PUBLIC API — экспорт для Stella CLI
|
|
770
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
771
|
+
|
|
772
|
+
export const TELEGRAM_BRAIN_COMMANDS = {
|
|
773
|
+
"/tg": "запустить Telegram бота",
|
|
774
|
+
"/tg-stop": "остановить Telegram бота",
|
|
775
|
+
"/tg-notify": "отправить уведомление всем",
|
|
776
|
+
"/tg-sessions": "показать сессии в терминале",
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function getBotStatus() {
|
|
780
|
+
return {
|
|
781
|
+
running: botRunning,
|
|
782
|
+
token: BOT_TOKEN.slice(0, 10) + "...",
|
|
783
|
+
auth: loadAuth(),
|
|
784
|
+
sessions: loadSessions(),
|
|
785
|
+
premium: loadPremium(),
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
790
|
+
// 9. CLI AUTH VERIFICATION — проверка кода из терминала
|
|
791
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
792
|
+
|
|
793
|
+
export function verifyAuthCode(code) {
|
|
794
|
+
const auth = loadAuth()
|
|
795
|
+
|
|
796
|
+
// Find pending code that matches
|
|
797
|
+
for (const [userId, pending] of Object.entries(auth.pendingCodes)) {
|
|
798
|
+
if (pending.code === code) {
|
|
799
|
+
// Link the user
|
|
800
|
+
auth.users[userId] = {
|
|
801
|
+
username: pending.username,
|
|
802
|
+
chatId: pending.chatId,
|
|
803
|
+
hardwareId: getHardwareFingerprint(),
|
|
804
|
+
authenticatedAt: Date.now(),
|
|
805
|
+
}
|
|
806
|
+
delete auth.pendingCodes[userId]
|
|
807
|
+
saveAuth(auth)
|
|
808
|
+
|
|
809
|
+
return {
|
|
810
|
+
success: true,
|
|
811
|
+
userId,
|
|
812
|
+
username: pending.username,
|
|
813
|
+
chatId: pending.chatId,
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return { success: false, error: "Код не найден или уже использован" }
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
export function getPendingCodes() {
|
|
822
|
+
const auth = loadAuth()
|
|
823
|
+
return auth.pendingCodes
|
|
824
|
+
}
|