Created
May 12, 2025 05:11
-
-
Save meta-ks/2d3270f73080e664e002a770a824a9df to your computer and use it in GitHub Desktop.
Extract teams meeting transcript from browser console. Works as on May, 2025
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
// Function to extract transcript | |
function extractTranscript() { | |
// Get all list cells that contain transcript entries | |
const listCells = document.querySelectorAll('.ms-List-cell'); | |
let transcript = ''; | |
// Iterate through each list cell | |
listCells.forEach((cell, index) => { | |
// Get speaker name if it exists | |
const speakerEl = cell.querySelector('[class*="itemDisplayName-"]'); | |
const speaker = speakerEl ? speakerEl.textContent.trim() : ''; | |
// Get timestamp if it exists | |
const timestampEl = cell.querySelector('[id^="Header-timestamp-"]'); | |
const timestamp = timestampEl ? timestampEl.textContent.trim() : ''; | |
// Get the text content | |
const textEl = cell.querySelector('[class*="entryText-"], [class*="eventText-"]'); | |
const text = textEl ? textEl.textContent.trim() : ''; | |
// Only add entries that have text content | |
if (text) { | |
// Format and add to transcript | |
if (speaker && timestamp) { | |
transcript += `[${timestamp}] ${speaker}: ${text}\n\n`; | |
} else if (speaker) { | |
transcript += `${speaker}: ${text}\n\n`; | |
} else { | |
transcript += `${text}\n\n`; | |
} | |
} | |
}); | |
return transcript; | |
}// Function to set up a mutation observer to capture transcript entries | |
function setupTranscriptCapture() { | |
// Create a set to store unique transcript entries | |
const capturedEntries = new Set(); | |
// Track transcript items by a unique identifier | |
const entryMap = new Map(); | |
// Create a visual indicator to show capture is active | |
const indicator = document.createElement('div'); | |
indicator.style.cssText = 'position:fixed;top:10px;right:10px;background:green;color:white;padding:10px;border-radius:5px;z-index:9999;'; | |
indicator.textContent = 'Transcript capture active: 0 entries'; | |
document.body.appendChild(indicator); | |
// Find the scrollable container with transcript items - using a more reliable selector | |
const scrollContainer = document.querySelector('#OneTranscript [data-is-scrollable="true"]'); | |
if (!scrollContainer) { | |
console.error('Could not find transcript container'); | |
return null; | |
} | |
// Create a function to extract and process entries | |
function processEntry(entry) { | |
const speakerEl = entry.querySelector('[class*="itemDisplayName-"]'); | |
const speaker = speakerEl ? speakerEl.textContent.trim() : ''; | |
const timestampEl = entry.querySelector('[id^="Header-timestamp-"]'); | |
const timestamp = timestampEl ? timestampEl.textContent.trim() : ''; | |
const textEl = entry.querySelector('[class*="entryText-"], [class*="eventText-"]'); | |
const text = textEl ? textEl.textContent.trim() : ''; | |
if (text) { | |
// Create a unique key for this entry | |
const key = `${timestamp}|${speaker}|${text}`; | |
// Store formatted entry text | |
let formattedEntry = ''; | |
if (speaker && timestamp) { | |
formattedEntry = `[${timestamp}] ${speaker}: ${text}`; | |
} else if (speaker) { | |
formattedEntry = `${speaker}: ${text}`; | |
} else { | |
formattedEntry = text; | |
} | |
// Only add if we haven't seen this entry before | |
if (!entryMap.has(key)) { | |
entryMap.set(key, formattedEntry); | |
capturedEntries.add(formattedEntry); | |
// Update indicator | |
indicator.textContent = `Transcript capture active: ${capturedEntries.size} entries`; | |
indicator.style.background = 'green'; | |
} | |
} | |
} | |
// Initial processing of visible entries | |
document.querySelectorAll('.ms-List-cell').forEach(cell => { | |
processEntry(cell); | |
}); | |
// Set up mutation observer to catch new entries as they're loaded | |
const observer = new MutationObserver(mutations => { | |
mutations.forEach(mutation => { | |
// Look for added nodes that might be transcript entries | |
mutation.addedNodes.forEach(node => { | |
if (node.nodeType === Node.ELEMENT_NODE) { | |
// If this is a list cell, process it | |
if (node.classList && node.classList.contains('ms-List-cell')) { | |
processEntry(node); | |
} else { | |
// Check for list cells inside this node | |
const cells = node.querySelectorAll('.ms-List-cell'); | |
cells.forEach(cell => processEntry(cell)); | |
} | |
} | |
}); | |
}); | |
// Briefly flash the indicator to show activity | |
indicator.style.background = 'blue'; | |
setTimeout(() => { | |
indicator.style.background = 'green'; | |
}, 200); | |
}); | |
// Start observing | |
observer.observe(scrollContainer, { | |
childList: true, | |
subtree: true | |
}); | |
// Add a button to get the collected transcript | |
const extractButton = document.createElement('button'); | |
extractButton.textContent = 'Extract Complete Transcript'; | |
extractButton.style.cssText = 'position:fixed;top:50px;right:10px;padding:10px;z-index:9999;background:#0078d4;color:white;border:none;border-radius:5px;cursor:pointer;'; | |
extractButton.onclick = () => { | |
// Sort entries by timestamp if possible | |
const transcript = Array.from(capturedEntries).join('\n\n'); | |
// Copy to clipboard | |
const textarea = document.createElement('textarea'); | |
textarea.value = transcript; | |
document.body.appendChild(textarea); | |
textarea.select(); | |
document.execCommand('copy'); | |
document.body.removeChild(textarea); | |
console.log('Full transcript extracted:'); | |
console.log(transcript); | |
console.log('Transcript copied to clipboard!'); | |
indicator.textContent = `Extracted ${capturedEntries.size} entries!`; | |
indicator.style.background = '#4CAF50'; | |
setTimeout(() => { | |
indicator.style.background = 'green'; | |
indicator.textContent = `Transcript capture active: ${capturedEntries.size} entries`; | |
}, 3000); | |
}; | |
document.body.appendChild(extractButton); | |
// Return the cleanup function | |
return function cleanup() { | |
observer.disconnect(); | |
document.body.removeChild(indicator); | |
document.body.removeChild(extractButton); | |
return Array.from(capturedEntries).join('\n\n'); | |
}; | |
} | |
// Function to ensure the transcript panel is open | |
function ensureTranscriptPanelOpen() { | |
return new Promise((resolve) => { | |
// Check if transcript panel is already visible | |
const transcriptPanel = document.querySelector('#OneTranscript'); | |
if (transcriptPanel && window.getComputedStyle(transcriptPanel).display !== 'none') { | |
console.log('Transcript panel is already open'); | |
return resolve(true); | |
} | |
// Try to find and click the Transcript button | |
// Look for button with "Transcript" text | |
const buttons = Array.from(document.querySelectorAll('span[class*="ms-Button-label"]')); | |
const transcriptButton = buttons.find(el => el.textContent.trim() === 'Transcript'); | |
if (transcriptButton) { | |
console.log('Found Transcript button, clicking it...'); | |
// Click the button (need to find the closest clickable parent) | |
const clickableParent = transcriptButton.closest('[data-is-focusable="true"]') || | |
transcriptButton.closest('button') || | |
transcriptButton.closest('[role="button"]'); | |
if (clickableParent) { | |
clickableParent.click(); | |
console.log('Clicked Transcript button'); | |
// Wait for the transcript container to appear | |
const checkForContainer = () => { | |
const container = document.querySelector('#OneTranscript [data-is-scrollable="true"]'); | |
if (container) { | |
console.log('Transcript container found'); | |
resolve(true); | |
} else { | |
console.log('Waiting for transcript container to load...'); | |
setTimeout(checkForContainer, 500); // Check again after 500ms | |
} | |
}; | |
// Start checking | |
setTimeout(checkForContainer, 1000); // Give it 1 second before first check | |
return; | |
} | |
} | |
console.warn('Could not find or click Transcript button. Please open the transcript panel manually.'); | |
resolve(false); | |
}); | |
} | |
// Start everything in the correct sequence | |
(async function() { | |
// First, make sure the transcript panel is open | |
await ensureTranscriptPanelOpen(); | |
// Then, start the transcript capture | |
const stopCapture = setupTranscriptCapture(); | |
if (stopCapture) { | |
console.log('Scroll through the ENTIRE transcript to capture all entries.'); | |
console.log('When finished, click the "Extract Complete Transcript" button or run stopCapture() to get the result.'); | |
} else { | |
console.log('Failed to set up transcript capture. Please check if the transcript panel is open and try again.'); | |
} | |
// Extract currently visible transcript | |
const transcript = extractTranscript(); | |
if (transcript) { | |
console.log(transcript); // Display in console | |
// Create a temporary textarea to copy to clipboard | |
const textarea = document.createElement('textarea'); | |
textarea.value = transcript; | |
document.body.appendChild(textarea); | |
textarea.select(); | |
document.execCommand('copy'); | |
document.body.removeChild(textarea); | |
console.log('Transcript has been copied to clipboard!'); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Run the script in browser console. it will open the trancscript pane. Scroll manually [fell free to update and share]
It will remove dupplicates and save the whole transcript. Then click copy