Explorar el Código

TelegramClient 发送消息

wuyi hace 2 meses
padre
commit
d3a467081b

+ 5 - 1
.env

@@ -21,4 +21,8 @@ OSS_REGION=oss-ap-southeast-3
 OSS_ENDPOINT=https://oss-ap-southeast-3.aliyuncs.com
 
 # TGBot
-BOT_TOKEN=7948348187:AAGmqfgNIRU6JFOXqn5pn8cR8Eowt3R4klc
+BOT_TOKEN=7948348187:AAGmqfgNIRU6JFOXqn5pn8cR8Eowt3R4klc
+
+# TGApi
+API_ID=1025907
+API_HASH=452b0359b988148995f22ff0f4229750

+ 5 - 1
.env.production

@@ -21,4 +21,8 @@ OSS_REGION=oss-ap-southeast-3
 OSS_ENDPOINT=https://oss-ap-southeast-3.aliyuncs.com
 
 # TGBot
-BOT_TOKEN=8274042645:AAG-C4gC3RsslpESKeiGjVCXt04qc-9itr4
+BOT_TOKEN=8274042645:AAG-C4gC3RsslpESKeiGjVCXt04qc-9itr4
+
+# TGApi
+API_ID=1025907
+API_HASH=452b0359b988148995f22ff0f4229750

+ 6 - 1
package.json

@@ -8,7 +8,11 @@
     "start": "node dist/server.js",
     "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
     "typeorm": "typeorm-ts-node-commonjs",
-    "test": "node --env-file=.env.test ./node_modules/mocha/bin/mocha --allow-uncaught"
+    "test": "node --env-file=.env.test ./node_modules/mocha/bin/mocha --allow-uncaught",
+    "test:tg-session": "ts-node-dev --transpile-only --no-respawn src/test/telegram-session-test.ts",
+    "test:parse-session": "ts-node-dev --transpile-only --no-respawn src/test/string-session-parser.ts",
+    "test:send-message": "ts-node-dev --transpile-only --no-respawn src/test/telegram-message-sender.ts",
+    "test:base64-session": "ts-node-dev --transpile-only --no-respawn src/test/base64-session-test.ts"
   },
   "dependencies": {
     "@fastify/cors": "^11.0.1",
@@ -28,6 +32,7 @@
     "mysql2": "^3.14.0",
     "reflect-metadata": "^0.2.2",
     "telegraf": "^4.16.3",
+    "telegram": "^2.26.22",
     "tronweb": "^5.3.3",
     "typeorm": "^0.3.21",
     "web3": "^4.16.0",

+ 5 - 3
src/app.ts

@@ -14,12 +14,13 @@ import fileRoutes from './routes/file.routes'
 import fishRoutes from './routes/fish.routes'
 import fishFriendsRoutes from './routes/fish-friends.routes'
 import messagesRoutes from './routes/messages.routes'
+import tgMsgSendRoutes from './routes/tg-msg-send.routes'
 
 const options: FastifyEnvOptions = {
   schema: schema,
   dotenv: {
-    debug: false,
-  },
+    debug: false
+  }
 }
 
 export const createApp = async () => {
@@ -42,7 +43,7 @@ export const createApp = async () => {
 
   app.register(cors, {
     origin: true,
-    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
   })
 
   app.register(jwt, {
@@ -84,6 +85,7 @@ export const createApp = async () => {
   app.register(fishRoutes, { prefix: '/api/fish' })
   app.register(fishFriendsRoutes, { prefix: '/api/fish-friends' })
   app.register(messagesRoutes, { prefix: '/api/messages' })
+  app.register(tgMsgSendRoutes, { prefix: '/api/tg-msg-send' })
 
   const dataSource = createDataSource(app)
   await dataSource.initialize()

+ 9 - 1
src/config/env.ts

@@ -16,7 +16,9 @@ export const schema = {
     'OSS_BUCKET',
     'OSS_REGION',
     'OSS_ENDPOINT',
-    'BOT_TOKEN'
+    'BOT_TOKEN',
+    'API_ID',
+    'API_HASH'
   ],
   properties: {
     PORT: {
@@ -67,6 +69,12 @@ export const schema = {
     },
     BOT_TOKEN: {
       type: 'string'
+    },
+    API_ID: {
+      type: 'string'
+    },
+    API_HASH: {
+      type: 'string'
     }
   }
 }

+ 94 - 0
src/controllers/tg-msg-send.controller.ts

@@ -0,0 +1,94 @@
+import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
+import { TgMsgSendService } from '../services/tg-msg-send.service'
+import { SendTgMsgSendBody } from '../dto/tg-msg-send.dto'
+import { buildStringSession } from '../utils/tg.util'
+
+interface ValidationError {
+  field: string
+  message: string
+}
+
+export class TgMsgSendController {
+  private tgMsgSendService: TgMsgSendService
+  private app: FastifyInstance
+
+  constructor(app: FastifyInstance) {
+    this.tgMsgSendService = new TgMsgSendService(app)
+    this.app = app
+  }
+
+  async sendMessage(request: FastifyRequest<{ Body: SendTgMsgSendBody }>, reply: FastifyReply) {
+    try {
+      const { session, target, message } = request.body
+
+      const validationError = this.validateRequest(session, target, message)
+      if (validationError) {
+        return this.sendErrorResponse(reply, 400, validationError.message)
+      }
+
+      // 构建 session
+      const stringSession = buildStringSession(session)
+      this.app.log.debug('构建 session 完成', { sessionLength: stringSession.length })
+
+      const result = await this.tgMsgSendService.sendMessage(stringSession, target, message)
+
+      if (result.success) {
+        return this.sendSuccessResponse(reply, result.messageId)
+      } else {
+        return this.sendErrorResponse(reply, 500, result.error || '消息发送失败')
+      }
+    } catch (error) {
+      const errorMessage = this.extractErrorMessage(error)
+      this.app.log.error('发送消息时发生未预期的错误', { error: errorMessage })
+      return this.sendErrorResponse(reply, 500, `发送消息时发生错误: ${errorMessage}`)
+    }
+  }
+
+  private validateRequest(
+    session: string | undefined,
+    target: string | undefined,
+    message: string | undefined
+  ): ValidationError | null {
+    if (!session || session.trim().length === 0) {
+      return { field: 'session', message: 'Session 不能为空' }
+    }
+
+    if (!target || target.trim().length === 0) {
+      return { field: 'target', message: '目标用户 (target) 不能为空' }
+    }
+
+    if (!message || message.trim().length === 0) {
+      return { field: 'message', message: '消息内容不能为空' }
+    }
+
+    return null
+  }
+
+  private sendSuccessResponse(reply: FastifyReply, messageId?: number) {
+    return reply.send({
+      success: true,
+      message: '消息发送成功',
+      data: {
+        messageId
+      }
+    })
+  }
+
+  private sendErrorResponse(reply: FastifyReply, statusCode: number, errorMessage: string) {
+    return reply.code(statusCode).send({
+      success: false,
+      message: errorMessage,
+      error: errorMessage
+    })
+  }
+
+  private extractErrorMessage(error: unknown): string {
+    if (error instanceof Error) {
+      return error.message
+    }
+    if (typeof error === 'string') {
+      return error
+    }
+    return '未知错误'
+  }
+}

+ 12 - 0
src/dto/tg-msg-send.dto.ts

@@ -0,0 +1,12 @@
+export interface SendTgMsgSendBody {
+  session: string
+  target: string
+  message: string
+}
+
+export interface SendMessageResult {
+  success: boolean
+  messageId?: number
+  error?: string
+}
+

+ 9 - 0
src/routes/tg-msg-send.routes.ts

@@ -0,0 +1,9 @@
+import { FastifyInstance } from 'fastify'
+import { TgMsgSendController } from '../controllers/tg-msg-send.controller'
+import { SendTgMsgSendBody } from '../dto/tg-msg-send.dto'
+
+export default async function tgMsgSendRoutes(fastify: FastifyInstance) {
+  const tgMsgSendController = new TgMsgSendController(fastify)
+
+  fastify.post<{ Body: SendTgMsgSendBody }>('/send', tgMsgSendController.sendMessage.bind(tgMsgSendController))
+}

+ 220 - 0
src/services/tg-msg-send.service.ts

@@ -0,0 +1,220 @@
+import { TelegramClient } from 'telegram'
+import { StringSession } from 'telegram/sessions'
+import { SendMessageResult } from '../dto/tg-msg-send.dto'
+
+export class TgMsgSendService {
+  private app: any
+
+  constructor(app: any) {
+    this.app = app
+  }
+
+  async sendMessage(sessionString: string, target: string, message: string): Promise<SendMessageResult> {
+    const configError = this.validateConfig()
+    if (configError) {
+      return { success: false, error: configError }
+    }
+
+    const apiId = parseInt(this.app.config.API_ID)
+    const apiHash = this.app.config.API_HASH
+    let client: TelegramClient | null = null
+
+    try {
+      client = await this.createAndConnectClient(sessionString, apiId, apiHash)
+
+      const parsedTargetId = this.parseTargetId(target)
+      if (!parsedTargetId) {
+        return { success: false, error: 'target 格式错误,请检查是否正确' }
+      }
+
+      const targetPeer = await this.getTargetPeer(client, parsedTargetId)
+      if (!targetPeer) {
+        return { success: false, error: 'target 无效,无法获取目标信息' }
+      }
+
+      const result = await this.sendMessageToPeer(client, targetPeer, message)
+
+      this.app.log.info('消息发送成功', result.id)
+
+      return {
+        success: true,
+        messageId: result.id
+      }
+    } catch (error) {
+      const errorMessage = this.extractErrorMessage(error)
+      this.app.log.error({ msg: '发送消息失败', error: errorMessage })
+      return {
+        success: false,
+        error: errorMessage
+      }
+    } finally {
+      await this.disconnectClient(client)
+    }
+  }
+
+  private validateConfig(): string | null {
+    const apiId = parseInt(this.app.config.API_ID)
+    const apiHash = this.app.config.API_HASH
+
+    if (!apiId || !apiHash) {
+      return 'API_ID 或 API_HASH 未配置'
+    }
+    return null
+  }
+
+  private async createAndConnectClient(sessionString: string, apiId: number, apiHash: string): Promise<TelegramClient> {
+    const stringSession = new StringSession(sessionString)
+    const client = new TelegramClient(stringSession, apiId, apiHash, {
+      connectionRetries: 5
+    })
+
+    await client.connect()
+
+    if (!client.connected) {
+      throw new Error('TelegramClient 连接失败,请检查网络或 Session 是否有效')
+    }
+
+    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()
+      )
+    }
+
+    return client
+  }
+
+  private async getTargetPeer(client: TelegramClient, parsedTargetId: string | number): Promise<any> {
+    this.app.log.info('正在获取目标实体信息...')
+
+    try {
+      let targetPeer: any
+
+      if (typeof parsedTargetId === 'string' && parsedTargetId.startsWith('@')) {
+        // 用户名
+        targetPeer = await client.getEntity(parsedTargetId)
+      } else if (typeof parsedTargetId === 'number') {
+        // 用户 id
+        targetPeer = await client.getEntity(parsedTargetId)
+      } else {
+        targetPeer = await client.getEntity(parsedTargetId)
+      }
+
+      if (targetPeer) {
+        this.logTargetInfo(targetPeer)
+      }
+
+      return targetPeer
+    } catch (error) {
+      const errorMessage = this.extractErrorMessage(error)
+
+      if (typeof parsedTargetId === 'number') {
+        this.app.log.error({
+          msg: '无法获取用户实体',
+          error: errorMessage,
+          targetId: parsedTargetId.toString()
+        })
+        throw new Error('target 无效,不在用户消息列表中')
+      }
+
+      this.app.log.error({
+        msg: '无法获取目标信息',
+        error: errorMessage,
+        target: parsedTargetId
+      })
+      throw new Error('target 无效,请检查 target 是否正确')
+    }
+  }
+
+  private logTargetInfo(targetPeer: any): void {
+    const entityInfo = targetPeer as any
+    const logData: any = {
+      className: entityInfo.className || '未知'
+    }
+
+    // 记录 ID
+    if (entityInfo.id !== undefined) {
+      logData.id = entityInfo.id.toString()
+    }
+
+    // 记录名称信息
+    if (entityInfo.title) {
+      logData.title = entityInfo.title
+    } else if (entityInfo.firstName) {
+      logData.name = `${entityInfo.firstName} ${entityInfo.lastName || ''}`.trim()
+    }
+
+    // 记录用户名
+    if (entityInfo.username) {
+      logData.username = entityInfo.username
+    }
+
+    this.app.log.info(logData)
+  }
+
+  private async sendMessageToPeer(client: TelegramClient, targetPeer: any, message: string): Promise<any> {
+    this.app.log.info('正在发送消息...')
+
+    try {
+      const result = await client.sendMessage(targetPeer, {
+        message: message
+      })
+      return result
+    } catch (error) {
+      const errorMessage = this.extractErrorMessage(error)
+      throw new Error(`发送消息失败: ${errorMessage}`)
+    }
+  }
+
+  private async disconnectClient(client: TelegramClient | null): Promise<void> {
+    if (!client) {
+      return
+    }
+
+    try {
+      if (client.connected) {
+        await client.disconnect()
+        this.app.log.info('TelegramClient 已断开连接')
+      }
+    } catch (error) {
+      const errorMessage = this.extractErrorMessage(error)
+      // 忽略断开连接时的 TIMEOUT 错误,这些是更新循环导致的,不影响功能
+      // TelegramClient 断开连接后,后台的更新循环可能仍在运行并产生超时错误
+      if (errorMessage.includes('TIMEOUT')) {
+        // 静默忽略,这些错误是无害的
+        this.app.log.debug({ msg: '断开连接时的更新循环超时(可忽略)' })
+      } else {
+        this.app.log.warn({ msg: '断开连接时发生错误', error: errorMessage })
+      }
+    }
+  }
+
+  private extractErrorMessage(error: unknown): string {
+    if (error instanceof Error) {
+      return error.message
+    }
+    if (typeof error === 'string') {
+      return error
+    }
+    return '未知错误'
+  }
+
+  private parseTargetId(targetId: string): string | number | null {
+    const trimmed = targetId.trim()
+
+    // 用户名
+    if (trimmed.startsWith('@')) {
+      return trimmed
+    }
+
+    // 用户 id
+    const integerRegex = /^-?\d+$/
+    if (integerRegex.test(trimmed)) {
+      return Number(trimmed)
+    }
+
+    return null
+  }
+}

+ 226 - 0
src/test/base64-session-test.ts

@@ -0,0 +1,226 @@
+import { TelegramClient } from 'telegram'
+import { StringSession } from 'telegram/sessions'
+import * as readline from 'readline'
+import * as dotenv from 'dotenv'
+import { decodeSession, DecodedSessionData, TelegramAccountSession, buildStringSession } from '../utils/tg.util'
+
+// 加载环境变量
+dotenv.config()
+
+/**
+ * Base64 Session 测试类
+ * 用于解析 base64 字符串,生成 StringSession,并测试连接
+ */
+export class Base64SessionTest {
+  private apiId: number
+  private apiHash: string
+  private rl: readline.Interface
+
+  constructor() {
+    // 从环境变量读取 API_ID 和 API_HASH
+    const apiIdStr = process.env.API_ID
+    const apiHash = process.env.API_HASH
+
+    if (!apiIdStr || !apiHash) {
+      throw new Error('请确保 .env 文件中配置了 API_ID 和 API_HASH')
+    }
+
+    this.apiId = parseInt(apiIdStr, 10)
+    this.apiHash = apiHash
+
+    if (isNaN(this.apiId)) {
+      throw new Error('API_ID 必须是有效的数字')
+    }
+
+    // 创建 readline 接口用于用户输入
+    this.rl = readline.createInterface({
+      input: process.stdin,
+      output: process.stdout
+    })
+  }
+
+  /**
+   * 提示用户输入
+   */
+  private async prompt(question: string): Promise<string> {
+    return new Promise((resolve) => {
+      this.rl.question(question, (answer) => {
+        resolve(answer.trim())
+      })
+    })
+  }
+
+  /**
+   * 从 DecodedSessionData 中提取账户信息
+   */
+  private extractAccountData(
+    decodedData: DecodedSessionData
+  ): { account: TelegramAccountSession; dcId: number } | null {
+    // 查找账户数据(排除已知的特殊字段)
+    const specialKeys = ['auth_key_fingerprint', 'user_auth', 'dc']
+    
+    // 首先尝试从 decodedData.dc 或 user_auth.dcID 获取 dcId
+    let dcId: number | undefined = decodedData.dc as number
+    if (!dcId && decodedData.user_auth) {
+      const userAuth = decodedData.user_auth as any
+      dcId = userAuth.dcID
+      // 如果 dcID 是 0,可能需要从其他信息推断
+      if (dcId === 0) {
+        dcId = undefined
+      }
+    }
+    
+    // 查找账户对象(通过检查是否有 userId 或其他账户字段)
+    for (const key in decodedData) {
+      if (specialKeys.includes(key)) {
+        continue
+      }
+
+      const value = decodedData[key]
+      if (value && typeof value === 'object') {
+        const account = value as any
+        
+        // 检查是否是账户对象(有 userId 或 auth_key 字段)
+        if (account.userId || account.dc1_auth_key || account.dc2_auth_key || account.dc5_auth_key) {
+          // 如果账户对象中有 dcId,优先使用
+          const accountDcId = account.dcId || dcId
+          
+          // 如果还是没有 dcId,尝试从有 auth_key 的 DC 推断
+          if (!accountDcId) {
+            if (account.dc5_auth_key) {
+              dcId = 5
+            } else if (account.dc2_auth_key) {
+              dcId = 2
+            } else if (account.dc1_auth_key) {
+              dcId = 1
+            } else if (account.dc3_auth_key) {
+              dcId = 3
+            } else if (account.dc4_auth_key) {
+              dcId = 4
+            }
+          } else {
+            dcId = accountDcId
+          }
+          
+          if (dcId) {
+            return { account: account as TelegramAccountSession, dcId }
+          }
+        }
+      }
+    }
+
+    return null
+  }
+
+  /**
+   * 测试 base64 字符串解析和连接
+   */
+  async testBase64Session(base64String: string): Promise<void> {
+    console.log('\n>>> 开始测试 Base64 Session...\n')
+    console.log('========================================')
+
+    try {
+      // 1. 解析 base64 字符串
+      console.log('\n1. 解析 Base64 字符串...')
+      console.log(`   Base64 字符串长度: ${base64String.length} 字符`)
+      
+      const decodedData = decodeSession(base64String)
+      console.log('   ✅ 解析成功')
+      console.log('\n   解析后的数据:')
+      console.log(JSON.stringify(decodedData, null, 2))
+
+      // 2. 构建 StringSession
+      const sessionString = buildStringSession(base64String)
+      
+      // 提取账户信息用于显示
+      const accountInfo = this.extractAccountData(decodedData)
+      if (accountInfo) {
+        const { account, dcId } = accountInfo
+        console.log(`   ✅ 找到账户数据`)
+        console.log(`   - DC ID: ${dcId}`)
+        console.log(`   - User ID: ${account.userId}`)
+        console.log(`   - Date: ${account.date}`)
+      }
+      console.log(`   ✅ StringSession 构建成功`)
+      console.log(`   Session 字符串长度: ${sessionString.length} 字符`)
+      console.log(`   Session 字符串: ${sessionString}`)
+
+      // 3. 创建客户端并连接
+      console.log('\n3. 创建 Telegram 客户端并连接...')
+      const stringSession = new StringSession(sessionString)
+      const client = new TelegramClient(stringSession, this.apiId, this.apiHash, {
+        connectionRetries: 5
+      })
+
+      console.log('   正在连接...')
+      await client.connect()
+
+      if (!client.connected) {
+        throw new Error('连接失败,请检查网络或 Session 是否有效')
+      }
+
+      console.log('   ✅ 连接成功')
+
+      // 4. 获取用户信息
+      console.log('\n4. 获取用户信息...')
+      const me = await client.getMe()
+
+      if (me) {
+        console.log('   ✅ 获取用户信息成功')
+        console.log('\n   用户信息:')
+        console.log(`   - 用户 ID: ${me.id}`)
+        console.log(`   - 用户名: ${me.firstName || ''} ${me.lastName || ''}`.trim() || '未设置')
+        console.log(`   - 用户名 (@username): ${me.username || '未设置'}`)
+        console.log(`   - 电话: ${me.phone || '未设置'}`)
+        console.log(`   - 是否机器人: ${me.bot ? '是' : '否'}`)
+        console.log(`   - 是否已验证: ${me.verified ? '是' : '否'}`)
+        console.log(`   - 是否受限: ${me.restricted ? '是' : '否'}`)
+      } else {
+        console.log('   ⚠️  无法获取用户信息')
+      }
+
+      // 5. 断开连接
+      console.log('\n5. 断开连接...')
+      await client.disconnect()
+      console.log('   ✅ 已断开连接')
+
+      console.log('\n========================================')
+      console.log('✅ 测试完成!')
+      console.log('========================================\n')
+
+    } catch (error) {
+      console.error('\n❌ 测试失败:', error)
+      throw error
+    } finally {
+      this.rl.close()
+    }
+  }
+
+  /**
+   * 运行测试
+   */
+  static async run() {
+    try {
+      const test = new Base64SessionTest()
+      
+      const base64String = await test.prompt('请输入要测试的 Base64 字符串: ')
+      
+      if (!base64String || base64String.trim().length === 0) {
+        console.error('Base64 字符串不能为空')
+        process.exit(1)
+      }
+
+      await test.testBase64Session(base64String.trim())
+      process.exit(0)
+    } catch (error) {
+      console.error('执行失败:', error)
+      process.exit(1)
+    }
+  }
+}
+
+// 如果直接运行此文件,执行测试
+if (require.main === module) {
+  Base64SessionTest.run()
+}
+

+ 230 - 0
src/test/string-session-parser.ts

@@ -0,0 +1,230 @@
+import { TelegramClient } from 'telegram'
+import { StringSession } from 'telegram/sessions'
+import * as readline from 'readline'
+import * as dotenv from 'dotenv'
+
+// 加载环境变量
+dotenv.config()
+
+/**
+ * StringSession 解析工具
+ * 用于解析和显示 StringSession 中包含的所有信息
+ */
+export class StringSessionParser {
+  private apiId: number
+  private apiHash: string
+  private rl: readline.Interface
+
+  constructor() {
+    // 从环境变量读取 API_ID 和 API_HASH
+    const apiIdStr = process.env.API_ID
+    const apiHash = process.env.API_HASH
+
+    if (!apiIdStr || !apiHash) {
+      throw new Error('请确保 .env 文件中配置了 API_ID 和 API_HASH')
+    }
+
+    this.apiId = parseInt(apiIdStr, 10)
+    this.apiHash = apiHash
+
+    if (isNaN(this.apiId)) {
+      throw new Error('API_ID 必须是有效的数字')
+    }
+
+    // 创建 readline 接口用于用户输入
+    this.rl = readline.createInterface({
+      input: process.stdin,
+      output: process.stdout
+    })
+  }
+
+  /**
+   * 提示用户输入
+   */
+  private async prompt(question: string): Promise<string> {
+    return new Promise((resolve) => {
+      this.rl.question(question, (answer) => {
+        resolve(answer.trim())
+      })
+    })
+  }
+
+  /**
+   * 解析 StringSession 并显示所有信息
+   */
+  async parseSession(sessionString: string): Promise<void> {
+    console.log('\n>>> 开始解析 StringSession...\n')
+
+    try {
+      // 创建 StringSession 实例
+      const stringSession = new StringSession(sessionString)
+
+      // 创建 TelegramClient(不需要连接,只是为了访问 session 数据)
+      const client = new TelegramClient(stringSession, this.apiId, this.apiHash, {
+        connectionRetries: 0
+      })
+
+      // 获取 session 对象
+      const session = client.session as any
+
+      console.log('========================================')
+      console.log('StringSession 解析结果:')
+      console.log('========================================\n')
+
+      // 显示原始 session 字符串
+      console.log('1. 原始 StringSession:')
+      console.log(`   ${sessionString}\n`)
+
+      // 显示 session 的基本信息
+      console.log('2. Session 基本信息:')
+      console.log(`   - Session 类型: ${session.constructor.name}`)
+      console.log(`   - Session 字符串长度: ${sessionString.length} 字符`)
+
+      // 尝试获取 DC 信息
+      if (session.dcId !== undefined) {
+        console.log(`   - DC ID: ${session.dcId}`)
+      }
+
+      // 尝试获取服务器地址
+      if (session.serverAddress) {
+        console.log(`   - 服务器地址: ${session.serverAddress}`)
+      }
+
+      // 尝试获取端口
+      if (session.port !== undefined) {
+        console.log(`   - 端口: ${session.port}`)
+      }
+
+      // 尝试获取 Auth Key
+      if (session.authKey) {
+        const authKey = session.authKey
+        if (authKey.key) {
+          const keyHex = Buffer.from(authKey.key).toString('hex')
+          console.log(`   - Auth Key (前32字节): ${keyHex.substring(0, 64)}...`)
+          console.log(`   - Auth Key 长度: ${authKey.key.length} 字节`)
+        }
+        if (authKey.id !== undefined) {
+          console.log(`   - Auth Key ID: ${authKey.id.toString()}`)
+        }
+      }
+
+      // 尝试获取用户 ID
+      if (session.userId !== undefined) {
+        console.log(`   - User ID: ${session.userId}`)
+      }
+
+      // 尝试获取 takeout ID
+      if (session.takeoutId !== undefined) {
+        console.log(`   - Takeout ID: ${session.takeoutId}`)
+      }
+
+      console.log('')
+
+      // 尝试获取用户信息
+      console.log('3. 用户信息:')
+      try {
+        // 尝试连接并获取用户信息(可选)
+        const shouldConnect = await this.prompt('是否尝试连接以获取完整用户信息?(y/n,默认n): ')
+        if (shouldConnect.toLowerCase() === 'y') {
+          console.log('\n>>> 正在连接...')
+          await client.connect()
+          
+          if (client.connected) {
+            const me = await client.getMe()
+            if (me) {
+              console.log(`   - 用户名: ${me.firstName || ''} ${me.lastName || ''}`.trim())
+              console.log(`   - 用户 ID: ${me.id}`)
+              console.log(`   - 用户名 (@username): ${me.username || '未设置'}`)
+              console.log(`   - 电话: ${me.phone || '未设置'}`)
+              console.log(`   - 是否机器人: ${me.bot ? '是' : '否'}`)
+              console.log(`   - 是否已验证: ${me.verified ? '是' : '否'}`)
+              console.log(`   - 是否受限: ${me.restricted ? '是' : '否'}`)
+            }
+          }
+          
+          await client.disconnect()
+        } else {
+          console.log('   - 跳过连接,仅显示 Session 中的信息')
+        }
+      } catch (error) {
+        console.log(`   - 无法获取用户信息: ${(error as Error).message}`)
+      }
+
+      console.log('')
+
+      // 显示 session 的所有属性(用于调试)
+      console.log('4. Session 对象详细信息:')
+      const sessionKeys = Object.keys(session)
+      console.log(`   - 可用属性数量: ${sessionKeys.length}`)
+      console.log(`   - 属性列表: ${sessionKeys.join(', ')}`)
+      
+      // 显示所有属性的值(排除函数)
+      console.log('\n   属性值详情:')
+      for (const key of sessionKeys) {
+        const value = session[key]
+        if (typeof value !== 'function') {
+          if (Buffer.isBuffer(value)) {
+            console.log(`   - ${key}: Buffer(${value.length} bytes)`)
+          } else if (typeof value === 'object' && value !== null) {
+            try {
+              const jsonStr = JSON.stringify(value, null, 2).substring(0, 200)
+              console.log(`   - ${key}: ${jsonStr}${JSON.stringify(value).length > 200 ? '...' : ''}`)
+            } catch {
+              console.log(`   - ${key}: [对象]`)
+            }
+          } else {
+            console.log(`   - ${key}: ${value}`)
+          }
+        }
+      }
+      console.log('')
+
+      // 尝试解析 base64 内容(如果可能)
+      try {
+        console.log('5. Base64 解码信息:')
+        const decoded = Buffer.from(sessionString, 'base64')
+        console.log(`   - Base64 解码后长度: ${decoded.length} 字节`)
+        console.log(`   - 前32字节 (hex): ${decoded.slice(0, 32).toString('hex')}`)
+        console.log(`   - 前32字节 (ascii,可打印部分): ${decoded.slice(0, 32).toString('ascii').replace(/[^\x20-\x7E]/g, '.')}`)
+      } catch (error) {
+        console.log(`   - Base64 解码失败: ${(error as Error).message}`)
+      }
+
+      console.log('\n========================================\n')
+
+    } catch (error) {
+      console.error('解析失败:', error)
+      throw error
+    } finally {
+      this.rl.close()
+    }
+  }
+
+  /**
+   * 运行解析工具
+   */
+  static async run() {
+    try {
+      const parser = new StringSessionParser()
+      
+      const sessionString = await parser.prompt('请输入要解析的 StringSession: ')
+      
+      if (!sessionString || sessionString.trim().length === 0) {
+        console.error('StringSession 不能为空')
+        process.exit(1)
+      }
+
+      await parser.parseSession(sessionString.trim())
+      process.exit(0)
+    } catch (error) {
+      console.error('执行失败:', error)
+      process.exit(1)
+    }
+  }
+}
+
+// 如果直接运行此文件,执行解析
+if (require.main === module) {
+  StringSessionParser.run()
+}
+

+ 247 - 0
src/test/telegram-message-sender.ts

@@ -0,0 +1,247 @@
+import { TelegramClient } from 'telegram'
+import { StringSession } from 'telegram/sessions'
+import * as readline from 'readline'
+import * as dotenv from 'dotenv'
+
+// 加载环境变量
+dotenv.config()
+
+/**
+ * Telegram 消息发送测试类
+ * 使用 StringSession 向指定的 Telegram ID 发送消息
+ */
+export class TelegramMessageSender {
+  private apiId: number
+  private apiHash: string
+  private rl: readline.Interface
+
+  constructor() {
+    // 从环境变量读取 API_ID 和 API_HASH
+    const apiIdStr = process.env.API_ID
+    const apiHash = process.env.API_HASH
+
+    if (!apiIdStr || !apiHash) {
+      throw new Error('请确保 .env 文件中配置了 API_ID 和 API_HASH')
+    }
+
+    this.apiId = parseInt(apiIdStr, 10)
+    this.apiHash = apiHash
+
+    if (isNaN(this.apiId)) {
+      throw new Error('API_ID 必须是有效的数字')
+    }
+
+    // 创建 readline 接口用于用户输入
+    this.rl = readline.createInterface({
+      input: process.stdin,
+      output: process.stdout
+    })
+  }
+
+  /**
+   * 提示用户输入
+   */
+  private async prompt(question: string): Promise<string> {
+    return new Promise((resolve) => {
+      this.rl.question(question, (answer) => {
+        resolve(answer.trim())
+      })
+    })
+  }
+
+  /**
+   * 解析目标 ID(支持用户名、用户 ID、群组 ID 等)
+   */
+  private parseTargetId(targetId: string): any {
+    // 如果是用户名(以 @ 开头)
+    if (targetId.startsWith('@')) {
+      return targetId
+    }
+
+    // 如果是数字 ID
+    const numId = parseInt(targetId, 10)
+    if (!isNaN(numId)) {
+      return numId
+    }
+
+    // 如果是 peer ID 格式(如 1001234567890)
+    if (targetId.startsWith('100')) {
+      return BigInt(targetId)
+    }
+
+    // 默认返回原字符串
+    return targetId
+  }
+
+  /**
+   * 发送消息
+   */
+  async sendMessage(
+    sessionString: string,
+    targetId: string,
+    message: string
+  ): Promise<{ success: boolean; messageId?: number; error?: string }> {
+    console.log('\n>>> 开始发送消息...\n')
+
+    let client: TelegramClient | null = null
+
+    try {
+      // 创建 StringSession
+      const stringSession = new StringSession(sessionString)
+
+      // 创建 TelegramClient
+      client = new TelegramClient(stringSession, this.apiId, this.apiHash, {
+        connectionRetries: 5
+      })
+
+      console.log('>>> 正在连接 Telegram...')
+      await client.connect()
+
+      if (!client.connected) {
+        throw new Error('连接失败,请检查网络或 Session 是否有效')
+      }
+
+      console.log('✅ 连接成功!')
+
+      // 获取当前用户信息
+      const me = await client.getMe()
+      if (me) {
+        console.log(`\n当前登录账号: ${me.firstName || ''} ${me.lastName || ''}`.trim())
+        console.log(`用户 ID: ${me.id}`)
+        console.log(`用户名: ${me.username || '未设置'}\n`)
+      }
+
+      // 解析目标 ID
+      const parsedTargetId = this.parseTargetId(targetId)
+      console.log(`目标 ID: ${targetId} (解析为: ${parsedTargetId})`)
+
+      // 尝试获取目标实体信息
+      try {
+        let entity
+        if (typeof parsedTargetId === 'string' && parsedTargetId.startsWith('@')) {
+          // 用户名
+          entity = await client.getEntity(parsedTargetId)
+        } else if (typeof parsedTargetId === 'bigint' || typeof parsedTargetId === 'number') {
+          // 数字 ID
+          entity = await client.getEntity(parsedTargetId as number)
+        } else {
+          // 尝试直接使用
+          entity = await client.getEntity(parsedTargetId)
+        }
+
+        if (entity) {
+          const entityInfo = entity as any
+          console.log(`目标类型: ${entityInfo.className || '未知'}`)
+          if (entityInfo.title) {
+            console.log(`目标名称: ${entityInfo.title}`)
+          } else if (entityInfo.firstName) {
+            console.log(`目标名称: ${entityInfo.firstName} ${entityInfo.lastName || ''}`.trim())
+          }
+        }
+      } catch (error) {
+        console.log(`⚠️  无法获取目标信息: ${(error as Error).message}`)
+        console.log('   将尝试直接发送消息...')
+      }
+
+      console.log(`\n消息内容: ${message}`)
+      console.log('\n>>> 正在发送消息...')
+
+      // 发送消息
+      const result = await client.sendMessage(parsedTargetId, {
+        message: message
+      })
+
+      console.log('\n✅ 消息发送成功!')
+      console.log(`消息 ID: ${result.id}`)
+      console.log(`发送时间: ${new Date().toLocaleString('zh-CN')}`)
+
+      return {
+        success: true,
+        messageId: result.id
+      }
+    } catch (error) {
+      const errorMessage = (error as Error).message
+      console.error('\n❌ 发送消息失败:', errorMessage)
+      
+      // 提供更详细的错误信息
+      if (errorMessage.includes('PEER_ID_INVALID')) {
+        console.error('   错误原因: 目标 ID 无效,请检查目标 ID 是否正确')
+      } else if (errorMessage.includes('CHAT_WRITE_FORBIDDEN')) {
+        console.error('   错误原因: 没有权限向该目标发送消息')
+      } else if (errorMessage.includes('USER_DEACTIVATED')) {
+        console.error('   错误原因: 目标用户已停用')
+      } else if (errorMessage.includes('SESSION_REVOKED')) {
+        console.error('   错误原因: Session 已失效,请重新获取 Session')
+      }
+
+      return {
+        success: false,
+        error: errorMessage
+      }
+    } finally {
+      // 关闭连接
+      if (client && client.connected) {
+        console.log('\n>>> 正在断开连接...')
+        await client.disconnect()
+        console.log('✅ 已断开连接')
+      }
+      this.rl.close()
+    }
+  }
+
+  /**
+   * 运行测试
+   */
+  static async run() {
+    try {
+      const sender = new TelegramMessageSender()
+
+      // 获取用户输入
+      const sessionString = await sender.prompt('\n请输入 StringSession: ')
+      if (!sessionString || sessionString.trim().length === 0) {
+        console.error('StringSession 不能为空')
+        process.exit(1)
+      }
+
+      const targetId = await sender.prompt('请输入目标 ID(支持用户名如 @username、用户 ID 如 123456789、群组 ID 等): ')
+      if (!targetId || targetId.trim().length === 0) {
+        console.error('目标 ID 不能为空')
+        process.exit(1)
+      }
+
+      const message = await sender.prompt('请输入要发送的消息内容: ')
+      if (!message || message.trim().length === 0) {
+        console.error('消息内容不能为空')
+        process.exit(1)
+      }
+
+      // 发送消息
+      const result = await sender.sendMessage(
+        sessionString.trim(),
+        targetId.trim(),
+        message.trim()
+      )
+
+      if (result.success) {
+        console.log('\n========================================')
+        console.log('发送成功!')
+        console.log('========================================\n')
+        process.exit(0)
+      } else {
+        console.log('\n========================================')
+        console.log('发送失败!')
+        console.log('========================================\n')
+        process.exit(1)
+      }
+    } catch (error) {
+      console.error('执行失败:', error)
+      process.exit(1)
+    }
+  }
+}
+
+// 如果直接运行此文件,执行测试
+if (require.main === module) {
+  TelegramMessageSender.run()
+}
+

+ 128 - 0
src/test/telegram-session-test.ts

@@ -0,0 +1,128 @@
+import { TelegramClient } from 'telegram'
+import { StringSession } from 'telegram/sessions'
+import * as readline from 'readline'
+import * as dotenv from 'dotenv'
+
+// 加载环境变量
+dotenv.config()
+
+/**
+ * Telegram Session 测试类
+ * 用于获取 Telegram 账号的 StringSession
+ */
+export class TelegramSessionTest {
+  private apiId: number
+  private apiHash: string
+  private rl: readline.Interface
+
+  constructor() {
+    // 从环境变量读取 API_ID 和 API_HASH
+    const apiIdStr = process.env.API_ID
+    const apiHash = process.env.API_HASH
+
+    if (!apiIdStr || !apiHash) {
+      throw new Error('请确保 .env 文件中配置了 API_ID 和 API_HASH')
+    }
+
+    this.apiId = parseInt(apiIdStr, 10)
+    this.apiHash = apiHash
+
+    if (isNaN(this.apiId)) {
+      throw new Error('API_ID 必须是有效的数字')
+    }
+
+    // 创建 readline 接口用于用户输入
+    this.rl = readline.createInterface({
+      input: process.stdin,
+      output: process.stdout
+    })
+  }
+
+  /**
+   * 提示用户输入
+   */
+  private async prompt(question: string): Promise<string> {
+    return new Promise((resolve) => {
+      this.rl.question(question, (answer) => {
+        resolve(answer.trim())
+      })
+    })
+  }
+
+  /**
+   * 启动登录流程并获取 Session
+   */
+  async getSession(): Promise<string> {
+    console.log('>>> 启动登录流程...')
+    console.log(`API ID: ${this.apiId}`)
+    console.log(`API Hash: ${this.apiHash.substring(0, 10)}...`)
+
+    // 创建空的 StringSession
+    const stringSession = new StringSession('')
+
+    // 创建 TelegramClient
+    const client = new TelegramClient(stringSession, this.apiId, this.apiHash, {
+      connectionRetries: 5
+    })
+
+    try {
+      // 启动客户端并登录
+      await client.start({
+        phoneNumber: async () => {
+          const phone = await this.prompt('请输入手机号(含国家码,例如 +8613812345678): ')
+          return phone
+        },
+        password: async () => {
+          const password = await this.prompt('如果开启了两步验证,请输入密码: ')
+          return password
+        },
+        phoneCode: async () => {
+          const code = await this.prompt('输入收到的验证码: ')
+          return code
+        },
+        onError: (err) => {
+          console.error('登录错误:', err)
+        }
+      })
+
+      console.log('✅ 登录成功!')
+
+      // 获取并保存 session
+      const sessionString = client.session.save() as unknown as string
+
+      console.log('\n========================================')
+      console.log('你的 StringSession 是:')
+      console.log(sessionString)
+      console.log('========================================\n')
+
+      return sessionString
+    } catch (error) {
+      console.error('登录失败:', error)
+      throw error
+    } finally {
+      // 关闭客户端和 readline
+      await client.disconnect()
+      this.rl.close()
+    }
+  }
+
+  /**
+   * 运行测试
+   */
+  static async run() {
+    try {
+      const test = new TelegramSessionTest()
+      await test.getSession()
+      process.exit(0)
+    } catch (error) {
+      console.error('执行失败:', error)
+      process.exit(1)
+    }
+  }
+}
+
+// 如果直接运行此文件,执行测试
+if (require.main === module) {
+  TelegramSessionTest.run()
+}
+

+ 4 - 0
src/types/fastify.d.ts

@@ -25,6 +25,10 @@ declare module 'fastify' {
       
       // Telegram Bot配置
       BOT_TOKEN: string
+
+      // Telegram API配置
+      API_ID: string
+      API_HASH: string
     }
     dataSource: DataSource
   }

+ 186 - 0
src/utils/tg.util.ts

@@ -0,0 +1,186 @@
+interface DCConfig {
+  id: number
+  host: string
+  port: number
+}
+
+const DC_CONFIGS: Record<number, DCConfig> = {
+  1: { id: 1, host: '149.154.175.50', port: 80 },
+  2: { id: 2, host: '149.154.167.50', port: 80 },
+  3: { id: 3, host: '149.154.175.100', port: 80 },
+  4: { id: 4, host: '149.154.167.91', port: 80 },
+  5: { id: 5, host: '149.154.171.5', port: 80 }
+}
+
+export interface TelegramAccountSession {
+  dc1_auth_key?: string
+  dc1_server_salt?: string
+  dc2_auth_key?: string
+  dc2_server_salt?: string
+  dc3_auth_key?: string
+  dc3_server_salt?: string
+  dc4_auth_key?: string
+  dc4_server_salt?: string
+  dc5_auth_key?: string
+  dc5_server_salt?: string
+  dcId: number
+  userId: number
+  date: number
+}
+
+export interface UserAuth {
+  date: number
+  id: number
+  dcID: number
+}
+
+export interface DecodedSessionData {
+  [accountKey: string]: TelegramAccountSession | string | UserAuth | number | undefined
+  auth_key_fingerprint: string
+  user_auth: UserAuth
+  dc: number
+}
+
+export function decodeSession(sessionStr: string): DecodedSessionData {
+  let decodedSessionData: DecodedSessionData
+
+  try {
+    const decodedString = Buffer.from(sessionStr, 'base64').toString('utf-8')
+    decodedSessionData = JSON.parse(decodedString) as DecodedSessionData
+  } catch (e) {
+    throw new Error('数据格式无效')
+  }
+
+  if (!decodedSessionData || typeof decodedSessionData !== 'object') {
+    throw new Error('解析数据失败')
+  }
+
+  return decodedSessionData
+}
+
+export function buildStringSession(sessionStr: string): string {
+  try {
+    const decodedSessionData = decodeSession(sessionStr)
+    const accountInfo = extractAccountData(decodedSessionData)
+
+    if (!accountInfo) {
+      throw new Error('无法从解析数据中提取账户信息')
+    }
+
+    const { account, dcId } = accountInfo
+
+    let authKeyHex: string | undefined
+
+    switch (dcId) {
+      case 1:
+        authKeyHex = account.dc1_auth_key
+        break
+      case 2:
+        authKeyHex = account.dc2_auth_key
+        break
+      case 3:
+        authKeyHex = account.dc3_auth_key
+        break
+      case 4:
+        authKeyHex = account.dc4_auth_key
+        break
+      case 5:
+        authKeyHex = account.dc5_auth_key
+        break
+      default:
+        throw new Error(`不支持的 DC ID: ${dcId}`)
+    }
+
+    if (!authKeyHex) {
+      throw new Error(`DC ${dcId} 的 auth_key 不存在`)
+    }
+
+    const authKeyBuffer = Buffer.from(authKeyHex, 'hex')
+
+    if (authKeyBuffer.length !== 256) {
+      throw new Error(`Auth Key 长度不正确,期望 256 字节,实际 ${authKeyBuffer.length} 字节`)
+    }
+
+    const dcConfig = DC_CONFIGS[dcId]
+    if (!dcConfig) {
+      throw new Error(`不支持的 DC ID: ${dcId}`)
+    }
+
+    const buffers: Buffer[] = []
+
+    buffers.push(Buffer.from([dcId]))
+
+    const serverAddressBuffer = Buffer.from(dcConfig.host)
+    const addressLengthBuffer = Buffer.allocUnsafe(2)
+    addressLengthBuffer.writeInt16BE(serverAddressBuffer.length, 0)
+    buffers.push(addressLengthBuffer)
+
+    buffers.push(serverAddressBuffer)
+
+    const portBuffer = Buffer.allocUnsafe(2)
+    portBuffer.writeInt16BE(dcConfig.port, 0)
+    buffers.push(portBuffer)
+
+    buffers.push(authKeyBuffer)
+
+    const sessionBuffer = Buffer.concat(buffers)
+
+    const encodedSession = sessionBuffer.toString('base64')
+
+    const sessionString = '1' + encodedSession
+
+    return sessionString
+  } catch (error) {
+    throw new Error(`构建 StringSession 失败: ${(error as Error).message}`)
+  }
+}
+
+function extractAccountData(decodedData: DecodedSessionData): { account: TelegramAccountSession; dcId: number } | null {
+  const specialKeys = ['auth_key_fingerprint', 'user_auth', 'dc']
+
+  let dcId: number | undefined = decodedData.dc as number
+  if (!dcId && decodedData.user_auth) {
+    const userAuth = decodedData.user_auth as any
+    dcId = userAuth.dcID
+    if (dcId === 0) {
+      dcId = undefined
+    }
+  }
+
+  for (const key in decodedData) {
+    if (specialKeys.includes(key)) {
+      continue
+    }
+
+    const value = decodedData[key]
+    if (value && typeof value === 'object') {
+      const account = value as any
+
+      if (account.userId || account.dc1_auth_key || account.dc2_auth_key || account.dc5_auth_key) {
+        const accountDcId = account.dcId || dcId
+
+        if (!accountDcId) {
+          if (account.dc5_auth_key) {
+            dcId = 5
+          } else if (account.dc2_auth_key) {
+            dcId = 2
+          } else if (account.dc1_auth_key) {
+            dcId = 1
+          } else if (account.dc3_auth_key) {
+            dcId = 3
+          } else if (account.dc4_auth_key) {
+            dcId = 4
+          }
+        } else {
+          dcId = accountDcId
+        }
+
+        if (dcId) {
+          return { account: account as TelegramAccountSession, dcId }
+        }
+      }
+    }
+  }
+
+  return null
+}

+ 291 - 2
yarn.lock

@@ -17,6 +17,11 @@
   resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.6.tgz"
   integrity sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==
 
+"@cryptography/aes@^0.1.1":
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/@cryptography/aes/-/aes-0.1.1.tgz#0096726a6a2a2cfc2e8bddf3997e98e49f1a224d"
+  integrity sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg==
+
 "@cspotcode/source-map-support@^0.8.0":
   version "0.8.1"
   resolved "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz"
@@ -678,6 +683,13 @@ asn1.js@^5.4.1:
     minimalistic-assert "^1.0.0"
     safer-buffer "^2.1.0"
 
+async-mutex@^0.3.0:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df"
+  integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==
+  dependencies:
+    tslib "^2.3.1"
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz"
@@ -732,6 +744,11 @@ bcryptjs@*, bcryptjs@^3.0.2:
   resolved "https://registry.npmmirror.com/bcryptjs/-/bcryptjs-3.0.2.tgz"
   integrity sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==
 
+big-integer@^1.6.48:
+  version "1.6.52"
+  resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
+  integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==
+
 bignumber.js@^9.0.1:
   version "9.3.0"
   resolved "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.0.tgz"
@@ -815,6 +832,13 @@ buffer@^6.0.3:
     base64-js "^1.3.1"
     ieee754 "^1.2.1"
 
+bufferutil@^4.0.1, bufferutil@^4.0.3:
+  version "4.0.9"
+  resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.9.tgz#6e81739ad48a95cad45a279588e13e95e24a800a"
+  integrity sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==
+  dependencies:
+    node-gyp-build "^4.3.0"
+
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz"
@@ -979,6 +1003,14 @@ cross-spawn@^7.0.6:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+d@1, d@^1.0.1, d@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/d/-/d-1.0.2.tgz#2aefd554b81981e7dccf72d6842ae725cb17e5de"
+  integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==
+  dependencies:
+    es5-ext "^0.10.64"
+    type "^2.7.2"
+
 dateformat@^2.0.0:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/dateformat/-/dateformat-2.2.0.tgz"
@@ -994,6 +1026,13 @@ dayjs@^1.11.9:
   resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz"
   integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
 
+debug@^2.2.0:
+  version "2.6.9"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+  integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+  dependencies:
+    ms "2.0.0"
+
 debug@^4.1.1, debug@^4.3.4:
   version "4.4.0"
   resolved "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz"
@@ -1057,6 +1096,36 @@ digest-header@^1.0.0:
   resolved "https://registry.npmmirror.com/digest-header/-/digest-header-1.1.0.tgz"
   integrity sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==
 
+dom-serializer@^1.0.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
+  integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^4.2.0"
+    entities "^2.0.0"
+
+domelementtype@^2.0.1, domelementtype@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+  integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@^4.0.0, domhandler@^4.2.0:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
+  integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
+  dependencies:
+    domelementtype "^2.2.0"
+
+domutils@^2.5.2:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+  dependencies:
+    dom-serializer "^1.0.1"
+    domelementtype "^2.2.0"
+    domhandler "^4.2.0"
+
 dotenv-expand@10.0.0:
   version "10.0.0"
   resolved "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz"
@@ -1135,6 +1204,11 @@ end-or-error@^1.0.1:
   resolved "https://registry.npmmirror.com/end-or-error/-/end-or-error-1.0.1.tgz"
   integrity sha512-OclLMSug+k2A0JKuf494im25ANRBVW8qsjmwbgX7lQ8P82H21PQ1PWkoYwb9y5yMBS69BPlwtzdIFClo3+7kOQ==
 
+entities@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
+  integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
 env-schema@^6.0.0:
   version "6.0.1"
   resolved "https://registry.npmmirror.com/env-schema/-/env-schema-6.0.1.tgz"
@@ -1171,6 +1245,33 @@ es-set-tostringtag@^2.1.0:
     has-tostringtag "^1.0.2"
     hasown "^2.0.2"
 
+es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64, es5-ext@~0.10.14:
+  version "0.10.64"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714"
+  integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==
+  dependencies:
+    es6-iterator "^2.0.3"
+    es6-symbol "^3.1.3"
+    esniff "^2.0.1"
+    next-tick "^1.1.0"
+
+es6-iterator@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+  integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==
+  dependencies:
+    d "1"
+    es5-ext "^0.10.35"
+    es6-symbol "^3.1.1"
+
+es6-symbol@^3.1.1, es6-symbol@^3.1.3:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.4.tgz#f4e7d28013770b4208ecbf3e0bf14d3bcb557b8c"
+  integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==
+  dependencies:
+    d "^1.0.2"
+    ext "^1.7.0"
+
 escalade@^3.1.1:
   version "3.2.0"
   resolved "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz"
@@ -1181,6 +1282,16 @@ escape-html@^1.0.3, escape-html@~1.0.3:
   resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
+esniff@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308"
+  integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==
+  dependencies:
+    d "^1.0.1"
+    es5-ext "^0.10.62"
+    event-emitter "^0.3.5"
+    type "^2.7.2"
+
 ethereum-cryptography@^2.0.0:
   version "2.2.1"
   resolved "https://registry.npmmirror.com/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz"
@@ -1204,6 +1315,14 @@ ethers@^6.6.0:
     tslib "2.7.0"
     ws "8.17.1"
 
+event-emitter@^0.3.5:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+  integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
 event-target-shim@^5.0.0:
   version "5.0.1"
   resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
@@ -1219,6 +1338,13 @@ eventemitter3@^5.0.1:
   resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz"
   integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
 
+ext@^1.7.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f"
+  integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==
+  dependencies:
+    type "^2.7.2"
+
 extend-shallow@^2.0.1:
   version "2.0.1"
   resolved "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz"
@@ -1507,6 +1633,11 @@ gopd@^1.0.1, gopd@^1.2.0:
   resolved "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz"
   integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
 
+graceful-fs@^4.1.11:
+  version "4.2.11"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
 has-property-descriptors@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz"
@@ -1555,6 +1686,16 @@ hmac-drbg@^1.0.1:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
+htmlparser2@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
+  integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^4.0.0"
+    domutils "^2.5.2"
+    entities "^2.0.0"
+
 http-errors@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz"
@@ -1585,6 +1726,11 @@ ieee754@^1.2.1:
   resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
+imurmurhash@^0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+  integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
 inflight@^1.0.4:
   version "1.0.6"
   resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz"
@@ -1603,6 +1749,11 @@ injectpromise@^1.0.0:
   resolved "https://registry.npmmirror.com/injectpromise/-/injectpromise-1.0.0.tgz"
   integrity sha512-qNq5wy4qX4uWHcVFOEU+RqZkoVG65FhvGkyDWbuBxILMjK6A1LFf5A1mgXZkD4nRx5FCorD81X/XvPKp/zVfPA==
 
+ip-address@^10.0.1:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.0.1.tgz#a8180b783ce7788777d796286d61bce4276818ed"
+  integrity sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==
+
 ipaddr.js@^2.1.0:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz"
@@ -1708,6 +1859,11 @@ is-typed-array@^1.1.3:
   dependencies:
     which-typed-array "^1.1.16"
 
+is-typedarray@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+  integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
+
 isarray@~1.0.0:
   version "1.0.0"
   resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz"
@@ -1861,7 +2017,7 @@ mime@^2.4.5, mime@^2.5.2:
   resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz"
   integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
 
-mime@^3:
+mime@^3, mime@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/mime/-/mime-3.0.0.tgz"
   integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
@@ -1931,6 +2087,11 @@ mri@^1.2.0:
   resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz"
   integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
 
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
 ms@^2.0.0, ms@^2.1.3:
   version "2.1.3"
   resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
@@ -1967,6 +2128,11 @@ named-placeholders@^1.1.3:
   dependencies:
     lru-cache "^7.14.1"
 
+next-tick@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
+  integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
+
 node-fetch@^2.7.0:
   version "2.7.0"
   resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz"
@@ -1974,11 +2140,23 @@ node-fetch@^2.7.0:
   dependencies:
     whatwg-url "^5.0.0"
 
+node-gyp-build@^4.3.0:
+  version "4.8.4"
+  resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
+  integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
+
 node-hex@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/node-hex/-/node-hex-1.0.1.tgz"
   integrity sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==
 
+node-localstorage@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/node-localstorage/-/node-localstorage-2.2.1.tgz#869723550a4883e426cb391d2df0b563a51c7c1c"
+  integrity sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw==
+  dependencies:
+    write-file-atomic "^1.1.4"
+
 normalize-path@^3.0.0, normalize-path@~3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz"
@@ -2041,6 +2219,16 @@ package-json-from-dist@^1.0.0:
   resolved "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz"
   integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
 
+pako@^2.0.3:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
+  integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
+
+path-browserify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
+  integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
@@ -2202,6 +2390,11 @@ readdirp@~3.6.0:
   dependencies:
     picomatch "^2.2.1"
 
+real-cancellable-promise@^1.1.1:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/real-cancellable-promise/-/real-cancellable-promise-1.2.3.tgz#6df13b7db40cbff9969975d6194c11e2356c4a13"
+  integrity sha512-hBI5Gy/55VEeeMtImMgEirD7eq5UmqJf1J8dFZtbJZA/3rB0pYFZ7PayMGueb6v4UtUtpKpP+05L0VwyE1hI9Q==
+
 real-require@^0.2.0:
   version "0.2.0"
   resolved "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz"
@@ -2430,6 +2623,24 @@ signal-exit@^4.0.1:
   resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz"
   integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
 
+slide@^1.1.5:
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
+  integrity sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==
+
+smart-buffer@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
+  integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
+
+socks@^2.6.2:
+  version "2.8.7"
+  resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea"
+  integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==
+  dependencies:
+    ip-address "^10.0.1"
+    smart-buffer "^4.2.0"
+
 sonic-boom@^4.0.1:
   version "4.2.0"
   resolved "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-4.2.0.tgz"
@@ -2493,6 +2704,11 @@ steed@^1.1.3:
     fastseries "^1.7.0"
     reusify "^1.0.0"
 
+store2@^2.13.0:
+  version "2.14.4"
+  resolved "https://registry.yarnpkg.com/store2/-/store2-2.14.4.tgz#81b313abaddade4dcd7570c5cc0e3264a8f7a242"
+  integrity sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==
+
 stream-http@2.8.2:
   version "2.8.2"
   resolved "https://registry.npmmirror.com/stream-http/-/stream-http-2.8.2.tgz"
@@ -2598,6 +2814,29 @@ telegraf@^4.16.3:
     safe-compare "^1.1.4"
     sandwich-stream "^2.0.2"
 
+telegram@^2.26.22:
+  version "2.26.22"
+  resolved "https://registry.yarnpkg.com/telegram/-/telegram-2.26.22.tgz#16beb73e52403b5d5bcc4602226e39dc24217333"
+  integrity sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg==
+  dependencies:
+    "@cryptography/aes" "^0.1.1"
+    async-mutex "^0.3.0"
+    big-integer "^1.6.48"
+    buffer "^6.0.3"
+    htmlparser2 "^6.1.0"
+    mime "^3.0.0"
+    node-localstorage "^2.2.1"
+    pako "^2.0.3"
+    path-browserify "^1.0.1"
+    real-cancellable-promise "^1.1.1"
+    socks "^2.6.2"
+    store2 "^2.13.0"
+    ts-custom-error "^3.2.0"
+    websocket "^1.0.34"
+  optionalDependencies:
+    bufferutil "^4.0.3"
+    utf-8-validate "^5.0.5"
+
 thenify-all@^1.0.0:
   version "1.6.0"
   resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz"
@@ -2675,6 +2914,11 @@ tronweb@^5.3.3:
     semver "^5.6.0"
     validator "^13.7.0"
 
+ts-custom-error@^3.2.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/ts-custom-error/-/ts-custom-error-3.3.1.tgz#8bd3c8fc6b8dc8e1cb329267c45200f1e17a65d1"
+  integrity sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==
+
 ts-node-dev@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz"
@@ -2725,11 +2969,23 @@ tslib@2.7.0:
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.7.0.tgz"
   integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
 
-tslib@^2.5.0:
+tslib@^2.3.1, tslib@^2.5.0:
   version "2.8.1"
   resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
   integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
 
+type@^2.7.2:
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486"
+  integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==
+
+typedarray-to-buffer@^3.1.5:
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+  integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+  dependencies:
+    is-typedarray "^1.0.0"
+
 typeorm@^0.3.21:
   version "0.3.21"
   resolved "https://registry.npmmirror.com/typeorm/-/typeorm-0.3.21.tgz"
@@ -2789,6 +3045,13 @@ urllib@^2.44.0:
     statuses "^1.3.1"
     utility "^1.16.1"
 
+utf-8-validate@^5.0.2, utf-8-validate@^5.0.5:
+  version "5.0.10"
+  resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2"
+  integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==
+  dependencies:
+    node-gyp-build "^4.3.0"
+
 util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -3063,6 +3326,18 @@ webidl-conversions@^3.0.0:
   resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
   integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
 
+websocket@^1.0.34:
+  version "1.0.35"
+  resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.35.tgz#374197207d7d4cc4c36cbf8a1bb886ee52a07885"
+  integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==
+  dependencies:
+    bufferutil "^4.0.1"
+    debug "^2.2.0"
+    es5-ext "^0.10.63"
+    typedarray-to-buffer "^3.1.5"
+    utf-8-validate "^5.0.2"
+    yaeti "^0.0.6"
+
 whatwg-url@^5.0.0:
   version "5.0.0"
   resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz"
@@ -3140,6 +3415,15 @@ wrappy@1:
   resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz"
   integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
 
+write-file-atomic@^1.1.4:
+  version "1.3.4"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f"
+  integrity sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==
+  dependencies:
+    graceful-fs "^4.1.11"
+    imurmurhash "^0.1.4"
+    slide "^1.1.5"
+
 ws@8.17.1:
   version "8.17.1"
   resolved "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz"
@@ -3186,6 +3470,11 @@ y18n@^5.0.5:
   resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz"
   integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
 
+yaeti@^0.0.6:
+  version "0.0.6"
+  resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577"
+  integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==
+
 yaml@^2.4.1, yaml@^2.4.2:
   version "2.7.1"
   resolved "https://registry.npmmirror.com/yaml/-/yaml-2.7.1.tgz"