Skip to content

Instantly share code, notes, and snippets.

@eeichinger
Created May 5, 2026 20:51
Show Gist options
  • Select an option

  • Save eeichinger/b45d4b4519cdb65687d253402301bac6 to your computer and use it in GitHub Desktop.

Select an option

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.
(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