Last active
June 23, 2025 22:17
-
-
Save trojblue/4a1df5023e6edf91dcda294ce665a390 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Export Full Meeting Transcripts (Lark) | |
// @namespace https://example.com | |
// @version 1.1 | |
// @description Export all visible and virtualized transcript data | |
// @match https://*.larksuite.com/minutes/* | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
const PARAGRAPH_SELECTOR = '.paragraph-editor-wrapper'; | |
const SPEAKER_SELECTOR = '.p-user-name'; | |
const TIME_SELECTOR = '.p-time'; | |
const TEXT_LINE_SELECTOR = '.ace-line'; | |
const exportButton = document.createElement('button'); | |
exportButton.textContent = 'Export Full Transcripts'; | |
Object.assign(exportButton.style, { | |
position: 'fixed', | |
top: '10px', | |
right: '10px', | |
zIndex: 99999, | |
padding: '6px 12px', | |
backgroundColor: '#007bff', | |
color: '#fff', | |
border: 'none', | |
borderRadius: '4px', | |
cursor: 'pointer' | |
}); | |
document.body.appendChild(exportButton); | |
const seenParagraphs = new Set(); | |
function extractParagraphData(paragraph) { | |
const key = paragraph.getAttribute('data-zone-id'); | |
if (key && seenParagraphs.has(key)) return null; | |
if (key) seenParagraphs.add(key); | |
const speakerEl = paragraph.querySelector(SPEAKER_SELECTOR); | |
const timeEl = paragraph.querySelector(TIME_SELECTOR); | |
const textEls = paragraph.querySelectorAll(TEXT_LINE_SELECTOR); | |
const speaker = speakerEl?.getAttribute('user-name-content') || speakerEl?.textContent.trim() || ''; | |
const time = timeEl?.getAttribute('time-content') || timeEl?.textContent.trim() || ''; | |
let text = ''; | |
textEls.forEach(line => { | |
text += line.innerText.trim() + ' '; | |
}); | |
return { | |
speaker, | |
time, | |
text: text.trim() | |
}; | |
} | |
async function scrollAndCollectTranscripts() { | |
const container = document.querySelector('.rc-virtual-list-holder'); | |
if (!container) { | |
alert("Transcript container not found."); | |
return; | |
} | |
const results = []; | |
let previousScrollTop = -1; | |
while (true) { | |
const paragraphs = document.querySelectorAll(PARAGRAPH_SELECTOR); | |
for (const p of paragraphs) { | |
const data = extractParagraphData(p); | |
if (data) results.push(data); | |
} | |
container.scrollTop += 500; | |
await new Promise(r => setTimeout(r, 300)); // wait for content to load | |
// Check if we're at the bottom (no new scroll movement) | |
if (container.scrollTop === previousScrollTop) break; | |
previousScrollTop = container.scrollTop; | |
} | |
return results; | |
} | |
function downloadFile(filename, content) { | |
const blob = new Blob([content], { type: 'application/json' }); | |
const url = URL.createObjectURL(blob); | |
const link = document.createElement('a'); | |
link.href = url; | |
link.download = filename; | |
link.click(); | |
URL.revokeObjectURL(url); | |
} | |
exportButton.onclick = async () => { | |
exportButton.textContent = 'Exporting...'; | |
const results = await scrollAndCollectTranscripts(); | |
downloadFile('full_transcript.json', JSON.stringify(results, null, 2)); | |
exportButton.textContent = 'Export Full Transcripts'; | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment