Created
May 5, 2026 20:51
-
-
Save eeichinger/b45d4b4519cdb65687d253402301bac6 to your computer and use it in GitHub Desktop.
Extract Transcripts from MS Teams if download is unavailable - open Teams in browser, open recap and paste below script into browser devtools/console.
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
| (async () => { | |
| const list = document.querySelector('.ms-List'); | |
| if (!list) return console.error('No .ms-List found'); | |
| let scroller = list; | |
| while (scroller && scroller !== document.body) { | |
| const cs = getComputedStyle(scroller); | |
| if (/(auto|scroll)/.test(cs.overflowY) && scroller.scrollHeight > scroller.clientHeight) break; | |
| scroller = scroller.parentElement; | |
| } | |
| const entries = new Map(); | |
| const collect = () => { | |
| document.querySelectorAll('[id^="sub-entry-"]').forEach(sub => { | |
| const pos = parseInt(sub.getAttribute('aria-posinset') || '0', 10); | |
| if (!pos || entries.has(pos)) return; | |
| const cell = sub.closest('[data-list-index]'); | |
| const speaker = cell?.querySelector('.itemDisplayName-387')?.textContent?.trim() || ''; | |
| const ts = cell?.querySelector('[id^="Header-timestamp-"]')?.textContent?.trim() || ''; | |
| entries.set(pos, { pos, speaker, ts, text: sub.textContent.trim() }); | |
| }); | |
| }; | |
| scroller.scrollTop = 0; | |
| await new Promise(r => setTimeout(r, 600)); | |
| collect(); | |
| let prev = -1, stagnant = 0; | |
| while (stagnant < 3) { | |
| scroller.scrollTop += scroller.clientHeight * 0.7; | |
| await new Promise(r => setTimeout(r, 350)); | |
| collect(); | |
| if (scroller.scrollTop === prev) stagnant++; else stagnant = 0; | |
| prev = scroller.scrollTop; | |
| } | |
| scroller.scrollTop = scroller.scrollHeight; | |
| await new Promise(r => setTimeout(r, 600)); | |
| collect(); | |
| const sorted = [...entries.values()].sort((a, b) => a.pos - b.pos); | |
| let lastSpeaker = ''; | |
| const out = sorted.map(e => { | |
| const sp = e.speaker || lastSpeaker; | |
| if (e.speaker) lastSpeaker = e.speaker; | |
| return `[${e.ts || '?'}] ${sp}: ${e.text}`; | |
| }).join('\n'); | |
| const expected = document.querySelector('[aria-setsize]')?.getAttribute('aria-setsize'); | |
| console.log(`Captured ${sorted.length} / ${expected} entries`); | |
| window.__transcript = out; | |
| console.log('Stored on window.__transcript (length: ' + out.length + ' chars)'); | |
| // Try modern clipboard API | |
| try { | |
| await navigator.clipboard.writeText(out); | |
| console.log('✅ Copied to clipboard via navigator.clipboard'); | |
| return; | |
| } catch (e) { | |
| console.warn('navigator.clipboard failed:', e.message); | |
| } | |
| // Fallback: textarea + execCommand | |
| const ta = document.createElement('textarea'); | |
| ta.value = out; | |
| ta.style.position = 'fixed'; | |
| ta.style.left = '0'; | |
| ta.style.top = '0'; | |
| ta.style.opacity = '0'; | |
| document.body.appendChild(ta); | |
| ta.focus(); | |
| ta.select(); | |
| try { | |
| const ok = document.execCommand('copy'); | |
| console.log(ok ? '✅ Copied via execCommand' : '❌ execCommand failed'); | |
| } finally { | |
| document.body.removeChild(ta); | |
| } | |
| console.log('If both copy methods failed, run: console.log(window.__transcript) and select-all in the console output, OR run: copy(window.__transcript) in Chrome DevTools.'); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment