qwen-api-proxy 1.0.11 → 1.0.13
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/README.md +7 -5
- package/bin/qwen-api-proxy.js +64 -53
- package/index.js +106 -20
- package/package.json +8 -2
- package/src/api/chat.js +71 -71
- package/src/api/chatHistory.js +19 -19
- package/src/api/fileUpload.js +23 -23
- package/src/api/imageGeneration.js +23 -23
- package/src/api/modelMapping.js +145 -153
- package/src/api/routes.js +148 -148
- package/src/api/tokenManager.js +93 -93
- package/src/browser/auth.js +13 -13
- package/src/browser/browser.js +9 -9
- package/src/browser/session.js +3 -3
- package/src/config.js +4 -4
- package/src/utils/accountSetup.js +14 -14
- package/src/utils/botSettings.js +14 -14
- package/src/utils/permissionChecker.js +157 -157
- package/src/utils/prompt.js +1 -1
- package/src/utils/proxy.js +11 -11
- package/src/utils/telegramBot.js +656 -654
- package/src/utils/telegramNotifier.js +7 -7
package/src/api/tokenManager.js
CHANGED
|
@@ -13,8 +13,8 @@ const TOKENS_FILE = path.join(SESSION_PATH, 'tokens.json');
|
|
|
13
13
|
let pointer = 0;
|
|
14
14
|
|
|
15
15
|
function ensureSessionDir() {
|
|
16
|
-
if (!fs.existsSync(SESSION_PATH)) fs.mkdirSync(SESSION_PATH, { recursive: true });
|
|
17
|
-
if (!fs.existsSync(ACCOUNTS_PATH)) fs.mkdirSync(ACCOUNTS_PATH, { recursive: true });
|
|
16
|
+
if (!fs.existsSync(SESSION_PATH)) {fs.mkdirSync(SESSION_PATH, { recursive: true });}
|
|
17
|
+
if (!fs.existsSync(ACCOUNTS_PATH)) {fs.mkdirSync(ACCOUNTS_PATH, { recursive: true });}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -34,28 +34,28 @@ export function hasCookies(accountId) {
|
|
|
34
34
|
*/
|
|
35
35
|
function decodeJwtExpiry(token) {
|
|
36
36
|
try {
|
|
37
|
-
if (!token || typeof token !== 'string') return null;
|
|
38
|
-
|
|
37
|
+
if (!token || typeof token !== 'string') {return null;}
|
|
38
|
+
|
|
39
39
|
const parts = token.split('.');
|
|
40
|
-
if (parts.length !== 3) return null;
|
|
41
|
-
|
|
40
|
+
if (parts.length !== 3) {return null;}
|
|
41
|
+
|
|
42
42
|
// Декодируем payload (вторая часть JWT)
|
|
43
43
|
// JWT использует URL-safe base64, нужно заменить - на + и _ на /
|
|
44
44
|
let base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Добавляем padding если нужно
|
|
47
47
|
while (base64.length % 4) {
|
|
48
48
|
base64 += '=';
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
const payload = Buffer.from(base64, 'base64').toString('utf8');
|
|
52
52
|
const decoded = JSON.parse(payload);
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
// JWT использует поле 'exp' для времени истечения (в секундах)
|
|
55
55
|
if (decoded.exp) {
|
|
56
56
|
return decoded.exp * 1000; // Конвертируем в миллисекунды
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
return null;
|
|
60
60
|
} catch (error) {
|
|
61
61
|
// Если не удалось декодировать, возвращаем null
|
|
@@ -65,12 +65,12 @@ function decodeJwtExpiry(token) {
|
|
|
65
65
|
|
|
66
66
|
export function loadTokens() {
|
|
67
67
|
ensureSessionDir();
|
|
68
|
-
if (!fs.existsSync(TOKENS_FILE)) return [];
|
|
68
|
+
if (!fs.existsSync(TOKENS_FILE)) {return [];}
|
|
69
69
|
try {
|
|
70
70
|
const tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8'));
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
// Добавляем expiryTime для каждого токена, если его нет
|
|
73
|
-
return tokens.map(token => {
|
|
73
|
+
return tokens.map((token) => {
|
|
74
74
|
if (!token.expiryTime && token.token) {
|
|
75
75
|
token.expiryTime = decodeJwtExpiry(token.token);
|
|
76
76
|
}
|
|
@@ -99,25 +99,25 @@ export function saveTokens(tokens) {
|
|
|
99
99
|
export async function getAvailableToken() {
|
|
100
100
|
const tokens = loadTokens();
|
|
101
101
|
const now = Date.now();
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
// Фильтруем токены: не rate-limited, не invalid, JWT не истёк, и есть cookies
|
|
104
|
-
const valid = tokens.filter(t => {
|
|
104
|
+
const valid = tokens.filter((t) => {
|
|
105
105
|
// Пропускаем недействительные токены
|
|
106
|
-
if (t.invalid) return false;
|
|
107
|
-
|
|
106
|
+
if (t.invalid) {return false;}
|
|
107
|
+
|
|
108
108
|
// Пропускаем токены с rate limit в будущем
|
|
109
|
-
if (t.resetAt && new Date(t.resetAt).getTime() > now) return false;
|
|
110
|
-
|
|
109
|
+
if (t.resetAt && new Date(t.resetAt).getTime() > now) {return false;}
|
|
110
|
+
|
|
111
111
|
// Пропускаем токены с истёкшим JWT
|
|
112
|
-
if (t.expiryTime && t.expiryTime <= now) return false;
|
|
113
|
-
|
|
112
|
+
if (t.expiryTime && t.expiryTime <= now) {return false;}
|
|
113
|
+
|
|
114
114
|
// Пропускаем токены без cookies.json
|
|
115
|
-
if (!hasCookies(t.id)) return false;
|
|
116
|
-
|
|
115
|
+
if (!hasCookies(t.id)) {return false;}
|
|
116
|
+
|
|
117
117
|
return true;
|
|
118
118
|
});
|
|
119
|
-
|
|
120
|
-
if (!valid.length) return null;
|
|
119
|
+
|
|
120
|
+
if (!valid.length) {return null;}
|
|
121
121
|
const token = valid[pointer % valid.length];
|
|
122
122
|
pointer = (pointer + 1) % valid.length;
|
|
123
123
|
return token;
|
|
@@ -126,28 +126,28 @@ export async function getAvailableToken() {
|
|
|
126
126
|
export function hasValidTokens() {
|
|
127
127
|
const tokens = loadTokens();
|
|
128
128
|
const now = Date.now();
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
// Проверяем, есть ли хотя бы один валидный токен с cookies
|
|
131
|
-
return tokens.some(t => {
|
|
131
|
+
return tokens.some((t) => {
|
|
132
132
|
// Пропускаем недействительные токены
|
|
133
|
-
if (t.invalid) return false;
|
|
134
|
-
|
|
133
|
+
if (t.invalid) {return false;}
|
|
134
|
+
|
|
135
135
|
// Пропускаем токены с rate limit в будущем
|
|
136
|
-
if (t.resetAt && new Date(t.resetAt).getTime() > now) return false;
|
|
137
|
-
|
|
136
|
+
if (t.resetAt && new Date(t.resetAt).getTime() > now) {return false;}
|
|
137
|
+
|
|
138
138
|
// Пропускаем токены с истёкшим JWT
|
|
139
|
-
if (t.expiryTime && t.expiryTime <= now) return false;
|
|
140
|
-
|
|
139
|
+
if (t.expiryTime && t.expiryTime <= now) {return false;}
|
|
140
|
+
|
|
141
141
|
// Пропускаем токены без cookies.json
|
|
142
|
-
if (!hasCookies(t.id)) return false;
|
|
143
|
-
|
|
142
|
+
if (!hasCookies(t.id)) {return false;}
|
|
143
|
+
|
|
144
144
|
return true;
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export function markRateLimited(id, hours = 24) {
|
|
149
149
|
const tokens = loadTokens();
|
|
150
|
-
const idx = tokens.findIndex(t => t.id === id);
|
|
150
|
+
const idx = tokens.findIndex((t) => t.id === id);
|
|
151
151
|
if (idx !== -1) {
|
|
152
152
|
tokens[idx].resetAt = new Date(Date.now() + hours * 3600 * 1000).toISOString();
|
|
153
153
|
saveTokens(tokens);
|
|
@@ -155,24 +155,24 @@ export function markRateLimited(id, hours = 24) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export function removeToken(id) {
|
|
158
|
-
saveTokens(loadTokens().filter(t => t.id !== id));
|
|
158
|
+
saveTokens(loadTokens().filter((t) => t.id !== id));
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
export { removeToken as removeInvalidToken };
|
|
162
162
|
|
|
163
163
|
export function markInvalid(id) {
|
|
164
164
|
const tokens = loadTokens();
|
|
165
|
-
const idx = tokens.findIndex(t => t.id === id);
|
|
165
|
+
const idx = tokens.findIndex((t) => t.id === id);
|
|
166
166
|
if (idx !== -1) { tokens[idx].invalid = true; saveTokens(tokens); }
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
export function markValid(id, newToken) {
|
|
170
170
|
const tokens = loadTokens();
|
|
171
|
-
const idx = tokens.findIndex(t => t.id === id);
|
|
171
|
+
const idx = tokens.findIndex((t) => t.id === id);
|
|
172
172
|
if (idx !== -1) {
|
|
173
173
|
tokens[idx].invalid = false;
|
|
174
174
|
tokens[idx].resetAt = null;
|
|
175
|
-
if (newToken) tokens[idx].token = newToken;
|
|
175
|
+
if (newToken) {tokens[idx].token = newToken;}
|
|
176
176
|
saveTokens(tokens);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -188,20 +188,20 @@ export function listTokens() {
|
|
|
188
188
|
export function getValidTokens() {
|
|
189
189
|
const tokens = loadTokens();
|
|
190
190
|
const now = Date.now();
|
|
191
|
-
|
|
192
|
-
return tokens.filter(t => {
|
|
191
|
+
|
|
192
|
+
return tokens.filter((t) => {
|
|
193
193
|
// Пропускаем недействительные токены
|
|
194
|
-
if (t.invalid) return false;
|
|
195
|
-
|
|
194
|
+
if (t.invalid) {return false;}
|
|
195
|
+
|
|
196
196
|
// Пропускаем токены с rate limit в будущем
|
|
197
|
-
if (t.resetAt && new Date(t.resetAt).getTime() > now) return false;
|
|
198
|
-
|
|
197
|
+
if (t.resetAt && new Date(t.resetAt).getTime() > now) {return false;}
|
|
198
|
+
|
|
199
199
|
// Пропускаем токены с истёкшим JWT
|
|
200
|
-
if (t.expiryTime && t.expiryTime <= now) return false;
|
|
201
|
-
|
|
200
|
+
if (t.expiryTime && t.expiryTime <= now) {return false;}
|
|
201
|
+
|
|
202
202
|
// Пропускаем токены без cookies.json
|
|
203
|
-
if (!hasCookies(t.id)) return false;
|
|
204
|
-
|
|
203
|
+
if (!hasCookies(t.id)) {return false;}
|
|
204
|
+
|
|
205
205
|
return true;
|
|
206
206
|
});
|
|
207
207
|
}
|
|
@@ -215,14 +215,14 @@ export function getValidTokens() {
|
|
|
215
215
|
*/
|
|
216
216
|
export function checkTokenExpiry(tokenId, warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
217
217
|
const tokens = loadTokens();
|
|
218
|
-
const token = tokens.find(t => t.id === tokenId);
|
|
219
|
-
|
|
218
|
+
const token = tokens.find((t) => t.id === tokenId);
|
|
219
|
+
|
|
220
220
|
if (!token) {
|
|
221
221
|
return { willExpireSoon: false, expiresAt: null, timeLeft: null, tokenFound: false };
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const now = Date.now();
|
|
225
|
-
|
|
225
|
+
|
|
226
226
|
// Если токен помечен как недействительный
|
|
227
227
|
if (token.invalid) {
|
|
228
228
|
return { willExpireSoon: true, expiresAt: null, timeLeft: null, tokenFound: true, isInvalid: true };
|
|
@@ -231,25 +231,25 @@ export function checkTokenExpiry(tokenId, warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
231
231
|
// Проверяем JWT expiry time (если есть)
|
|
232
232
|
if (token.expiryTime) {
|
|
233
233
|
const jwtTimeLeft = token.expiryTime - now;
|
|
234
|
-
|
|
234
|
+
|
|
235
235
|
// Если JWT уже истёк
|
|
236
236
|
if (jwtTimeLeft <= 0) {
|
|
237
|
-
return {
|
|
238
|
-
willExpireSoon: true,
|
|
239
|
-
expiresAt: new Date(token.expiryTime),
|
|
240
|
-
timeLeft: 0,
|
|
241
|
-
tokenFound: true,
|
|
237
|
+
return {
|
|
238
|
+
willExpireSoon: true,
|
|
239
|
+
expiresAt: new Date(token.expiryTime),
|
|
240
|
+
timeLeft: 0,
|
|
241
|
+
tokenFound: true,
|
|
242
242
|
isExpired: true,
|
|
243
243
|
expiredType: 'jwt'
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
-
|
|
246
|
+
|
|
247
247
|
// Если JWT истекает в ближайшее время
|
|
248
248
|
if (jwtTimeLeft <= warningMs) {
|
|
249
|
-
return {
|
|
250
|
-
willExpireSoon: true,
|
|
251
|
-
expiresAt: new Date(token.expiryTime),
|
|
252
|
-
timeLeft: jwtTimeLeft,
|
|
249
|
+
return {
|
|
250
|
+
willExpireSoon: true,
|
|
251
|
+
expiresAt: new Date(token.expiryTime),
|
|
252
|
+
timeLeft: jwtTimeLeft,
|
|
253
253
|
tokenFound: true,
|
|
254
254
|
isExpiringSoon: true,
|
|
255
255
|
expiredType: 'jwt'
|
|
@@ -261,35 +261,35 @@ export function checkTokenExpiry(tokenId, warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
261
261
|
if (token.resetAt) {
|
|
262
262
|
const resetTime = new Date(token.resetAt).getTime();
|
|
263
263
|
const timeLeft = resetTime - now;
|
|
264
|
-
|
|
264
|
+
|
|
265
265
|
// Если уже истёк или истекает в ближайшее время
|
|
266
266
|
if (timeLeft <= 0) {
|
|
267
|
-
return {
|
|
268
|
-
willExpireSoon: true,
|
|
269
|
-
expiresAt: new Date(token.resetAt),
|
|
270
|
-
timeLeft: 0,
|
|
271
|
-
tokenFound: true,
|
|
267
|
+
return {
|
|
268
|
+
willExpireSoon: true,
|
|
269
|
+
expiresAt: new Date(token.resetAt),
|
|
270
|
+
timeLeft: 0,
|
|
271
|
+
tokenFound: true,
|
|
272
272
|
isExpired: true,
|
|
273
273
|
expiredType: 'rate_limit'
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
|
-
|
|
276
|
+
|
|
277
277
|
if (timeLeft <= warningMs) {
|
|
278
|
-
return {
|
|
279
|
-
willExpireSoon: true,
|
|
280
|
-
expiresAt: new Date(token.resetAt),
|
|
281
|
-
timeLeft,
|
|
278
|
+
return {
|
|
279
|
+
willExpireSoon: true,
|
|
280
|
+
expiresAt: new Date(token.resetAt),
|
|
281
|
+
timeLeft,
|
|
282
282
|
tokenFound: true,
|
|
283
283
|
isExpiringSoon: true,
|
|
284
284
|
expiredType: 'rate_limit'
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
willExpireSoon: false,
|
|
290
|
-
expiresAt: new Date(token.resetAt),
|
|
291
|
-
timeLeft,
|
|
292
|
-
tokenFound: true
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
willExpireSoon: false,
|
|
290
|
+
expiresAt: new Date(token.resetAt),
|
|
291
|
+
timeLeft,
|
|
292
|
+
tokenFound: true
|
|
293
293
|
};
|
|
294
294
|
}
|
|
295
295
|
|
|
@@ -305,13 +305,13 @@ export function checkTokenExpiry(tokenId, warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
305
305
|
export function checkAllTokensExpiry(warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
306
306
|
const tokens = loadTokens();
|
|
307
307
|
const now = Date.now();
|
|
308
|
-
|
|
308
|
+
|
|
309
309
|
const expiringTokens = [];
|
|
310
310
|
let activeTokens = 0;
|
|
311
311
|
|
|
312
|
-
tokens.forEach(token => {
|
|
312
|
+
tokens.forEach((token) => {
|
|
313
313
|
const expiryInfo = checkTokenExpiry(token.id, warningMs);
|
|
314
|
-
|
|
314
|
+
|
|
315
315
|
if (expiryInfo.willExpireSoon) {
|
|
316
316
|
expiringTokens.push({
|
|
317
317
|
...token,
|
|
@@ -340,15 +340,15 @@ export function checkAllTokensExpiry(warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
340
340
|
export async function getSafeToken(warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
341
341
|
const tokens = loadTokens();
|
|
342
342
|
const now = Date.now();
|
|
343
|
-
|
|
343
|
+
|
|
344
344
|
// Фильтруем токены, которые не истекают в ближайшее время и имеют cookies
|
|
345
|
-
const safeTokens = tokens.filter(t => {
|
|
345
|
+
const safeTokens = tokens.filter((t) => {
|
|
346
346
|
// Пропускаем недействительные токены
|
|
347
|
-
if (t.invalid) return false;
|
|
348
|
-
|
|
347
|
+
if (t.invalid) {return false;}
|
|
348
|
+
|
|
349
349
|
// Пропускаем токены без cookies.json
|
|
350
|
-
if (!hasCookies(t.id)) return false;
|
|
351
|
-
|
|
350
|
+
if (!hasCookies(t.id)) {return false;}
|
|
351
|
+
|
|
352
352
|
// Проверяем rate limit reset time
|
|
353
353
|
if (t.resetAt) {
|
|
354
354
|
const resetTime = new Date(t.resetAt).getTime();
|
|
@@ -357,7 +357,7 @@ export async function getSafeToken(warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
357
357
|
return false;
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
-
|
|
360
|
+
|
|
361
361
|
// Проверяем JWT expiry time (если есть)
|
|
362
362
|
if (t.expiryTime) {
|
|
363
363
|
const jwtTimeLeft = t.expiryTime - now;
|
|
@@ -366,7 +366,7 @@ export async function getSafeToken(warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
366
366
|
return false;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
|
-
|
|
369
|
+
|
|
370
370
|
return true;
|
|
371
371
|
});
|
|
372
372
|
|
|
@@ -376,7 +376,7 @@ export async function getSafeToken(warningMs = TOKEN_EXPIRY_WARNING_MS) {
|
|
|
376
376
|
|
|
377
377
|
const token = safeTokens[pointer % safeTokens.length];
|
|
378
378
|
pointer = (pointer + 1) % safeTokens.length;
|
|
379
|
-
|
|
379
|
+
|
|
380
380
|
logInfo(`Использован безопасный токен: ${token.id}`);
|
|
381
381
|
return token;
|
|
382
382
|
}
|
package/src/browser/auth.js
CHANGED
|
@@ -4,20 +4,20 @@ import { extractAuthToken } from '../api/chat.js';
|
|
|
4
4
|
import { logInfo, logError, logWarn } from '../logger/index.js';
|
|
5
5
|
import { CHAT_PAGE_URL, AUTH_SIGNIN_URL, PAGE_TIMEOUT, RETRY_DELAY } from '../config.js';
|
|
6
6
|
|
|
7
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
8
|
|
|
9
9
|
function isPlaywright(context) {
|
|
10
10
|
return context && typeof context.newPage === 'function';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async function getPage(context) {
|
|
14
|
-
if (context && typeof context.goto === 'function') return context;
|
|
15
|
-
if (context && typeof context.newPage === 'function') return await context.newPage();
|
|
14
|
+
if (context && typeof context.goto === 'function') {return context;}
|
|
15
|
+
if (context && typeof context.newPage === 'function') {return await context.newPage();}
|
|
16
16
|
throw new Error('Неверный контекст: не страница Puppeteer, не контекст Playwright');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async function promptUser(question) {
|
|
20
|
-
return new Promise(resolve => {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
21
|
process.stdout.write(question);
|
|
22
22
|
const onData = (data) => {
|
|
23
23
|
process.stdin.removeListener('data', onData);
|
|
@@ -30,13 +30,13 @@ async function promptUser(question) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async function countLoginContainers(page, isPW) {
|
|
33
|
-
if (isPW) return page.locator('.login-container').count();
|
|
33
|
+
if (isPW) {return page.locator('.login-container').count();}
|
|
34
34
|
return (await page.$$('.login-container')).length;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export async function checkAuthentication(context) {
|
|
38
38
|
try {
|
|
39
|
-
if (getAuthenticationStatus()) return true;
|
|
39
|
+
if (getAuthenticationStatus()) {return true;}
|
|
40
40
|
|
|
41
41
|
const page = await getPage(context);
|
|
42
42
|
const isPW = isPlaywright(context);
|
|
@@ -45,7 +45,7 @@ export async function checkAuthentication(context) {
|
|
|
45
45
|
|
|
46
46
|
try {
|
|
47
47
|
await page.goto(CHAT_PAGE_URL, { waitUntil: 'domcontentloaded', timeout: PAGE_TIMEOUT });
|
|
48
|
-
if (isPW) await page.waitForLoadState('domcontentloaded');
|
|
48
|
+
if (isPW) {await page.waitForLoadState('domcontentloaded');}
|
|
49
49
|
await delay(RETRY_DELAY);
|
|
50
50
|
|
|
51
51
|
const pageTitle = await page.title();
|
|
@@ -65,7 +65,7 @@ export async function checkAuthentication(context) {
|
|
|
65
65
|
await saveSession(context);
|
|
66
66
|
logInfo('Сессия обновлена');
|
|
67
67
|
} catch (e) { logError('Не удалось обновить сессию', e); }
|
|
68
|
-
if (isPW) await page.close();
|
|
68
|
+
if (isPW) {await page.close();}
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -90,7 +90,7 @@ export async function checkAuthentication(context) {
|
|
|
90
90
|
setAuthenticationStatus(true);
|
|
91
91
|
await saveSession(context);
|
|
92
92
|
await extractAuthToken(context, true);
|
|
93
|
-
if (isPW) await page.close();
|
|
93
|
+
if (isPW) {await page.close();}
|
|
94
94
|
return true;
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -98,7 +98,7 @@ export async function checkAuthentication(context) {
|
|
|
98
98
|
setAuthenticationStatus(false);
|
|
99
99
|
return false;
|
|
100
100
|
} catch (error) {
|
|
101
|
-
if (isPW) await page.close().catch(() => {});
|
|
101
|
+
if (isPW) {await page.close().catch(() => {});}
|
|
102
102
|
throw error;
|
|
103
103
|
}
|
|
104
104
|
} catch (error) {
|
|
@@ -139,8 +139,8 @@ export async function startManualAuthentication(context, skipRestart = false) {
|
|
|
139
139
|
await saveSession(context);
|
|
140
140
|
await extractAuthToken(context, true);
|
|
141
141
|
logInfo('Сессия сохранена успешно!');
|
|
142
|
-
if (isPW) await page.close();
|
|
143
|
-
if (!skipRestart) await restartBrowserInHeadlessMode();
|
|
142
|
+
if (isPW) {await page.close();}
|
|
143
|
+
if (!skipRestart) {await restartBrowserInHeadlessMode();}
|
|
144
144
|
return true;
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -148,7 +148,7 @@ export async function startManualAuthentication(context, skipRestart = false) {
|
|
|
148
148
|
setAuthenticationStatus(false);
|
|
149
149
|
return false;
|
|
150
150
|
} catch (error) {
|
|
151
|
-
if (isPW) await page.close().catch(() => {});
|
|
151
|
+
if (isPW) {await page.close().catch(() => {});}
|
|
152
152
|
throw error;
|
|
153
153
|
}
|
|
154
154
|
} catch (error) {
|
package/src/browser/browser.js
CHANGED
|
@@ -18,10 +18,10 @@ let browserInstance = null;
|
|
|
18
18
|
let browserContext = null;
|
|
19
19
|
export let isAuthenticated = false;
|
|
20
20
|
|
|
21
|
-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
21
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
22
|
|
|
23
23
|
export async function initBrowser(visibleMode = true, skipManualRestart = false) {
|
|
24
|
-
if (browserInstance) return true;
|
|
24
|
+
if (browserInstance) {return true;}
|
|
25
25
|
|
|
26
26
|
logInfo('Инициализация браузера с Puppeteer Stealth...');
|
|
27
27
|
try {
|
|
@@ -117,11 +117,11 @@ async function saveSessionPuppeteer(page) {
|
|
|
117
117
|
try {
|
|
118
118
|
const cookies = await page.cookies();
|
|
119
119
|
const sessionDir = path.join(process.cwd(), SESSION_DIR, ACCOUNTS_DIR);
|
|
120
|
-
if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true });
|
|
120
|
+
if (!fs.existsSync(sessionDir)) {fs.mkdirSync(sessionDir, { recursive: true });}
|
|
121
121
|
|
|
122
122
|
const accountId = `acc_${Date.now()}`;
|
|
123
123
|
const accountDir = path.join(sessionDir, accountId);
|
|
124
|
-
if (!fs.existsSync(accountDir)) fs.mkdirSync(accountDir, { recursive: true });
|
|
124
|
+
if (!fs.existsSync(accountDir)) {fs.mkdirSync(accountDir, { recursive: true });}
|
|
125
125
|
|
|
126
126
|
fs.writeFileSync(path.join(accountDir, 'cookies.json'), JSON.stringify(cookies, null, 2));
|
|
127
127
|
logInfo(`Cookies сохранены для аккаунта ${accountId}`);
|
|
@@ -151,7 +151,7 @@ async function startManualAuthenticationPuppeteer(page, skipManualRestart) {
|
|
|
151
151
|
console.log('После успешной авторизации нажмите ENTER для продолжения...');
|
|
152
152
|
|
|
153
153
|
await new Promise((resolve) => {
|
|
154
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
154
|
+
if (process.stdin.isTTY) {process.stdin.setRawMode(false);}
|
|
155
155
|
process.stdin.resume();
|
|
156
156
|
process.stdin.setEncoding('utf8');
|
|
157
157
|
const onData = (key) => {
|
|
@@ -180,7 +180,7 @@ async function startManualAuthenticationPuppeteer(page, skipManualRestart) {
|
|
|
180
180
|
} else {
|
|
181
181
|
logWarn('Токен не найден в localStorage/sessionStorage');
|
|
182
182
|
logInfo('Попытка извлечь токен из cookies...');
|
|
183
|
-
const tokenCookie = cookies.find(c => c.name.toLowerCase().includes('token') || c.name.toLowerCase().includes('auth'));
|
|
183
|
+
const tokenCookie = cookies.find((c) => c.name.toLowerCase().includes('token') || c.name.toLowerCase().includes('auth'));
|
|
184
184
|
if (tokenCookie) {
|
|
185
185
|
logInfo(`Токен найден в cookie: ${tokenCookie.name}`);
|
|
186
186
|
saveAuthToken(tokenCookie.value);
|
|
@@ -188,12 +188,12 @@ async function startManualAuthenticationPuppeteer(page, skipManualRestart) {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
const accountId = await saveSessionPuppeteer(page);
|
|
191
|
-
if (accountId) logInfo(`Сессия сохранена с ID: ${accountId}`);
|
|
191
|
+
if (accountId) {logInfo(`Сессия сохранена с ID: ${accountId}`);}
|
|
192
192
|
|
|
193
193
|
setAuthenticationStatus(true);
|
|
194
194
|
logInfo('Авторизация завершена успешно');
|
|
195
195
|
|
|
196
|
-
if (!skipManualRestart) await restartBrowserInHeadlessMode();
|
|
196
|
+
if (!skipManualRestart) {await restartBrowserInHeadlessMode();}
|
|
197
197
|
} catch (error) {
|
|
198
198
|
logError('Ошибка при ручной авторизации', error);
|
|
199
199
|
throw error;
|
|
@@ -216,7 +216,7 @@ export async function shutdownBrowser() {
|
|
|
216
216
|
if (browserInstance) {
|
|
217
217
|
try {
|
|
218
218
|
const pages = await browserInstance.pages();
|
|
219
|
-
for (const page of pages) await page.close().catch(() => {});
|
|
219
|
+
for (const page of pages) {await page.close().catch(() => {});}
|
|
220
220
|
await browserInstance.close();
|
|
221
221
|
} catch (e) { logError('Ошибка при закрытии браузера', e); }
|
|
222
222
|
}
|
package/src/browser/session.js
CHANGED
|
@@ -16,7 +16,7 @@ function getSessionFilePath(accountId, fileName) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function ensureDir(dirPath) {
|
|
19
|
-
if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
|
|
19
|
+
if (!fs.existsSync(dirPath)) {fs.mkdirSync(dirPath, { recursive: true });}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function initSessionDirectory() {
|
|
@@ -91,7 +91,7 @@ export function clearSession(accountId = null) {
|
|
|
91
91
|
for (const p of paths) {
|
|
92
92
|
if (fs.existsSync(p)) { fs.unlinkSync(p); cleared = true; }
|
|
93
93
|
}
|
|
94
|
-
if (cleared) logInfo('Сессия очищена');
|
|
94
|
+
if (cleared) {logInfo('Сессия очищена');}
|
|
95
95
|
return cleared;
|
|
96
96
|
} catch (error) {
|
|
97
97
|
logError('Ошибка при очистке сессии', error);
|
|
@@ -103,7 +103,7 @@ export function hasSession(accountId = null) {
|
|
|
103
103
|
return [
|
|
104
104
|
getSessionFilePath(accountId, 'state.json'),
|
|
105
105
|
getSessionFilePath(accountId, 'cookies.json')
|
|
106
|
-
].some(p => fs.existsSync(p));
|
|
106
|
+
].some((p) => fs.existsSync(p));
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export function saveAuthToken(token) {
|
package/src/config.js
CHANGED
|
@@ -24,9 +24,9 @@ if (dotenvResult.error) {
|
|
|
24
24
|
// Все значения читаются из env-переменных с фоллбэками на дефолты.
|
|
25
25
|
|
|
26
26
|
function toBoolean(value) {
|
|
27
|
-
if (typeof value === 'boolean') return value;
|
|
28
|
-
if (typeof value === 'number') return value === 1;
|
|
29
|
-
if (typeof value !== 'string') return false;
|
|
27
|
+
if (typeof value === 'boolean') {return value;}
|
|
28
|
+
if (typeof value === 'number') {return value === 1;}
|
|
29
|
+
if (typeof value !== 'string') {return false;}
|
|
30
30
|
return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase());
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -92,7 +92,7 @@ export const LOG_MAX_FILES = Number(process.env.LOG_MAX_FILES) || 5;
|
|
|
92
92
|
// ─── Telegram уведомления ───────────────────────────────────────────────────
|
|
93
93
|
export const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || null;
|
|
94
94
|
export const TELEGRAM_USER_IDS = process.env.TELEGRAM_USER_IDS
|
|
95
|
-
? process.env.TELEGRAM_USER_IDS.split(',').map(id => id.trim()).filter(id => id)
|
|
95
|
+
? process.env.TELEGRAM_USER_IDS.split(',').map((id) => id.trim()).filter((id) => id)
|
|
96
96
|
: [];
|
|
97
97
|
export const TOKEN_EXPIRY_WARNING_MS = Number(process.env.TOKEN_EXPIRY_WARNING_MS) || 3600000; // 1 hour
|
|
98
98
|
|
|
@@ -14,7 +14,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
14
14
|
|
|
15
15
|
function ensureAccountDir(id) {
|
|
16
16
|
const accountDir = path.resolve(__dirname, '..', '..', SESSION_DIR, ACCOUNTS_DIR, id);
|
|
17
|
-
if (!fs.existsSync(accountDir)) fs.mkdirSync(accountDir, { recursive: true });
|
|
17
|
+
if (!fs.existsSync(accountDir)) {fs.mkdirSync(accountDir, { recursive: true });}
|
|
18
18
|
return accountDir;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -35,7 +35,7 @@ export async function addAccountInteractive() {
|
|
|
35
35
|
|
|
36
36
|
if (!token) {
|
|
37
37
|
token = loadAuthToken();
|
|
38
|
-
if (token) logInfo('Токен получен из сохранённого файла.');
|
|
38
|
+
if (token) {logInfo('Токен получен из сохранённого файла.');}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
if (!token) {
|
|
@@ -65,15 +65,15 @@ export async function interactiveAccountMenu() {
|
|
|
65
65
|
console.log('1 - Добавить новый аккаунт');
|
|
66
66
|
console.log('2 - Завершить');
|
|
67
67
|
const choice = await prompt('Ваш выбор (1/2): ');
|
|
68
|
-
if (choice === '1') await addAccountInteractive();
|
|
69
|
-
else if (choice === '2') break;
|
|
70
|
-
else console.log('Неверный выбор.');
|
|
68
|
+
if (choice === '1') {await addAccountInteractive();}
|
|
69
|
+
else if (choice === '2') {break;}
|
|
70
|
+
else {console.log('Неверный выбор.');}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export async function reloginAccountInteractive() {
|
|
75
75
|
const tokens = loadTokens();
|
|
76
|
-
const invalids = tokens.filter(t => t.invalid);
|
|
76
|
+
const invalids = tokens.filter((t) => t.invalid);
|
|
77
77
|
if (!invalids.length) {
|
|
78
78
|
console.log('Нет аккаунтов, требующих повторного входа.');
|
|
79
79
|
await prompt('Нажмите ENTER чтобы вернуться в меню...');
|
|
@@ -113,13 +113,13 @@ export async function removeAccountInteractive() {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
const now = Date.now();
|
|
116
|
-
const validTokens = tokens.filter(t => {
|
|
117
|
-
if (t.invalid) return false;
|
|
118
|
-
if (t.resetAt && new Date(t.resetAt).getTime() > now) return false;
|
|
119
|
-
if (t.expiryTime && t.expiryTime <= now) return false;
|
|
116
|
+
const validTokens = tokens.filter((t) => {
|
|
117
|
+
if (t.invalid) {return false;}
|
|
118
|
+
if (t.resetAt && new Date(t.resetAt).getTime() > now) {return false;}
|
|
119
|
+
if (t.expiryTime && t.expiryTime <= now) {return false;}
|
|
120
120
|
// Проверяем наличие cookies.json
|
|
121
121
|
const cookiesPath = path.resolve(__dirname, '..', '..', SESSION_DIR, ACCOUNTS_DIR, t.id, 'cookies.json');
|
|
122
|
-
if (!fs.existsSync(cookiesPath)) return false;
|
|
122
|
+
if (!fs.existsSync(cookiesPath)) {return false;}
|
|
123
123
|
return true;
|
|
124
124
|
});
|
|
125
125
|
|
|
@@ -132,7 +132,7 @@ export async function removeAccountInteractive() {
|
|
|
132
132
|
console.log('\nДоступные аккаунты:');
|
|
133
133
|
validTokens.forEach((t, idx) => console.log(`${idx + 1} - ${t.id}`));
|
|
134
134
|
const choice = await prompt('Номер аккаунта, который нужно удалить (или ENTER для отмены): ');
|
|
135
|
-
if (!choice) return;
|
|
135
|
+
if (!choice) {return;}
|
|
136
136
|
const num = parseInt(choice, 10);
|
|
137
137
|
if (isNaN(num) || num < 1 || num > validTokens.length) {
|
|
138
138
|
console.log('Неверный выбор.');
|
|
@@ -142,11 +142,11 @@ export async function removeAccountInteractive() {
|
|
|
142
142
|
|
|
143
143
|
const acc = validTokens[num - 1];
|
|
144
144
|
const confirm = await prompt(`Точно удалить ${acc.id}? (y/N): `);
|
|
145
|
-
if (confirm.toLowerCase() !== 'y') return;
|
|
145
|
+
if (confirm.toLowerCase() !== 'y') {return;}
|
|
146
146
|
|
|
147
147
|
removeToken(acc.id);
|
|
148
148
|
const dir = path.resolve(__dirname, '..', '..', SESSION_DIR, ACCOUNTS_DIR, acc.id);
|
|
149
|
-
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
|
|
149
|
+
if (fs.existsSync(dir)) {fs.rmSync(dir, { recursive: true, force: true });}
|
|
150
150
|
|
|
151
151
|
logInfo(`Аккаунт ${acc.id} удалён.`);
|
|
152
152
|
await prompt('ENTER чтобы вернуться...');
|