Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save tristantarrant/e3097b8af4d6e684b63ac1fee64117a0 to your computer and use it in GitHub Desktop.

Select an option

Save tristantarrant/e3097b8af4d6e684b63ac1fee64117a0 to your computer and use it in GitHub Desktop.
TamperMonkey Google Drive Video transcript exporter
// ==UserScript==
// @name Goole Meet Transcript
// @namespace http://tampermonkey.net/
// @version 2025-05-26
// @description try to take over the world!
// @author You
// @match https://drive.google.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// ==/UserScript==
(function() {
'use strict';
const TARGET_DIV_SELECTOR = 'div[aria-label="Transcript sidebar"]'; // By aria-label
function collectTextNodesWithIterator(element) {
let textContent = [];
const treeWalker = document.createNodeIterator(
element,
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
{
acceptNode: function(node) {
if (node.nodeType === Node.ELEMENT_NODE && (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {
return NodeFilter.FILTER_REJECT;
}
if (node.nodeType === Node.TEXT_NODE) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
}
}
);
let currentNode;
while ((currentNode = treeWalker.nextNode())) {
const trimmedText = currentNode.nodeValue.trim();
if (trimmedText.length > 0) {
textContent.push(currentNode.nodeValue);
}
}
return textContent.join('\n');
}
async function copyTextToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('Text copied to clipboard successfully!');
return true; // Indicate success
} catch (err) {
console.error('Failed to copy text to clipboard:', err);
return false; // Indicate failure
}
}
// Function to run when the div is found
function handleDivFound(targetDiv) {
console.log('🎉 The specific div has been added to the DOM!', targetDiv);
const floatingDiv = document.createElement('div');
Object.assign(floatingDiv.style, {
position: 'fixed',
zIndex: '9999',
top: '60px',
right: '20px',
backgroundColor: '#333',
color: 'white',
padding: '15px 25px',
borderRadius: '8px',
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
fontFamily: 'Arial, sans-serif',
fontSize: '16px',
textAlign: 'center'
});
const extractButton = document.createElement('button');
extractButton.innerText = "Extract transcript";
extractButton.onclick = function() {
const text = collectTextNodesWithIterator(targetDiv);
copyTextToClipboard(text);
};
floatingDiv.appendChild(extractButton);
document.body.appendChild(floatingDiv);
// If you only need to detect it once, disconnect the observer:
observer.disconnect();
console.log('MutationObserver disconnected after finding the div.');
}
// --- MutationObserver Logic ---
// Callback function to execute when mutations are observed
const observerCallback = function(mutationsList, observer) {
for (const mutation of mutationsList) {
// We are interested in 'childList' mutations, which means nodes were added or removed.
if (mutation.type === 'childList') {
// Check if any added nodes match our target selector
for (const addedNode of mutation.addedNodes) {
// Node.ELEMENT_NODE ensures we're only looking at elements (not text nodes, etc.)
if (addedNode.nodeType === Node.ELEMENT_NODE) {
// Check if the added node itself is the target div
if (addedNode.matches(TARGET_DIV_SELECTOR)) {
handleDivFound(addedNode);
return; // Stop processing and potentially disconnect
}
// Check if the added node contains the target div (deep search)
const foundInside = addedNode.querySelector(TARGET_DIV_SELECTOR);
if (foundInside) {
handleDivFound(foundInside);
return; // Stop processing and potentially disconnect
}
}
}
}
}
};
// Create an instance of the MutationObserver with the defined callback
const observer = new MutationObserver(observerCallback);
// Configuration of the observer:
const observerConfig = {
childList: true, // Observe direct children additions/removals
subtree: true // Observe changes in the entire subtree (descendants)
};
// Start observing the target node (usually document.body or document.documentElement)
// for the configured mutations.
// document.documentElement is often preferred as it covers the entire HTML structure.
observer.observe(document.documentElement, observerConfig);
console.log('MutationObserver started, waiting for div:', TARGET_DIV_SELECTOR);
// --- Optional: Check if the div already exists on initial load ---
// It's good practice to check if the element is already present when the script runs,
// in case it's not dynamically added but part of the initial HTML.
const initialDiv = document.querySelector(TARGET_DIV_SELECTOR);
if (initialDiv) {
console.log('The div was already present in the DOM on script load.');
handleDivFound(initialDiv);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment