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.
@@ -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
- }
@@ -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
- }