Created
May 27, 2025 11:53
-
-
Save tristantarrant/e3097b8af4d6e684b63ac1fee64117a0 to your computer and use it in GitHub Desktop.
TamperMonkey Google Drive Video transcript exporter
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 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