// ==UserScript== // @name 腾讯会议转写纪要导出神器 (Tencent Meeting Transcript Exporter) // @namespace https://github.com/awesome-tampermonkey // @version 1.0.0 // @description 一键导出腾讯会议录制视频的转写内容和纪要,支持Markdown、HTML、TXT格式导出和复制 // @author 东哥说AI // @match https://meeting.tencent.com/cw/* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 配置常量 const CONFIG = { BUTTON_STYLE: ` position: fixed; top: 20px; right: 20px; z-index: 10000; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; padding: 12px 20px; font-size: 14px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); transition: all 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; `, MODAL_STYLE: ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 10001; display: flex; justify-content: center; align-items: center; `, MODAL_CONTENT_STYLE: ` background: white; border-radius: 12px; padding: 30px; max-width: 500px; width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; ` }; // 工具函数 const utils = { // 获取会议标题 getMeetingTitle() { const titleElement = document.querySelector('.meeting-main-subject .subject') || document.querySelector('.meeting-subject') || document.querySelector('.meeting-title'); return titleElement ? titleElement.textContent.trim() : '腾讯会议转写'; }, // 获取录制时间 getRecordingTime() { const timeElement = document.querySelector('.meeting-begin-time-in-date'); if (timeElement) { const timeText = timeElement.textContent.trim(); // 格式:2025/05/21 21:35:16 return timeText; } return new Date().toLocaleString('zh-CN'); }, // 获取会议关键词/标签 getMeetingKeywords() { const keywords = []; const topicElements = document.querySelectorAll('.topicTag .topicText'); topicElements.forEach(element => { const keyword = element.textContent.trim(); if (keyword) { keywords.push(keyword); } }); return keywords; }, // 获取当前激活的Tab类型 getCurrentTabType() { const activeTab = document.querySelector('.met-tabs__tabitem.is-active .tab'); if (!activeTab) return 'transcript'; const tabText = activeTab.textContent.trim(); if (tabText.includes('转写')) return 'transcript'; if (tabText.includes('纪要')) return 'summary'; return 'transcript'; }, // 获取转写内容(支持虚拟滚动) async getTranscriptContent() { // 查找虚拟滚动容器 const scrollContainer = document.querySelector('.auto-meeting-minutes .minutes-module-list'); if (!scrollContainer) { // 降级到原始方法 return this.getTranscriptContentFallback(); } const originalScrollTop = scrollContainer.scrollTop; let lastScrollTop = -1; let allContent = new Map(); // 使用Map避免重复 try { // 滚动到顶部开始 scrollContainer.scrollTop = 0; await this.sleep(200); // 持续滚动直到底部,收集所有内容 while (scrollContainer.scrollTop !== lastScrollTop) { lastScrollTop = scrollContainer.scrollTop; // 收集当前可见的转写内容 const currentElements = scrollContainer.querySelectorAll('.paragraph-module_detail-page-style__Lhz8l'); currentElements.forEach(element => { const pidAttr = element.getAttribute('data-pid'); if (pidAttr && !allContent.has(pidAttr)) { const timeElement = element.querySelector('.paragraph-module_p-start-time__QAWWl'); const speakerElement = element.querySelector('.paragraph-module_speaker-name__afSbd'); const textElement = element.querySelector('.paragraph-module_sentences__zK2oL'); if (textElement && textElement.textContent.trim()) { const time = timeElement ? timeElement.textContent.trim() : ''; const speaker = speakerElement ? speakerElement.textContent.trim() : '未知发言人'; const text = textElement.textContent.trim(); allContent.set(pidAttr, { pid: parseInt(pidAttr), time, speaker, text }); } } }); // 向下滚动一屏 scrollContainer.scrollTop += scrollContainer.clientHeight; await this.sleep(100); // 等待内容加载 } // 恢复原始滚动位置 scrollContainer.scrollTop = originalScrollTop; // 按pid排序并返回 return Array.from(allContent.values()).sort((a, b) => a.pid - b.pid); } catch (error) { console.error('获取转写内容时出错:', error); // 恢复滚动位置 scrollContainer.scrollTop = originalScrollTop; // 降级到原始方法 return this.getTranscriptContentFallback(); } }, // 降级方法:获取当前可见的转写内容 getTranscriptContentFallback() { const transcriptElements = document.querySelectorAll('.auto-meeting-minutes .paragraph-module_detail-page-style__Lhz8l'); let content = []; transcriptElements.forEach(element => { const timeElement = element.querySelector('.paragraph-module_p-start-time__QAWWl'); const speakerElement = element.querySelector('.paragraph-module_speaker-name__afSbd'); const textElement = element.querySelector('.paragraph-module_sentences__zK2oL'); if (textElement && textElement.textContent.trim()) { const time = timeElement ? timeElement.textContent.trim() : ''; const speaker = speakerElement ? speakerElement.textContent.trim() : '未知发言人'; const text = textElement.textContent.trim(); content.push({ time, speaker, text }); } }); return content; }, // 延时函数 sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, // 获取纪要内容 getSummaryContent() { const summaryContainer = document.querySelector('.summary-content-wrap'); if (!summaryContainer) return null; const summaryElements = summaryContainer.querySelectorAll('h4, p, li, div[contenteditable="true"]'); let content = []; summaryElements.forEach(element => { const text = element.textContent.trim(); if (text && text.length > 0) { const tagName = element.tagName.toLowerCase(); content.push({ type: tagName, text: text }); } }); return content; }, // 格式化为Markdown formatAsMarkdown(data, type) { const title = this.getMeetingTitle(); const recordingTime = this.getRecordingTime(); const keywords = this.getMeetingKeywords(); const exportTime = new Date().toLocaleString('zh-CN'); let markdown = `# ${title}\n\n`; markdown += `**录制时间**: ${recordingTime}\n`; markdown += `**导出时间**: ${exportTime}\n`; markdown += `**内容类型**: ${type === 'transcript' ? '转写内容' : '会议纪要'}\n`; // 添加关键词标签 if (keywords.length > 0) { markdown += `**会议关键词**: ${keywords.join('、')}\n`; } markdown += `\n`; if (type === 'transcript' && Array.isArray(data)) { markdown += `## 转写内容\n\n`; data.forEach(item => { markdown += `### ${item.speaker}${item.time ? ` (${item.time})` : ''}\n\n`; markdown += `${item.text}\n\n`; }); } else if (type === 'summary' && Array.isArray(data)) { markdown += `## 会议纪要\n\n`; data.forEach(item => { if (item.type === 'h4') { markdown += `### ${item.text}\n\n`; } else if (item.type === 'li') { markdown += `- ${item.text}\n`; } else { markdown += `${item.text}\n\n`; } }); } return markdown; }, // 格式化为HTML formatAsHTML(data, type) { const title = this.getMeetingTitle(); const recordingTime = this.getRecordingTime(); const keywords = this.getMeetingKeywords(); const exportTime = new Date().toLocaleString('zh-CN'); let html = `\n\n
\n`; html += ` \n`; html += ` \n`; html += `录制时间: ${recordingTime}
\n`; html += `导出时间: ${exportTime}
\n`; html += `内容类型: ${type === 'transcript' ? '转写内容' : '会议纪要'}
\n`; if (keywords.length > 0) { html += `${item.text}
\n`; } }); } html += `\n`; return html; }, // 格式化为TXT formatAsTXT(data, type) { const title = this.getMeetingTitle(); const recordingTime = this.getRecordingTime(); const keywords = this.getMeetingKeywords(); const exportTime = new Date().toLocaleString('zh-CN'); let txt = `${title}\n`; txt += `${'='.repeat(title.length)}\n\n`; txt += `录制时间: ${recordingTime}\n`; txt += `导出时间: ${exportTime}\n`; txt += `内容类型: ${type === 'transcript' ? '转写内容' : '会议纪要'}\n`; // 添加关键词标签 if (keywords.length > 0) { txt += `会议关键词: ${keywords.join('、')}\n`; } txt += `\n`; if (type === 'transcript' && Array.isArray(data)) { txt += `转写内容\n${'-'.repeat(10)}\n\n`; data.forEach(item => { txt += `${item.speaker}${item.time ? ` (${item.time})` : ''}\n`; txt += `${item.text}\n\n`; }); } else if (type === 'summary' && Array.isArray(data)) { txt += `会议纪要\n${'-'.repeat(10)}\n\n`; data.forEach(item => { if (item.type === 'h4') { txt += `\n${item.text}\n${'-'.repeat(item.text.length)}\n`; } else if (item.type === 'li') { txt += `• ${item.text}\n`; } else { txt += `${item.text}\n\n`; } }); } return txt; }, // 下载文件 downloadFile(content, filename, type) { const mimeTypes = { 'md': 'text/markdown', 'html': 'text/html', 'txt': 'text/plain' }; const blob = new Blob([content], { type: mimeTypes[type] || 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, // 复制到剪贴板 async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (err) { // 降级方案 const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); const success = document.execCommand('copy'); document.body.removeChild(textArea); return success; } }, // 显示提示消息 showMessage(message, type = 'success') { const messageDiv = document.createElement('div'); const colors = { 'success': '#10b981', 'error': '#ef4444', 'info': '#3b82f6' }; messageDiv.style.cssText = ` position: fixed; top: 80px; right: 20px; z-index: 10002; padding: 12px 20px; border-radius: 6px; color: white; font-weight: 600; font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); transition: all 0.3s ease; background: ${colors[type] || colors.success}; `; messageDiv.textContent = message; document.body.appendChild(messageDiv); setTimeout(() => { messageDiv.style.opacity = '0'; messageDiv.style.transform = 'translateX(100%)'; setTimeout(() => { if (messageDiv.parentNode) { messageDiv.parentNode.removeChild(messageDiv); } }, 300); }, 3000); } }; // 主要功能类 class TencentMeetingExporter { constructor() { this.init(); } init() { this.createExportButton(); } createExportButton() { const button = document.createElement('button'); button.textContent = '📝 导出转写/纪要'; button.style.cssText = CONFIG.BUTTON_STYLE; button.addEventListener('mouseenter', () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)'; }); button.addEventListener('mouseleave', () => { button.style.transform = 'translateY(0)'; button.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.4)'; }); button.addEventListener('click', () => { this.showExportModal(); }); document.body.appendChild(button); } showExportModal() { const modal = document.createElement('div'); modal.style.cssText = CONFIG.MODAL_STYLE; const modalContent = document.createElement('div'); modalContent.style.cssText = CONFIG.MODAL_CONTENT_STYLE; const currentTab = utils.getCurrentTabType(); const tabName = currentTab === 'transcript' ? '转写内容' : '会议纪要'; modalContent.innerHTML = `选择导出格式,文件名将自动使用会议标题