油猴脚本,一键导出腾讯会议转写内容

想把腾讯视频会议转写的文字稿导出,在网上找到一个油猴脚本,无法使用,然后放到 deepseek 里面让其修改,经过几次反复,修改成功。下面是代码,复制到油猴里面就可以用了。代码里面有修改前代码的 github 地址。

油猴脚本,一键导出腾讯会议转写内容

// ==UserScript==
// @name         腾讯会议转写纪要导出神器 (Tencent Meeting Transcript Exporter) 修复版
// @namespace    https://github.com/awesome-tampermonkey
// @version      1.1.2
// @description  一键导出腾讯会议录制视频和纯文字转写的内容和纪要,支持 Markdown、HTML、TXT 格式导出和复制
// @author       东哥说 AI
// @match        https://meeting.tencent.com/cw/*
// @match        https://meeting.tencent.com/ct/*
// @grant        none
// @license      MIT
// ==/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 = {getPageType() {
            const url = window.location.href;
            if (url.includes('/cw/')) return 'recording';
            if (url.includes('/ct/')) return 'transcript';
            return 'unknown';
        },
 
        getMeetingTitle() {
            // 尝试多种方式获取会议标题
            const selectors = [
                '.meeting-main-subject .subject',
                '.meeting-subject',
                '.meeting-title',
                '.style_header-info__6mQkP', // 新增选择器
                'h1', // 通用标题
            ];
             
            for (const sel of selectors) {const el = document.querySelector(sel);
                if (el) {const text = el.textContent.trim();
                    // 如果只获取到部分内容(比如 "返回" 等),继续查找
                    if (text && text.length> 3 && !text.startsWith('返回')) {return text;}
                }
            }
             
            // 如果以上都失败,从 URL 或其他地方获取
            const pageTitle = document.title;
            if (pageTitle && pageTitle !== '腾讯会议') {return pageTitle;}
             
            return '腾讯会议转写';
        },
         
        getRecordingTime() {
            // 尝试从页面中提取录制时间
            const selectors = [
                '.meeting-begin-time-in-date',
                '.meeting-time',
                '[class*="time"]',
            ];
             
            for (const sel of selectors) {const el = document.querySelector(sel);
                if (el) {const text = el.textContent.trim();
                    // 匹配时间格式
                    if (/\d{4}[\/\-]\d{2}[\/\-]\d{2}/.test(text)) {return text.match(/\d{4}[\/\-]\d{2}[\/\-]\d{2}[\s\d\:]*/)[0];
                    }
                }
            }
             
            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;
        },
 
        getCurrentTabType() {const pageType = this.getPageType();
            if (pageType === 'transcript') return 'both';
 
            const activeTab = document.querySelector('.met-tabs__tabitem.is-active .tab');
            if (!activeTab) return 'transcript';
 
            const tabText = activeTab.textContent.trim();
            if (tabText.includes('纪要')) return 'summary';
            return 'transcript';
        },
 
        // 修复后的转写内容获取方法
        async getTranscriptContent() {const pageType = this.getPageType();
             
            // 查找虚拟滚动容器
            const scrollContainer = document.querySelector('.minutes-module-list');
             
            if (!scrollContainer) {return this.getTranscriptContentFallback();
            }
             
            // 检查是否需要滚动
            if (scrollContainer.scrollHeight <= scrollContainer.clientHeight + 100) {return this.getTranscriptContentFallback();
            }
             
            const originalScrollTop = scrollContainer.scrollTop;
            let allContent = new Map();
            let lastScrollTop = -1;
            let stuckCounter = 0;
             
            try {
                // 先滚动到顶部
                scrollContainer.scrollTop = 0;
                await this.sleep(300);
                 
                // 持续滚动直到底部
                while (scrollContainer.scrollTop !== lastScrollTop && stuckCounter < 3) {
                    lastScrollTop = scrollContainer.scrollTop;
                     
                    // 收集当前可见的内容
                    const paragraphs = document.querySelectorAll('.paragraph-module_detail-page-style__Lhz8l');
                     
                    paragraphs.forEach(para => {const pid = para.getAttribute('data-pid');
                        if (!pid || allContent.has(pid)) return;
                         
                        const speakerElement = para.querySelector('.paragraph-module_speaker-name__afSbd');
                        const timeElement = para.querySelector('.paragraph-module_p-start-time__QAWWl');
                        const textElement = para.querySelector('.paragraph-module_sentences__zK2oL');
                         
                        if (textElement && textElement.textContent.trim()) {
                            allContent.set(pid, {pid: parseInt(pid),
                                time: timeElement ? timeElement.textContent.trim() : '',
                                speaker: speakerElement ? speakerElement.textContent.trim() : '未知发言人',
                                text: textElement.textContent.trim()});
                        }
                    });
                     
                    // 向下滚动
                    const newScrollTop = scrollContainer.scrollTop + scrollContainer.clientHeight * 0.8;
                    scrollContainer.scrollTop = newScrollTop;
                     
                    // 等待渲染
                    await this.sleep(150);
                     
                    // 检查是否卡住
                    if (scrollContainer.scrollTop === lastScrollTop) {
                        stuckCounter++;
                        // 尝试多滚动一点
                        scrollContainer.scrollTop += 50;
                        await this.sleep(100);
                    } else {stuckCounter = 0;}
                }
                 
                // 恢复原始滚动位置
                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 paragraphs = document.querySelectorAll('.paragraph-module_detail-page-style__Lhz8l');
            let content = [];
             
            paragraphs.forEach(para => {const speakerElement = para.querySelector('.paragraph-module_speaker-name__afSbd');
                const timeElement = para.querySelector('.paragraph-module_p-start-time__QAWWl');
                const textElement = para.querySelector('.paragraph-module_sentences__zK2oL');
                 
                if (textElement && textElement.textContent.trim()) {
                    content.push({time: timeElement ? timeElement.textContent.trim() : '',
                        speaker: speakerElement ? speakerElement.textContent.trim() : '未知发言人',
                        text: textElement.textContent.trim()});
                }
            });
             
            return content;
        },
 
        sleep(ms) {return new Promise(resolve => setTimeout(resolve, ms));
        },
 
        getSummaryContent() {const pageType = this.getPageType();
             
            if (pageType === 'transcript') {const summaryContainer = document.querySelector('.aisummary-container .summary-content-wrap');
                if (!summaryContainer) return null;
 
                const elements = summaryContainer.querySelectorAll('h4, p, li');
                return Array.from(elements).map(el => ({type: el.tagName.toLowerCase(),
                    text: el.textContent.trim()})).filter(item => item.text);
            }
 
            const summaryContainer = document.querySelector('.summary-content-wrap');
            if (!summaryContainer) return null;
 
            const elements = summaryContainer.querySelectorAll('h4, p, li, div[contenteditable="true"]');
            return Array.from(elements).map(el => ({type: el.tagName.toLowerCase(),
                text: el.textContent.trim()})).filter(item => item.text);
        },
 
        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---\n\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---\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;
        },
 
        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 += `    ${title}\n`;
            html += `    \n\n\n`;
             
            html += `    
\n`; html += `

${title}

\n`; html += `

录制时间 : ${recordingTime}

\n`; html += `

导出时间 : ${exportTime}

\n`; html += `

内容类型 : ${type === 'transcript' ? '转写内容' : '会议纪要'}

\n`; if (keywords.length> 0) {html += `
\n`; html += ` 会议关键词 : `; keywords.forEach(keyword => {html += `${keyword}`; }); html += `\n
\n`; } html += `
\n`; if (type === 'transcript' && Array.isArray(data)) {html += `

转写内容

\n`; data.forEach(item => {html += `
\n`; html += `
${item.speaker}${item.time ? ` (${item.time})` : ''}
\n`; html += `
${item.text}
\n`; html += `
\n`; }); } else if (type === 'summary' && Array.isArray(data)) {html += `

会议纪要

\n`; data.forEach(item => {if (item.type === 'h4') {html += `

${item.text}

\n`; } else if (item.type === 'li') {html += `
  • ${item.text}
  • \n`; } else {html += `

    ${item.text}

    \n`; } }); } html += `\n`; return html; }, 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(Math.min(title.length, 50))}\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(Math.min(item.text.length, 30))}\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(['\ufeff' + content], {type: mimeTypes[type] || 'text/plain' }); // 添加 BOM 解决中文乱码 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; textArea.style.position = 'fixed'; textArea.style.opacity = '0'; document.body.appendChild(textArea); textArea.select(); const success = document.execCommand('copy'); document.body.removeChild(textArea); return success; } }, showMessage(message, type = 'success') {const existingMsg = document.querySelector('.tm-exporter-message'); if (existingMsg) existingMsg.remove(); const messageDiv = document.createElement('div'); messageDiv.className = 'tm-exporter-message'; const colors = { 'success': '#10b981', 'error': '#ef4444', 'info': '#3b82f6', 'warning': '#f59e0b' }; 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}; max-width: 400px; word-wrap: break-word; `; 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 existingBtn = document.querySelector('.tm-exporter-btn'); if (existingBtn) existingBtn.remove(); const button = document.createElement('button'); button.className = 'tm-exporter-btn'; 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 currentTab = utils.getCurrentTabType(); let modalHTML; if (currentTab === 'both') { modalHTML = `

    导出转写 / 纪要

    选择要导出的内容类型和格式

    `; } else { const tabName = currentTab === 'transcript' ? '转写内容' : '会议纪要'; modalHTML = `

    导出 ${tabName}

    选择导出格式,文件名将自动使用会议标题

    `; } const modal = document.createElement('div'); modal.style.cssText = CONFIG.MODAL_STYLE; const modalContent = document.createElement('div'); modalContent.style.cssText = CONFIG.MODAL_CONTENT_STYLE; modalContent.innerHTML = modalHTML; // 添加按钮悬停效果 const buttons = modalContent.querySelectorAll('button[id^="export-"], button[id^="copy-"]'); buttons.forEach(btn => {btn.addEventListener('mouseenter', () => { btn.style.background = btn.style.color; btn.style.color = 'white'; }); btn.addEventListener('mouseleave', () => { btn.style.background = 'white'; btn.style.color = btn.style.borderColor; }); }); // 绑定事件 if (currentTab === 'both') {modalContent.querySelector('#export-transcript-md')?.addEventListener('click', () => this.exportContent('md', 'transcript')); modalContent.querySelector('#export-summary-md')?.addEventListener('click', () => this.exportContent('md', 'summary')); modalContent.querySelector('#export-transcript-html')?.addEventListener('click', () => this.exportContent('html', 'transcript')); modalContent.querySelector('#export-summary-html')?.addEventListener('click', () => this.exportContent('html', 'summary')); modalContent.querySelector('#export-transcript-txt')?.addEventListener('click', () => this.exportContent('txt', 'transcript')); modalContent.querySelector('#copy-transcript-md')?.addEventListener('click', () => this.copyContent('transcript')); } else {modalContent.querySelector('#export-md')?.addEventListener('click', () => this.exportContent('md')); modalContent.querySelector('#export-html')?.addEventListener('click', () => this.exportContent('html')); modalContent.querySelector('#export-txt')?.addEventListener('click', () => this.exportContent('txt')); modalContent.querySelector('#copy-md')?.addEventListener('click', () => this.copyContent()); } modalContent.querySelector('#modal-close').addEventListener('click', () => this.closeModal(modal)); modal.addEventListener('click', (e) => {if (e.target === modal) {this.closeModal(modal); } }); modal.appendChild(modalContent); document.body.appendChild(modal); } async exportContent(format, contentType = null) { try {const currentTab = utils.getCurrentTabType(); const exportType = contentType || (currentTab === 'both' ? 'transcript' : currentTab); let data; if (exportType === 'transcript') {utils.showMessage('正在获取转写内容,请稍候...', 'info'); data = await utils.getTranscriptContent(); if (!data || data.length === 0) {utils.showMessage('未找到转写内容,请确保页面已加载完成', 'error'); return; } utils.showMessage(` 获取成功!共找到 ${data.length} 条转写记录 `, 'success'); } else {data = utils.getSummaryContent(); if (!data || data.length === 0) {utils.showMessage('未找到纪要内容,请确保页面已加载完成', 'error'); return; } } const title = utils.getMeetingTitle(); const recordingTime = utils.getRecordingTime(); let timestamp; if (recordingTime) {timestamp = recordingTime.replace(/[^\d]/g, '').slice(0, 14); if (timestamp.length>= 12) {timestamp = timestamp.slice(0, 8) + '_' + timestamp.slice(8); } } else {const now = new Date(); timestamp = now.getFullYear().toString() + (now.getMonth() + 1).toString().padStart(2, '0') + now.getDate().toString().padStart(2, '0') + '_' + now.getHours().toString().padStart(2, '0') + now.getMinutes().toString().padStart(2, '0') + now.getSeconds().toString().padStart(2, '0'); } const tabName = exportType === 'transcript' ? '转写' : '纪要'; // 清理文件名中的特殊字符 const safeTitle = title.replace(/[\\/:*?"<>|]/g,'_'); const filename = `${safeTitle}_${tabName}_${timestamp}.${format}`; let content; switch (format) { case 'md': content = utils.formatAsMarkdown(data, exportType); break; case 'html': content = utils.formatAsHTML(data, exportType); break; case 'txt': content = utils.formatAsTXT(data, exportType); break; } utils.downloadFile(content, filename, format); utils.showMessage(`${format.toUpperCase()} 文件导出成功!`); const modal = document.querySelector('div[style*="position: fixed"][style*="z-index: 10001"]'); if (modal) this.closeModal(modal); } catch (error) {console.error('导出失败:', error); utils.showMessage('导出失败:' + error.message, 'error'); } } async copyContent(contentType = null) { try {const currentTab = utils.getCurrentTabType(); const exportType = contentType || (currentTab === 'both' ? 'transcript' : currentTab); let data; if (exportType === 'transcript') {utils.showMessage('正在获取转写内容,请稍候...', 'info'); data = await utils.getTranscriptContent(); if (!data || data.length === 0) {utils.showMessage('未找到转写内容,请确保页面已加载完成', 'error'); return; } utils.showMessage(` 获取成功!共找到 ${data.length} 条转写记录 `, 'success'); } else {data = utils.getSummaryContent(); if (!data || data.length === 0) {utils.showMessage('未找到纪要内容,请确保页面已加载完成', 'error'); return; } } const content = utils.formatAsMarkdown(data, exportType); const success = await utils.copyToClipboard(content); if (success) {utils.showMessage('Markdown 内容已复制到剪贴板!'); } else {utils.showMessage('复制失败,请重试', 'error'); } const modal = document.querySelector('div[style*="position: fixed"][style*="z-index: 10001"]'); if (modal) this.closeModal(modal); } catch (error) {console.error('复制失败:', error); utils.showMessage('复制失败:' + error.message, 'error'); } } closeModal(modal) { modal.style.opacity = '0'; setTimeout(() => {if (modal.parentNode) {modal.parentNode.removeChild(modal); } }, 300); } } // 页面加载完成后初始化 function init() { // 先检查页面是否包含转写内容 const checkInterval = setInterval(() => {const hasContent = document.querySelector('.minutes-module-list'); if (hasContent) {clearInterval(checkInterval); new TencentMeetingExporter();} }, 500); // 最多等待 10 秒 setTimeout(() => {clearInterval(checkInterval); if (!document.querySelector('.tm-exporter-btn')) {new TencentMeetingExporter(); } }, 10000); } init();})();

    © 版权声明
    THE END
    喜欢就支持一下吧
    点赞15 分享
    评论 抢沙发

    请登录后发表评论

      暂无评论内容