|
|
@@ -20,11 +20,16 @@ export class TgClientService {
|
|
|
private apiHash: string
|
|
|
private initPromise: Promise<void> | null = null
|
|
|
private readonly defaultConnectionOptions = {
|
|
|
- connectionRetries: 5,
|
|
|
- retryDelay: 2000,
|
|
|
- requestRetries: 5,
|
|
|
+ connectionRetries: 2,
|
|
|
+ retryDelay: 1500,
|
|
|
+ requestRetries: 3,
|
|
|
useWSS: true
|
|
|
}
|
|
|
+ private readonly connectTimeoutMs = 15000
|
|
|
+ private readonly connectionStrategies = [
|
|
|
+ { label: 'WSS', options: { useWSS: true } },
|
|
|
+ { label: 'TCP', options: { useWSS: false } }
|
|
|
+ ]
|
|
|
private activeClients: Set<TelegramClient> = new Set()
|
|
|
|
|
|
private constructor() {}
|
|
|
@@ -61,7 +66,7 @@ export class TgClientService {
|
|
|
return this.client
|
|
|
}
|
|
|
|
|
|
- if (this.client && this.client.connected) {
|
|
|
+ if (this.client) {
|
|
|
await this.disconnect()
|
|
|
}
|
|
|
|
|
|
@@ -75,67 +80,62 @@ export class TgClientService {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- if (this.client.connected) {
|
|
|
- await this.client.disconnect()
|
|
|
- this.app.log.info('TelegramClient 已断开连接')
|
|
|
- await this.client.destroy()
|
|
|
- this.app.log.info('TelegramClient 已销毁')
|
|
|
- }
|
|
|
- this.activeClients.delete(this.client)
|
|
|
- this.logActiveClientCount()
|
|
|
- } catch (error) {
|
|
|
- const errorMessage = error instanceof Error ? error.message : String(error)
|
|
|
- if (!errorMessage.includes('TIMEOUT')) {
|
|
|
- this.app.log.error({ msg: '断开连接时发生错误', error: errorMessage })
|
|
|
- }
|
|
|
- } finally {
|
|
|
- this.client = null
|
|
|
- this.currentSession = null
|
|
|
- }
|
|
|
+ await this.disposeClient(this.client, 'TelegramClient')
|
|
|
+ this.client = null
|
|
|
+ this.currentSession = null
|
|
|
}
|
|
|
|
|
|
async createConnectedClient(sessionString: string): Promise<TelegramClient> {
|
|
|
await this.initializeApp()
|
|
|
|
|
|
const stringSession = new StringSession(sessionString)
|
|
|
- const connectionOptions = { ...this.defaultConnectionOptions }
|
|
|
+ let lastError: unknown = null
|
|
|
|
|
|
- this.app.log.info(
|
|
|
- `正在建立连接... useWSS=${connectionOptions.useWSS ? 'true' : 'false'} (443 优先), retries=${
|
|
|
- connectionOptions.connectionRetries
|
|
|
- }`
|
|
|
- )
|
|
|
-
|
|
|
- const client = new TelegramClient(stringSession, this.apiId, this.apiHash, connectionOptions)
|
|
|
+ for (const strategy of this.connectionStrategies) {
|
|
|
+ const connectionOptions = { ...this.defaultConnectionOptions, ...strategy.options }
|
|
|
+ this.app.log.info(
|
|
|
+ `正在建立连接[${strategy.label}]... useWSS=${connectionOptions.useWSS ? 'true' : 'false'} (443 优先), retries=${
|
|
|
+ connectionOptions.connectionRetries
|
|
|
+ }, retryDelay=${connectionOptions.retryDelay}ms`
|
|
|
+ )
|
|
|
|
|
|
- try {
|
|
|
- await client.connect()
|
|
|
- } catch (err) {
|
|
|
- const errorMessage = err instanceof Error ? err.message : String(err)
|
|
|
- this.app.log.error({ msg: '连接 Telegram 失败', error: errorMessage, useWSS: connectionOptions.useWSS })
|
|
|
- throw err
|
|
|
- }
|
|
|
+ const client = new TelegramClient(stringSession, this.apiId, this.apiHash, connectionOptions)
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.connectWithTimeout(client)
|
|
|
+
|
|
|
+ const me = await this.ensureValidSession(client)
|
|
|
+ if (me) {
|
|
|
+ this.app.log.info(
|
|
|
+ `当前登录账号: id: ${me.id} ,name: ${`${me.firstName || ''} ${me.lastName || ''}`.trim()} ${me.username || ''}`.trim()
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ this.app.log.warn('无法获取账号信息')
|
|
|
+ }
|
|
|
+
|
|
|
+ this.activeClients.add(client)
|
|
|
+ this.logActiveClientCount()
|
|
|
+ return client
|
|
|
+ } catch (error) {
|
|
|
+ lastError = error
|
|
|
+ const errorMessage = this.extractErrorMessage(error)
|
|
|
+ this.app.log.error({
|
|
|
+ msg: `连接 Telegram 失败 (${strategy.label})`,
|
|
|
+ error: errorMessage,
|
|
|
+ useWSS: connectionOptions.useWSS
|
|
|
+ })
|
|
|
|
|
|
- if (!client.connected) {
|
|
|
- throw new Error('TelegramClient 连接失败,请检查网络或 Session 是否有效')
|
|
|
- }
|
|
|
+ await this.disposeClient(client, `TelegramClient(${strategy.label})`)
|
|
|
|
|
|
- this.app.log.info('TelegramClient 连接成功,正在获取账号信息...')
|
|
|
- const me = await client.getMe()
|
|
|
- if (me) {
|
|
|
- this.app.log.info(
|
|
|
- `当前登录账号: id: ${me.id} ,name: ${me.firstName || ''} ${me.lastName || ''} ${me.username || ''}`.trim()
|
|
|
- )
|
|
|
- } else {
|
|
|
- this.app.log.warn('无法获取账号信息')
|
|
|
+ if (this.isSessionRevokedError(error)) {
|
|
|
+ throw new Error('Telegram Session 已失效或被吊销,请重新登录生成新的 session 字符串')
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- this.app.log.info('账号信息获取完成')
|
|
|
|
|
|
- this.activeClients.add(client)
|
|
|
- this.logActiveClientCount()
|
|
|
-
|
|
|
- return client
|
|
|
+ throw new Error(
|
|
|
+ `TelegramClient 连接失败,已尝试 WSS/TCP,最后错误: ${this.extractErrorMessage(lastError) || '未知'}`
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
async disconnectClient(client: TelegramClient | null): Promise<void> {
|
|
|
@@ -143,21 +143,7 @@ export class TgClientService {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- if (client.connected) {
|
|
|
- await client.disconnect()
|
|
|
- this.app.log.info('TelegramClient 已断开连接')
|
|
|
- }
|
|
|
- await client.destroy()
|
|
|
- this.app.log.info('TelegramClient 已销毁')
|
|
|
- this.activeClients.delete(client)
|
|
|
- this.logActiveClientCount()
|
|
|
- } catch (error) {
|
|
|
- const errorMessage = error instanceof Error ? error.message : String(error)
|
|
|
- if (!errorMessage.includes('TIMEOUT')) {
|
|
|
- this.app.log.error({ msg: '断开连接时发生错误', error: errorMessage })
|
|
|
- }
|
|
|
- }
|
|
|
+ await this.disposeClient(client, 'TelegramClient')
|
|
|
}
|
|
|
|
|
|
getActiveClientCount(): number {
|
|
|
@@ -276,6 +262,81 @@ export class TgClientService {
|
|
|
return '未知错误'
|
|
|
}
|
|
|
|
|
|
+ private isSessionRevokedError(error: unknown): boolean {
|
|
|
+ const msg = this.extractErrorMessage(error)
|
|
|
+ return (
|
|
|
+ msg.includes('SESSION_REVOKED') ||
|
|
|
+ msg.includes('AUTH_KEY_UNREGISTERED') ||
|
|
|
+ msg.includes('AUTH_KEY_INVALID')
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ private async connectWithTimeout(client: TelegramClient): Promise<void> {
|
|
|
+ let timer: NodeJS.Timeout | null = null
|
|
|
+
|
|
|
+ try {
|
|
|
+ await Promise.race([
|
|
|
+ client.connect(),
|
|
|
+ new Promise((_, reject) => {
|
|
|
+ timer = setTimeout(() => reject(new Error(`连接超时(${this.connectTimeoutMs}ms)`)), this.connectTimeoutMs)
|
|
|
+ })
|
|
|
+ ])
|
|
|
+ } finally {
|
|
|
+ if (timer) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!client.connected) {
|
|
|
+ throw new Error('TelegramClient 连接失败,请检查网络或 Session 是否有效')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async ensureValidSession(client: TelegramClient): Promise<any> {
|
|
|
+ try {
|
|
|
+ this.app.log.info('TelegramClient 连接成功,正在获取账号信息...')
|
|
|
+ return await client.getMe()
|
|
|
+ } catch (error) {
|
|
|
+ if (this.isSessionRevokedError(error)) {
|
|
|
+ throw new Error('Telegram Session 已失效或被吊销,需要重新登录获取新的 session')
|
|
|
+ }
|
|
|
+ throw error
|
|
|
+ } finally {
|
|
|
+ this.app.log.info('账号信息获取完成')
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private async disposeClient(client: TelegramClient | null, context: string): Promise<void> {
|
|
|
+ if (!client) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (client.connected) {
|
|
|
+ await client.disconnect()
|
|
|
+ this.app.log.info(`${context} 已断开连接`)
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ const errorMessage = this.extractErrorMessage(error)
|
|
|
+ if (!errorMessage.includes('TIMEOUT')) {
|
|
|
+ this.app.log.error({ msg: `${context} 断开时发生错误`, error: errorMessage })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await client.destroy()
|
|
|
+ this.app.log.info(`${context} 已销毁`)
|
|
|
+ } catch (error) {
|
|
|
+ const errorMessage = this.extractErrorMessage(error)
|
|
|
+ if (!errorMessage.includes('TIMEOUT')) {
|
|
|
+ this.app.log.error({ msg: `${context} 销毁时发生错误`, error: errorMessage })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.activeClients.delete(client)
|
|
|
+ this.logActiveClientCount()
|
|
|
+ }
|
|
|
+
|
|
|
private logTargetInfo(targetPeer: any): void {
|
|
|
const entityInfo = targetPeer as any
|
|
|
const logData: any = {
|