evogram-gramjs 1.0.3 → 1.1.1
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 → 8539886557}/database.db +0 -0
- package/evogramjs.sqlite +0 -0
- package/lib/commands/AddAccount.command.d.ts +13 -0
- package/lib/commands/AddAccount.command.js +303 -12
- package/lib/commands/index.d.ts +2 -0
- package/lib/commands/index.js +4 -0
- package/lib/sessions/SessionAuth.d.ts +10 -0
- package/lib/sessions/SessionAuth.js +162 -0
- package/lib/test.d.ts +1 -0
- package/lib/test.js +144 -0
- package/package-lock.json +3938 -4
- package/package.json +4 -3
- package/qr-code-qr-auth-1769097743188.png +0 -0
- package/src/EvogramGramJS.ts +0 -98
- package/src/commands/Accounts.command.ts +0 -89
- package/src/commands/AddAccount.command.ts +0 -171
- package/src/commands/index.ts +0 -0
- package/src/commands/managment/DeleteAccount.command.ts +0 -13
- package/src/config/database.config.ts +0 -75
- package/src/entities/Session.entity.ts +0 -58
- package/src/entities/SessionEventLog.entity.ts +0 -41
- package/src/index.ts +0 -7
- package/src/services/DatabaseService.ts +0 -82
- package/src/services/ImageUploadService.ts +0 -49
- package/src/sessions/Session.ts +0 -21
- package/src/sessions/SessionAuth.ts +0 -173
- package/src/sessions/SessionLogger.ts +0 -208
- package/src/sessions/SessionManager.ts +0 -211
- package/src/types/auth.types.ts +0 -94
- package/src/types/session.types.ts +0 -96
- package/src/utils/Deferrer.ts +0 -12
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import axios from 'axios'
|
|
2
|
-
import FormData from 'form-data'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Сервис для загрузки изображений на внешние хостинги
|
|
6
|
-
*/
|
|
7
|
-
export class ImageUploadService {
|
|
8
|
-
static readonly UPLOAD_URL = 'https://imgbox.vu/uploads'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Загружает изображение на imgbox.vu/uploads
|
|
12
|
-
*
|
|
13
|
-
* @param imageBuffer Буфер изображения
|
|
14
|
-
* @param filename Имя файла (опционально)
|
|
15
|
-
* @returns Promise с URL загруженного изображения
|
|
16
|
-
* @throws Error если загрузка не удалась
|
|
17
|
-
*/
|
|
18
|
-
static async uploadToImgbox(imageBuffer: Buffer, filename?: string): Promise<string> {
|
|
19
|
-
try {
|
|
20
|
-
const formData = new FormData()
|
|
21
|
-
formData.append('file', imageBuffer, {
|
|
22
|
-
filename: filename || 'avatar.jpg',
|
|
23
|
-
contentType: 'image/jpeg',
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
const response = await axios.post(this.UPLOAD_URL, formData, {
|
|
27
|
-
headers: formData.getHeaders(),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const result = response.data
|
|
31
|
-
|
|
32
|
-
// Проверяем формат ответа imgbox.vu
|
|
33
|
-
// API может возвращать URL в разных форматах, нужно проверить структуру ответа
|
|
34
|
-
if (result.url) {
|
|
35
|
-
return result.url
|
|
36
|
-
} else if (result.data?.url) {
|
|
37
|
-
return result.data.url
|
|
38
|
-
} else if (typeof result === 'string' && result.startsWith('http')) {
|
|
39
|
-
return result
|
|
40
|
-
} else {
|
|
41
|
-
// Если формат ответа неизвестен, возвращаем весь ответ как строку для отладки
|
|
42
|
-
throw new Error(`Неожиданный формат ответа от imgbox.vu: ${JSON.stringify(result)}`)
|
|
43
|
-
}
|
|
44
|
-
} catch (error: any) {
|
|
45
|
-
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
46
|
-
throw new Error(`Ошибка загрузки изображения на imgbox.vu: ${errorMessage}`)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
package/src/sessions/Session.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { TelegramClient } from 'telegram'
|
|
2
|
-
import { EvogramGramJS } from '../EvogramGramJS'
|
|
3
|
-
import { SessionAuth } from './SessionAuth'
|
|
4
|
-
import { SessionLogger } from './SessionLogger'
|
|
5
|
-
|
|
6
|
-
export class Session {
|
|
7
|
-
/** Объект авторизации для сессии */
|
|
8
|
-
public auth: SessionAuth
|
|
9
|
-
|
|
10
|
-
constructor(public readonly sessionId: string, public readonly client: TelegramClient, public readonly logger: SessionLogger | undefined, auth: SessionAuth) {
|
|
11
|
-
this.auth = auth
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
public async db() {
|
|
15
|
-
return (await EvogramGramJS.databaseService.getSession(this.sessionId)) ?? null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
public async user() {
|
|
19
|
-
return await this.client.getMe()
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,208 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events'
|
|
2
|
-
import { Api, TelegramClient } from 'telegram'
|
|
3
|
-
import { UpdateConnectionState } from 'telegram/network'
|
|
4
|
-
import { StringSession } from 'telegram/sessions'
|
|
5
|
-
import { EvogramGramJS } from '../EvogramGramJS'
|
|
6
|
-
import { Session as SessionEntity } from '../entities/Session.entity'
|
|
7
|
-
import { DatabaseService } from '../services/DatabaseService'
|
|
8
|
-
import { SessionConfig, SessionEvent, SessionEventData, SessionInfo, SessionInvalidEventData } from '../types/session.types'
|
|
9
|
-
import { Session } from './Session'
|
|
10
|
-
import { SessionAuth } from './SessionAuth'
|
|
11
|
-
import { SessionLogger } from './SessionLogger'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Сервис для управления множеством сессий Telegram клиентов
|
|
15
|
-
*
|
|
16
|
-
* Предоставляет удобный API для:
|
|
17
|
-
* - Добавления и удаления сессий
|
|
18
|
-
* - Получения информации о сессиях
|
|
19
|
-
* - Отслеживания событий (подключение, отключение, ошибки)
|
|
20
|
-
* - Сохранения данных в базе данных
|
|
21
|
-
*/
|
|
22
|
-
export class SessionManager extends EventEmitter {
|
|
23
|
-
constructor(private readonly TELEGRAM_APP_ID: number, private readonly TELEGRAM_APP_HASH: string, private readonly databaseService?: DatabaseService) {
|
|
24
|
-
super()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Хранилище всех активных сессий */
|
|
28
|
-
private sessions: Map<string, SessionInfo> = new Map()
|
|
29
|
-
|
|
30
|
-
public async initialize(): Promise<void> {
|
|
31
|
-
const sessions = await this.databaseService?.getSessions()
|
|
32
|
-
if (!sessions || sessions.length === 0) return
|
|
33
|
-
|
|
34
|
-
for (const session of sessions) {
|
|
35
|
-
await this.addSession({
|
|
36
|
-
sessionId: session.sessionId,
|
|
37
|
-
sessionString: session.sessionString,
|
|
38
|
-
apiId: session.apiId,
|
|
39
|
-
apiHash: session.apiHash,
|
|
40
|
-
clientOptions: session.clientOptions ? JSON.parse(session.clientOptions) : undefined,
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Добавляет новую сессию
|
|
47
|
-
*
|
|
48
|
-
* @param config Конфигурация сессии
|
|
49
|
-
* @returns Promise с информацией о созданной сессии
|
|
50
|
-
* @throws Error если сессия с таким ID уже существует
|
|
51
|
-
*/
|
|
52
|
-
async addSession(config: SessionConfig): Promise<SessionInfo> {
|
|
53
|
-
// Проверяем, не существует ли уже сессия с таким ID в памяти
|
|
54
|
-
if (this.sessions.has(config.sessionId)) return this.sessions.get(config.sessionId)!
|
|
55
|
-
|
|
56
|
-
// Создаем строковую сессию (если не предоставлена, создается пустая)
|
|
57
|
-
const session = new StringSession(config.sessionString || '')
|
|
58
|
-
|
|
59
|
-
// Создаем Telegram клиент
|
|
60
|
-
const client = new TelegramClient(session, config.apiId ?? this.TELEGRAM_APP_ID, config.apiHash ?? this.TELEGRAM_APP_HASH, {
|
|
61
|
-
useIPV6: config.clientOptions?.useIPV6 ?? false,
|
|
62
|
-
connectionRetries: config.clientOptions?.connectionRetries ?? 5,
|
|
63
|
-
})
|
|
64
|
-
await client.connect()
|
|
65
|
-
|
|
66
|
-
client.addEventHandler(async (event) => {
|
|
67
|
-
if ((await this.getSession(config.sessionId)?.db())?.error) return
|
|
68
|
-
else if (!(await this.getSession(config.sessionId)?.db())?.sessionString) return
|
|
69
|
-
|
|
70
|
-
if (event instanceof UpdateConnectionState) {
|
|
71
|
-
const error = await client
|
|
72
|
-
.getMe()
|
|
73
|
-
.then(() => undefined)
|
|
74
|
-
.catch((error) => error)
|
|
75
|
-
|
|
76
|
-
if (error) {
|
|
77
|
-
this.emit(SessionEvent.SESSION_INVALID, {
|
|
78
|
-
sessionId: config.sessionId,
|
|
79
|
-
error: error,
|
|
80
|
-
} as SessionInvalidEventData)
|
|
81
|
-
|
|
82
|
-
await EvogramGramJS.databaseService.updateSession(config.sessionId, { error: error.errorMessage })
|
|
83
|
-
await this.disconnectSession(config.sessionId)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const sessionAuth = new SessionAuth(config.sessionId, client)
|
|
89
|
-
|
|
90
|
-
const sessionInfo = new Session(config.sessionId, client, undefined, sessionAuth)
|
|
91
|
-
this.sessions.set(config.sessionId, sessionInfo)
|
|
92
|
-
|
|
93
|
-
// Генерируем событие добавления сессии
|
|
94
|
-
this.emit(SessionEvent.SESSION_ADDED, {
|
|
95
|
-
sessionId: config.sessionId,
|
|
96
|
-
sessionInfo,
|
|
97
|
-
} as SessionEventData)
|
|
98
|
-
|
|
99
|
-
return sessionInfo
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async disconnectSession(sessionId: string): Promise<void> {
|
|
103
|
-
const session = this.sessions.get(sessionId)
|
|
104
|
-
if (!session) throw new Error(`Сессия с ID ${sessionId} не найдена`)
|
|
105
|
-
|
|
106
|
-
await session.client.disconnect()
|
|
107
|
-
this.emit(SessionEvent.SESSION_DISCONNECTED, {
|
|
108
|
-
sessionId: sessionId,
|
|
109
|
-
} as SessionEventData)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async getActiveSessions(): Promise<SessionInfo[]> {
|
|
113
|
-
const sessions = Array.from(this.sessions.values())
|
|
114
|
-
|
|
115
|
-
const results = await Promise.all(
|
|
116
|
-
sessions.map(async (session) => {
|
|
117
|
-
const dbData = await session.db()
|
|
118
|
-
return { session, hasError: !!dbData?.error }
|
|
119
|
-
})
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
return results.filter((result) => !result.hasError).map((result) => result.session)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Получает информацию о сессии по ID
|
|
127
|
-
*
|
|
128
|
-
* @param sessionId Идентификатор сессии
|
|
129
|
-
* @returns Информация о сессии или undefined, если не найдена
|
|
130
|
-
*/
|
|
131
|
-
getSession(sessionId: string): SessionInfo | undefined {
|
|
132
|
-
const sessionInfo = this.sessions.get(sessionId)
|
|
133
|
-
return sessionInfo
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Получает Telegram клиент по ID сессии
|
|
138
|
-
*
|
|
139
|
-
* @param sessionId Идентификатор сессии
|
|
140
|
-
* @returns Telegram клиент или undefined, если сессия не найдена
|
|
141
|
-
*/
|
|
142
|
-
getClient(sessionId: string): TelegramClient | undefined {
|
|
143
|
-
return this.getSession(sessionId)?.client
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Получает логгер сессии по ID
|
|
148
|
-
*
|
|
149
|
-
* @param sessionId Идентификатор сессии
|
|
150
|
-
* @returns Логгер сессии или undefined, если сессия не найдена или логирование отключено
|
|
151
|
-
*/
|
|
152
|
-
getLogger(sessionId: string): SessionLogger | undefined {
|
|
153
|
-
return this.getSession(sessionId)?.logger
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Получает все активные сессии
|
|
158
|
-
*
|
|
159
|
-
* @returns Массив информации о всех сессиях
|
|
160
|
-
*/
|
|
161
|
-
getAllSessions(): SessionInfo[] {
|
|
162
|
-
return Array.from(this.sessions.values())
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async getAllLoadedSessions(): Promise<({ db: SessionEntity | null; user: Api.User | null } & Omit<SessionInfo, 'db' | 'user'>)[]> {
|
|
166
|
-
const sessions = Array.from(this.sessions.values())
|
|
167
|
-
|
|
168
|
-
const results = await Promise.all(
|
|
169
|
-
sessions
|
|
170
|
-
.filter((x) => x.sessionId)
|
|
171
|
-
.map(async (session) => {
|
|
172
|
-
return {
|
|
173
|
-
...session,
|
|
174
|
-
db: await session.db().catch(() => null),
|
|
175
|
-
user: await session.user().catch(() => null),
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
//@ts-ignore
|
|
181
|
-
return results
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Получает список всех ID сессий
|
|
186
|
-
*
|
|
187
|
-
* @returns Массив идентификаторов сессий
|
|
188
|
-
*/
|
|
189
|
-
getAllSessionIds(): string[] {
|
|
190
|
-
return Array.from(this.sessions.keys())
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Проверяет, существует ли сессия с указанным ID
|
|
195
|
-
*
|
|
196
|
-
* @param sessionId Идентификатор сессии
|
|
197
|
-
* @returns true, если сессия существует, иначе false
|
|
198
|
-
*/
|
|
199
|
-
hasSession(sessionId: string): boolean {
|
|
200
|
-
return this.sessions.has(sessionId)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Получает количество активных сессий
|
|
205
|
-
*
|
|
206
|
-
* @returns Количество сессий
|
|
207
|
-
*/
|
|
208
|
-
getSessionCount(): number {
|
|
209
|
-
return this.sessions.size
|
|
210
|
-
}
|
|
211
|
-
}
|