evogram-gramjs 1.0.0
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/.evogram/7878190870/database.db +0 -0
- package/lib/EvogramGramJS.d.ts +51 -0
- package/lib/EvogramGramJS.js +99 -0
- package/lib/commands/Accounts.command.d.ts +6 -0
- package/lib/commands/Accounts.command.js +90 -0
- package/lib/commands/AddAccount.command.d.ts +6 -0
- package/lib/commands/AddAccount.command.js +170 -0
- package/lib/commands/index.d.ts +0 -0
- package/lib/commands/index.js +1 -0
- package/lib/commands/managment/DeleteAccount.command.d.ts +0 -0
- package/lib/commands/managment/DeleteAccount.command.js +12 -0
- package/lib/config/database.config.d.ts +26 -0
- package/lib/config/database.config.js +31 -0
- package/lib/entities/Session.entity.d.ts +28 -0
- package/lib/entities/Session.entity.js +71 -0
- package/lib/entities/SessionEventLog.entity.d.ts +22 -0
- package/lib/entities/SessionEventLog.entity.js +50 -0
- package/lib/examples/auth.example.d.ts +10 -0
- package/lib/examples/auth.example.js +126 -0
- package/lib/examples/database.example.d.ts +13 -0
- package/lib/examples/database.example.js +109 -0
- package/lib/examples/usage.example.d.ts +13 -0
- package/lib/examples/usage.example.js +127 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +10 -0
- package/lib/services/DatabaseService.d.ts +30 -0
- package/lib/services/DatabaseService.js +93 -0
- package/lib/services/ImageUploadService.d.ts +15 -0
- package/lib/services/ImageUploadService.js +56 -0
- package/lib/sessions/Session.d.ts +13 -0
- package/lib/sessions/Session.js +25 -0
- package/lib/sessions/SessionAuth.d.ts +62 -0
- package/lib/sessions/SessionAuth.js +165 -0
- package/lib/sessions/SessionLogger.d.ts +84 -0
- package/lib/sessions/SessionLogger.js +196 -0
- package/lib/sessions/SessionManager.d.ts +79 -0
- package/lib/sessions/SessionManager.js +184 -0
- package/lib/types/auth.types.d.ts +90 -0
- package/lib/types/auth.types.js +19 -0
- package/lib/types/session.types.d.ts +87 -0
- package/lib/types/session.types.js +21 -0
- package/lib/utils/Deferrer.d.ts +6 -0
- package/lib/utils/Deferrer.js +14 -0
- package/package-lock.json +6 -0
- package/package.json +27 -0
- package/src/EvogramGramJS.ts +98 -0
- package/src/commands/Accounts.command.ts +84 -0
- package/src/commands/AddAccount.command.ts +171 -0
- package/src/commands/index.ts +0 -0
- package/src/commands/managment/DeleteAccount.command.ts +13 -0
- package/src/config/database.config.ts +75 -0
- package/src/entities/Session.entity.ts +58 -0
- package/src/entities/SessionEventLog.entity.ts +41 -0
- package/src/index.ts +7 -0
- package/src/services/DatabaseService.ts +82 -0
- package/src/services/ImageUploadService.ts +49 -0
- package/src/sessions/Session.ts +21 -0
- package/src/sessions/SessionAuth.ts +173 -0
- package/src/sessions/SessionLogger.ts +208 -0
- package/src/sessions/SessionManager.ts +190 -0
- package/src/types/auth.types.ts +94 -0
- package/src/types/session.types.ts +96 -0
- package/src/utils/Deferrer.ts +12 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { EventEmitter } from 'events'
|
|
2
|
+
import { TelegramClient } from 'telegram'
|
|
3
|
+
import { EvogramGramJS } from '../EvogramGramJS'
|
|
4
|
+
import { ImageUploadService } from '../services/ImageUploadService'
|
|
5
|
+
import { AuthState } from '../types/auth.types'
|
|
6
|
+
import { Deferred } from '../utils/Deferrer'
|
|
7
|
+
import { SessionLogger } from './SessionLogger'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* События авторизации для сессии
|
|
11
|
+
*/
|
|
12
|
+
export enum SessionAuthEvent {
|
|
13
|
+
/** Начало авторизации */
|
|
14
|
+
AUTH_STARTED = 'auth:started',
|
|
15
|
+
/** Код отправлен */
|
|
16
|
+
CODE_SENT = 'auth:code_sent',
|
|
17
|
+
/** Код получен */
|
|
18
|
+
CODE_RECEIVED = 'auth:code_received',
|
|
19
|
+
/** Требуется пароль */
|
|
20
|
+
PASSWORD_REQUIRED = 'auth:password_required',
|
|
21
|
+
/** Авторизация успешна */
|
|
22
|
+
AUTH_SUCCESS = 'auth:success',
|
|
23
|
+
/** Ошибка авторизации */
|
|
24
|
+
AUTH_ERROR = 'auth:error',
|
|
25
|
+
/** Авторизация отменена */
|
|
26
|
+
AUTH_CANCELLED = 'auth:cancelled',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Класс авторизации для конкретной сессии
|
|
31
|
+
* Предоставляет методы авторизации, привязанные к сессии
|
|
32
|
+
*/
|
|
33
|
+
export class SessionAuth extends EventEmitter {
|
|
34
|
+
public state: {
|
|
35
|
+
code: Deferred<string>
|
|
36
|
+
password: Deferred<string>
|
|
37
|
+
isRequiredPassword: boolean
|
|
38
|
+
stage: AuthState
|
|
39
|
+
connect: Deferred<boolean>
|
|
40
|
+
} = null as any
|
|
41
|
+
|
|
42
|
+
constructor(private readonly sessionId: string, private readonly client: TelegramClient, private readonly logger?: SessionLogger) {
|
|
43
|
+
super()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async start(phoneNumber: string) {
|
|
47
|
+
if (this.state) throw new Error(`Авторизация для сессии "${this.sessionId}" уже начата`)
|
|
48
|
+
|
|
49
|
+
this.state = {
|
|
50
|
+
code: new Deferred<string>(),
|
|
51
|
+
password: new Deferred<string>(),
|
|
52
|
+
isRequiredPassword: false,
|
|
53
|
+
stage: AuthState.INITIAL,
|
|
54
|
+
connect: new Deferred<boolean>(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await new Promise(async (res, rej) => {
|
|
59
|
+
await this.client
|
|
60
|
+
.start({
|
|
61
|
+
phoneNumber,
|
|
62
|
+
phoneCode: async () => {
|
|
63
|
+
res(true)
|
|
64
|
+
this.state.stage = AuthState.WAITING_CODE
|
|
65
|
+
|
|
66
|
+
const code = await this.state.code.promise
|
|
67
|
+
this.state.code = new Deferred<string>()
|
|
68
|
+
|
|
69
|
+
return code
|
|
70
|
+
},
|
|
71
|
+
password: async () => {
|
|
72
|
+
this.state.isRequiredPassword = true
|
|
73
|
+
this.state.stage = AuthState.WAITING_PASSWORD
|
|
74
|
+
|
|
75
|
+
const password = await this.state.password.promise
|
|
76
|
+
this.state.password = new Deferred<string>()
|
|
77
|
+
|
|
78
|
+
return password
|
|
79
|
+
},
|
|
80
|
+
onError: (error) => {
|
|
81
|
+
this.state.stage = AuthState.ERROR
|
|
82
|
+
|
|
83
|
+
this.emit(SessionAuthEvent.AUTH_ERROR, {
|
|
84
|
+
sessionId: this.sessionId,
|
|
85
|
+
error: error instanceof Error ? error.message : String(error),
|
|
86
|
+
})
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
.catch(rej)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const me = await this.client.getMe()
|
|
93
|
+
this.state.connect.resolve(true)
|
|
94
|
+
|
|
95
|
+
let avatarBuffer = await this.client.downloadProfilePhoto('me'),
|
|
96
|
+
avatarUrl
|
|
97
|
+
if (avatarBuffer) avatarUrl = await ImageUploadService.uploadToImgbox(Buffer.isBuffer(avatarBuffer) ? avatarBuffer : Buffer.from(avatarBuffer))
|
|
98
|
+
|
|
99
|
+
await EvogramGramJS.databaseService.saveSession({
|
|
100
|
+
sessionId: this.sessionId,
|
|
101
|
+
apiId: this.client.apiId,
|
|
102
|
+
apiHash: this.client.apiHash,
|
|
103
|
+
userId: Number(me.id),
|
|
104
|
+
username: me.username,
|
|
105
|
+
firstName: me.firstName,
|
|
106
|
+
lastName: me.lastName,
|
|
107
|
+
phoneNumber: me.phone,
|
|
108
|
+
sessionString: this.client.session.save()!,
|
|
109
|
+
avatarUrl,
|
|
110
|
+
})
|
|
111
|
+
} catch {}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
this.emit(SessionAuthEvent.AUTH_ERROR, {
|
|
119
|
+
sessionId: this.sessionId,
|
|
120
|
+
error: error instanceof Error ? error.message : String(error),
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async setCode(code: string) {
|
|
131
|
+
if (!this.state) throw new Error(`Авторизация для сессии "${this.sessionId}" не начата`)
|
|
132
|
+
const errorPromise = new Promise<Error>((resolve, reject) => {
|
|
133
|
+
this.once(SessionAuthEvent.AUTH_ERROR, (data) => data.sessionId === this.sessionId && resolve(data.error))
|
|
134
|
+
setTimeout(resolve, 1_000)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
this.state.code.resolve(code)
|
|
138
|
+
const error = await errorPromise
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
success: error ? false : true,
|
|
142
|
+
error: error || undefined,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async setPassword(password: string) {
|
|
147
|
+
if (!this.state) throw new Error(`Авторизация для сессии "${this.sessionId}" не начата`)
|
|
148
|
+
|
|
149
|
+
const errorPromise = new Promise<Error>((resolve, reject) => {
|
|
150
|
+
this.once(SessionAuthEvent.AUTH_ERROR, (data) => data.sessionId === this.sessionId && resolve(data.error))
|
|
151
|
+
setTimeout(resolve, 1_000)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
this.state.password.resolve(password)
|
|
155
|
+
const error = await errorPromise
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
success: error ? false : true,
|
|
159
|
+
error: error || undefined,
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Проверяет, требуется ли пароль для авторизации
|
|
165
|
+
*
|
|
166
|
+
* @returns true, если требуется пароль
|
|
167
|
+
*/
|
|
168
|
+
async isRequiredPassword(): Promise<boolean> {
|
|
169
|
+
if (this.state?.isRequiredPassword) return true
|
|
170
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
171
|
+
return this.state?.isRequiredPassword ?? false
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createWriteStream, WriteStream } from 'fs'
|
|
2
|
+
import { mkdir } from 'fs/promises'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Логгер для отдельной сессии
|
|
7
|
+
* Записывает все действия и активность сессии в отдельный файл
|
|
8
|
+
*/
|
|
9
|
+
export class SessionLogger {
|
|
10
|
+
private logStream: WriteStream | null = null
|
|
11
|
+
private logFilePath: string
|
|
12
|
+
private isInitialized: boolean = false
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param sessionId Идентификатор сессии
|
|
16
|
+
* @param logsDirectory Директория для хранения логов (по умолчанию './logs')
|
|
17
|
+
*/
|
|
18
|
+
constructor(private readonly sessionId: string, private readonly logsDirectory: string = './logs') {
|
|
19
|
+
// Формируем путь к файлу лога
|
|
20
|
+
const sanitizedSessionId = this.sanitizeFileName(sessionId)
|
|
21
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
22
|
+
this.logFilePath = join(this.logsDirectory, `session-${sanitizedSessionId}-${timestamp}.log`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Инициализирует логгер (создает директорию и файл)
|
|
27
|
+
*/
|
|
28
|
+
async initialize(): Promise<void> {
|
|
29
|
+
if (this.isInitialized) return
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Создаем директорию для логов, если её нет
|
|
33
|
+
await mkdir(this.logsDirectory, { recursive: true })
|
|
34
|
+
|
|
35
|
+
// Создаем поток записи в файл (режим append)
|
|
36
|
+
this.logStream = createWriteStream(this.logFilePath, { flags: 'a' })
|
|
37
|
+
|
|
38
|
+
// Записываем заголовок
|
|
39
|
+
this.writeLog('INFO', `Логгер инициализирован для сессии: ${this.sessionId}`)
|
|
40
|
+
this.writeLog('INFO', `Файл лога: ${this.logFilePath}`)
|
|
41
|
+
this.writeLog('INFO', `Время создания: ${new Date().toISOString()}`)
|
|
42
|
+
this.writeLog('INFO', '─'.repeat(80))
|
|
43
|
+
|
|
44
|
+
this.isInitialized = true
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`Ошибка инициализации логгера для сессии ${this.sessionId}:`, error)
|
|
47
|
+
throw error
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Записывает лог-запись в файл
|
|
53
|
+
*
|
|
54
|
+
* @param level Уровень логирования (INFO, WARN, ERROR, DEBUG)
|
|
55
|
+
* @param message Сообщение для логирования
|
|
56
|
+
* @param data Дополнительные данные (опционально)
|
|
57
|
+
*/
|
|
58
|
+
log(level: 'INFO' | 'WARN' | 'ERROR' | 'DEBUG', message: string, data?: any): void {
|
|
59
|
+
if (!this.isInitialized) {
|
|
60
|
+
// Если логгер еще не инициализирован, инициализируем синхронно
|
|
61
|
+
this.initialize().catch((error) => {
|
|
62
|
+
console.error(`Ошибка инициализации логгера:`, error)
|
|
63
|
+
})
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.writeLog(level, message, data)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Логирует информацию
|
|
72
|
+
*/
|
|
73
|
+
info(message: string, data?: any): void {
|
|
74
|
+
this.log('INFO', message, data)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Логирует предупреждение
|
|
79
|
+
*/
|
|
80
|
+
warn(message: string, data?: any): void {
|
|
81
|
+
this.log('WARN', message, data)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Логирует ошибку
|
|
86
|
+
*/
|
|
87
|
+
error(message: string, error?: Error | any): void {
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
89
|
+
const errorStack = error instanceof Error ? error.stack : undefined
|
|
90
|
+
this.log('ERROR', message, { error: errorMessage, stack: errorStack })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Логирует отладочную информацию
|
|
95
|
+
*/
|
|
96
|
+
debug(message: string, data?: any): void {
|
|
97
|
+
this.log('DEBUG', message, data)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Логирует действие сессии
|
|
102
|
+
*
|
|
103
|
+
* @param action Название действия
|
|
104
|
+
* @param details Детали действия
|
|
105
|
+
*/
|
|
106
|
+
logAction(action: string, details?: any): void {
|
|
107
|
+
this.info(`Действие: ${action}`, details)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Логирует событие от Telegram клиента
|
|
112
|
+
*
|
|
113
|
+
* @param eventName Название события
|
|
114
|
+
* @param eventData Данные события
|
|
115
|
+
*/
|
|
116
|
+
logTelegramEvent(eventName: string, eventData?: any): void {
|
|
117
|
+
this.debug(`Telegram событие: ${eventName}`, eventData)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Закрывает поток записи
|
|
122
|
+
*/
|
|
123
|
+
async close(): Promise<void> {
|
|
124
|
+
if (this.logStream) {
|
|
125
|
+
this.writeLog('INFO', '─'.repeat(80))
|
|
126
|
+
this.writeLog('INFO', `Логгер закрыт для сессии: ${this.sessionId}`)
|
|
127
|
+
this.writeLog('INFO', `Время закрытия: ${new Date().toISOString()}`)
|
|
128
|
+
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
this.logStream!.end((error: Error | null | undefined) => {
|
|
131
|
+
if (error) {
|
|
132
|
+
reject(error)
|
|
133
|
+
} else {
|
|
134
|
+
this.logStream = null
|
|
135
|
+
this.isInitialized = false
|
|
136
|
+
resolve()
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Получает путь к файлу лога
|
|
145
|
+
*/
|
|
146
|
+
getLogFilePath(): string {
|
|
147
|
+
return this.logFilePath
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Внутренний метод для записи в файл
|
|
152
|
+
*
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
private writeLog(level: string, message: string, data?: any): void {
|
|
156
|
+
if (!this.logStream) {
|
|
157
|
+
// Если поток еще не создан, выводим в консоль
|
|
158
|
+
console.log(`[${this.sessionId}] [${level}] ${message}`, data || '')
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const timestamp = new Date().toISOString()
|
|
163
|
+
const logEntry: any = {
|
|
164
|
+
timestamp,
|
|
165
|
+
level,
|
|
166
|
+
sessionId: this.sessionId,
|
|
167
|
+
message,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (data !== undefined) {
|
|
171
|
+
logEntry.data = data
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Форматируем запись для удобного чтения
|
|
175
|
+
const logLine = this.formatLogEntry(logEntry)
|
|
176
|
+
this.logStream.write(logLine + '\n')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Форматирует запись лога для записи в файл
|
|
181
|
+
*
|
|
182
|
+
* @private
|
|
183
|
+
*/
|
|
184
|
+
private formatLogEntry(entry: any): string {
|
|
185
|
+
const { timestamp, level, message, data } = entry
|
|
186
|
+
let logLine = `[${timestamp}] [${level}] ${message}`
|
|
187
|
+
|
|
188
|
+
if (data !== undefined) {
|
|
189
|
+
try {
|
|
190
|
+
const dataStr = typeof data === 'string' ? data : JSON.stringify(data, null, 2)
|
|
191
|
+
logLine += `\n${dataStr}`
|
|
192
|
+
} catch (error) {
|
|
193
|
+
logLine += `\n[Не удалось сериализовать данные]`
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return logLine
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Очищает имя файла от недопустимых символов
|
|
202
|
+
*
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
private sanitizeFileName(fileName: string): string {
|
|
206
|
+
return fileName.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { EventEmitter } from 'events'
|
|
2
|
+
import { TelegramClient } from 'telegram'
|
|
3
|
+
import { UpdateConnectionState } from 'telegram/network'
|
|
4
|
+
import { StringSession } from 'telegram/sessions'
|
|
5
|
+
import { EvogramGramJS } from '../EvogramGramJS'
|
|
6
|
+
import { DatabaseService } from '../services/DatabaseService'
|
|
7
|
+
import { SessionConfig, SessionEvent, SessionEventData, SessionInfo, SessionInvalidEventData } from '../types/session.types'
|
|
8
|
+
import { Session } from './Session'
|
|
9
|
+
import { SessionAuth } from './SessionAuth'
|
|
10
|
+
import { SessionLogger } from './SessionLogger'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Сервис для управления множеством сессий Telegram клиентов
|
|
14
|
+
*
|
|
15
|
+
* Предоставляет удобный API для:
|
|
16
|
+
* - Добавления и удаления сессий
|
|
17
|
+
* - Получения информации о сессиях
|
|
18
|
+
* - Отслеживания событий (подключение, отключение, ошибки)
|
|
19
|
+
* - Сохранения данных в базе данных
|
|
20
|
+
*/
|
|
21
|
+
export class SessionManager extends EventEmitter {
|
|
22
|
+
constructor(private readonly TELEGRAM_APP_ID: number, private readonly TELEGRAM_APP_HASH: string, private readonly databaseService?: DatabaseService) {
|
|
23
|
+
super()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Хранилище всех активных сессий */
|
|
27
|
+
private sessions: Map<string, SessionInfo> = new Map()
|
|
28
|
+
|
|
29
|
+
public async initialize(): Promise<void> {
|
|
30
|
+
const sessions = await this.databaseService?.getSessions()
|
|
31
|
+
if (!sessions || sessions.length === 0) return
|
|
32
|
+
|
|
33
|
+
for (const session of sessions) {
|
|
34
|
+
await this.addSession({
|
|
35
|
+
sessionId: session.sessionId,
|
|
36
|
+
sessionString: session.sessionString,
|
|
37
|
+
apiId: session.apiId,
|
|
38
|
+
apiHash: session.apiHash,
|
|
39
|
+
clientOptions: session.clientOptions ? JSON.parse(session.clientOptions) : undefined,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Добавляет новую сессию
|
|
46
|
+
*
|
|
47
|
+
* @param config Конфигурация сессии
|
|
48
|
+
* @returns Promise с информацией о созданной сессии
|
|
49
|
+
* @throws Error если сессия с таким ID уже существует
|
|
50
|
+
*/
|
|
51
|
+
async addSession(config: SessionConfig): Promise<SessionInfo> {
|
|
52
|
+
// Проверяем, не существует ли уже сессия с таким ID в памяти
|
|
53
|
+
if (this.sessions.has(config.sessionId)) return this.sessions.get(config.sessionId)!
|
|
54
|
+
|
|
55
|
+
// Создаем строковую сессию (если не предоставлена, создается пустая)
|
|
56
|
+
const session = new StringSession(config.sessionString || '')
|
|
57
|
+
|
|
58
|
+
// Создаем Telegram клиент
|
|
59
|
+
const client = new TelegramClient(session, config.apiId ?? this.TELEGRAM_APP_ID, config.apiHash ?? this.TELEGRAM_APP_HASH, {
|
|
60
|
+
useIPV6: config.clientOptions?.useIPV6 ?? false,
|
|
61
|
+
connectionRetries: config.clientOptions?.connectionRetries ?? 5,
|
|
62
|
+
})
|
|
63
|
+
await client.connect()
|
|
64
|
+
|
|
65
|
+
client.addEventHandler(async (event) => {
|
|
66
|
+
if ((await this.getSession(config.sessionId)?.db())?.error) return
|
|
67
|
+
|
|
68
|
+
if (event instanceof UpdateConnectionState) {
|
|
69
|
+
const error = await client
|
|
70
|
+
.getMe()
|
|
71
|
+
.then(() => undefined)
|
|
72
|
+
.catch((error) => error)
|
|
73
|
+
|
|
74
|
+
if (error) {
|
|
75
|
+
this.emit(SessionEvent.SESSION_INVALID, {
|
|
76
|
+
sessionId: config.sessionId,
|
|
77
|
+
error: error,
|
|
78
|
+
} as SessionInvalidEventData)
|
|
79
|
+
|
|
80
|
+
await EvogramGramJS.databaseService.updateSession(config.sessionId, { error: error.errorMessage })
|
|
81
|
+
await this.disconnectSession(config.sessionId)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const sessionAuth = new SessionAuth(config.sessionId, client)
|
|
87
|
+
|
|
88
|
+
const sessionInfo = new Session(config.sessionId, client, undefined, sessionAuth)
|
|
89
|
+
this.sessions.set(config.sessionId, sessionInfo)
|
|
90
|
+
|
|
91
|
+
// Генерируем событие добавления сессии
|
|
92
|
+
this.emit(SessionEvent.SESSION_ADDED, {
|
|
93
|
+
sessionId: config.sessionId,
|
|
94
|
+
sessionInfo,
|
|
95
|
+
} as SessionEventData)
|
|
96
|
+
|
|
97
|
+
return sessionInfo
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async disconnectSession(sessionId: string): Promise<void> {
|
|
101
|
+
const session = this.sessions.get(sessionId)
|
|
102
|
+
if (!session) throw new Error(`Сессия с ID ${sessionId} не найдена`)
|
|
103
|
+
|
|
104
|
+
await session.client.disconnect()
|
|
105
|
+
this.emit(SessionEvent.SESSION_DISCONNECTED, {
|
|
106
|
+
sessionId: sessionId,
|
|
107
|
+
} as SessionEventData)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async getActiveSessions(): Promise<SessionInfo[]> {
|
|
111
|
+
const sessions = Array.from(this.sessions.values())
|
|
112
|
+
|
|
113
|
+
const results = await Promise.all(
|
|
114
|
+
sessions.map(async (session) => {
|
|
115
|
+
const dbData = await session.db()
|
|
116
|
+
return { session, hasError: !!dbData?.error }
|
|
117
|
+
})
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return results.filter((result) => !result.hasError).map((result) => result.session)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Получает информацию о сессии по ID
|
|
125
|
+
*
|
|
126
|
+
* @param sessionId Идентификатор сессии
|
|
127
|
+
* @returns Информация о сессии или undefined, если не найдена
|
|
128
|
+
*/
|
|
129
|
+
getSession(sessionId: string): SessionInfo | undefined {
|
|
130
|
+
const sessionInfo = this.sessions.get(sessionId)
|
|
131
|
+
return sessionInfo
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Получает Telegram клиент по ID сессии
|
|
136
|
+
*
|
|
137
|
+
* @param sessionId Идентификатор сессии
|
|
138
|
+
* @returns Telegram клиент или undefined, если сессия не найдена
|
|
139
|
+
*/
|
|
140
|
+
getClient(sessionId: string): TelegramClient | undefined {
|
|
141
|
+
return this.getSession(sessionId)?.client
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Получает логгер сессии по ID
|
|
146
|
+
*
|
|
147
|
+
* @param sessionId Идентификатор сессии
|
|
148
|
+
* @returns Логгер сессии или undefined, если сессия не найдена или логирование отключено
|
|
149
|
+
*/
|
|
150
|
+
getLogger(sessionId: string): SessionLogger | undefined {
|
|
151
|
+
return this.getSession(sessionId)?.logger
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Получает все активные сессии
|
|
156
|
+
*
|
|
157
|
+
* @returns Массив информации о всех сессиях
|
|
158
|
+
*/
|
|
159
|
+
getAllSessions(): SessionInfo[] {
|
|
160
|
+
return Array.from(this.sessions.values())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Получает список всех ID сессий
|
|
165
|
+
*
|
|
166
|
+
* @returns Массив идентификаторов сессий
|
|
167
|
+
*/
|
|
168
|
+
getAllSessionIds(): string[] {
|
|
169
|
+
return Array.from(this.sessions.keys())
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Проверяет, существует ли сессия с указанным ID
|
|
174
|
+
*
|
|
175
|
+
* @param sessionId Идентификатор сессии
|
|
176
|
+
* @returns true, если сессия существует, иначе false
|
|
177
|
+
*/
|
|
178
|
+
hasSession(sessionId: string): boolean {
|
|
179
|
+
return this.sessions.has(sessionId)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Получает количество активных сессий
|
|
184
|
+
*
|
|
185
|
+
* @returns Количество сессий
|
|
186
|
+
*/
|
|
187
|
+
getSessionCount(): number {
|
|
188
|
+
return this.sessions.size
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Состояния процесса авторизации
|
|
3
|
+
*/
|
|
4
|
+
export enum AuthState {
|
|
5
|
+
/** Начальное состояние - не авторизован */
|
|
6
|
+
INITIAL = 'initial',
|
|
7
|
+
/** Ожидание кода подтверждения */
|
|
8
|
+
WAITING_CODE = 'waiting_code',
|
|
9
|
+
/** Ожидание пароля (2FA) */
|
|
10
|
+
WAITING_PASSWORD = 'waiting_password',
|
|
11
|
+
/** Авторизация завершена */
|
|
12
|
+
AUTHORIZED = 'authorized',
|
|
13
|
+
/** Ошибка авторизации */
|
|
14
|
+
ERROR = 'error',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Информация о процессе авторизации
|
|
19
|
+
*/
|
|
20
|
+
export interface AuthInfo {
|
|
21
|
+
/** Идентификатор сессии */
|
|
22
|
+
sessionId: string
|
|
23
|
+
/** Текущее состояние авторизации */
|
|
24
|
+
state: AuthState
|
|
25
|
+
/** Номер телефона (если указан) */
|
|
26
|
+
phoneNumber?: string
|
|
27
|
+
/** Требуется ли пароль (2FA) */
|
|
28
|
+
isRequiredPassword: boolean
|
|
29
|
+
/** Код подтверждения (если ожидается) */
|
|
30
|
+
code?: string
|
|
31
|
+
/** Пароль (2FA) (если ожидается) */
|
|
32
|
+
password?: string
|
|
33
|
+
/** Хеш кода подтверждения */
|
|
34
|
+
phoneCodeHash?: string
|
|
35
|
+
/** Сообщение об ошибке (если есть) */
|
|
36
|
+
error?: string
|
|
37
|
+
/** Время начала авторизации */
|
|
38
|
+
startedAt: Date
|
|
39
|
+
/** Время последнего обновления */
|
|
40
|
+
updatedAt: Date
|
|
41
|
+
/** Промисы для ожидания кода и пароля */
|
|
42
|
+
_codeResolver?: (code: string) => void
|
|
43
|
+
_codeRejecter?: (error: Error) => void
|
|
44
|
+
_passwordResolver?: (password: string) => void
|
|
45
|
+
_passwordRejecter?: (error: Error) => void
|
|
46
|
+
/** Промис для отслеживания начальной ошибки (до запроса кода) */
|
|
47
|
+
_initialErrorRejecter?: (error: Error) => void
|
|
48
|
+
/** Resolver для промиса, когда код был запрошен (успешное начало) */
|
|
49
|
+
_codeRequestedResolver?: () => void
|
|
50
|
+
/** Флаг, что код был запрошен (для различения начальной ошибки от ошибки после запроса кода) */
|
|
51
|
+
_codeRequested?: boolean
|
|
52
|
+
/** Флаг, что была ошибка неверного кода (для отслеживания повторной попытки) */
|
|
53
|
+
_hasCodeError?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Результат отправки кода
|
|
58
|
+
*/
|
|
59
|
+
export interface SendCodeResult {
|
|
60
|
+
/** Успешно ли отправлен код */
|
|
61
|
+
success: boolean
|
|
62
|
+
/** Требуется ли пароль (2FA) */
|
|
63
|
+
isRequiredPassword: boolean
|
|
64
|
+
/** Сообщение об ошибке (если есть) */
|
|
65
|
+
error?: string
|
|
66
|
+
/** Тип отправки кода (sms, call, etc.) */
|
|
67
|
+
phoneCodeHash?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Результат ввода кода
|
|
72
|
+
*/
|
|
73
|
+
export interface SignInResult {
|
|
74
|
+
/** Успешно ли авторизован */
|
|
75
|
+
success: boolean
|
|
76
|
+
/** Требуется ли пароль (2FA) */
|
|
77
|
+
isRequiredPassword: boolean
|
|
78
|
+
/** Сообщение об ошибке (если есть) */
|
|
79
|
+
error?: string
|
|
80
|
+
/** Строка сессии (если авторизация успешна) */
|
|
81
|
+
sessionString?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Результат ввода пароля
|
|
86
|
+
*/
|
|
87
|
+
export interface SignInWithPasswordResult {
|
|
88
|
+
/** Успешно ли авторизован */
|
|
89
|
+
success: boolean
|
|
90
|
+
/** Сообщение об ошибке (если есть) */
|
|
91
|
+
error?: string
|
|
92
|
+
/** Строка сессии (если авторизация успешна) */
|
|
93
|
+
sessionString?: string
|
|
94
|
+
}
|