diff --git a/README.md b/README.md index 06af29d..61fae4e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Simple Google Meet transcripts. Private and open source. ![marquee-large](/assets/marquee-large.png) -Extension status: 🟢 OPERATIONAL (v2.1.2) +Extension status: 🟢 OPERATIONAL (v2.1.3)

diff --git a/extension/background.js b/extension/background.js index 50a00b9..7faaebc 100644 --- a/extension/background.js +++ b/extension/background.js @@ -1,26 +1,38 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { console.log(message.type) - if (message.type == "save_and_download") { - chrome.storage.local.set( - { - transcript: message.transcript, - chatMessages: message.chatMessages, - meetingTitle: message.meetingTitle, - meetingStartTimeStamp: message.meetingStartTimeStamp - }, - function () { - console.log("Saved transcript and meta data, downloading now if non empty") - // Download only if any transcript is present, irrespective of chat messages - if (message.transcript.length > 0) - downloadTranscript() + if (message.type == "new_meeting_started") { + // Saving current tab id, to download transcript when this tab is closed + chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) { + const tabId = tabs[0].id + chrome.storage.local.set({ meetingTabId: tabId }, function () { + console.log("Meeting tab id saved") }) + }) } if (message.type == "download") { + // Invalidate tab id since transcript is downloaded, prevents double downloading of transcript from tab closed event listener + chrome.storage.local.set({ meetingTabId: null }, function () { + console.log("Meeting tab id cleared") + }) downloadTranscript() } return true }) +// Download transcript if meeting tab is closed +chrome.tabs.onRemoved.addListener(function (tabid) { + chrome.storage.local.get(["meetingTabId"], function (data) { + if (tabid == data.meetingTabId) { + console.log("Successfully intercepted tab close") + downloadTranscript() + // Clearing meetingTabId to prevent misfires of onRemoved until next meeting actually starts + chrome.storage.local.set({ meetingTabId: null }, function () { + console.log("Meeting tab id cleared for next meeting") + }) + } + }) +}) + function downloadTranscript() { chrome.storage.local.get(["userName", "transcript", "chatMessages", "meetingTitle", "meetingStartTimeStamp"], function (result) { if (result.userName && result.transcript && result.chatMessages) { diff --git a/extension/content.js b/extension/content.js index 9b6ce48..a388e72 100644 --- a/extension/content.js +++ b/extension/content.js @@ -67,6 +67,9 @@ checkExtensionStatus().then(() => { // CRITICAL DOM DEPENDENCY. Wait until the meeting end icon appears, used to detect meeting start checkElement(".google-material-icons", "call_end").then(() => { console.log("Meeting started") + chrome.runtime.sendMessage({ type: "new_meeting_started" }, function (response) { + console.log(response); + }); hasMeetingStarted = true try { @@ -134,19 +137,14 @@ checkExtensionStatus().then(() => { //*********** MEETING END ROUTINES **********// - // Event listener to capture browser tab or window close - window.addEventListener("beforeunload", unloadCallback) - // CRITICAL DOM DEPENDENCY. Event listener to capture meeting end button click by user contains(".google-material-icons", "call_end")[0].parentElement.addEventListener("click", () => { // To suppress further errors hasMeetingEnded = true - // Remove unload event listener registered earlier, to prevent double downloads. Otherwise, unload event will trigger the callback, when user navigates away from meeting end page. - window.removeEventListener("beforeunload", unloadCallback) transcriptObserver.disconnect() chatMessagesObserver.disconnect() - // Push any data in the buffer variables to the transcript array, but avoid pushing blank ones. Handles one or more speaking when meeting ends. + // Push any data in the buffer variables to the transcript array, but avoid pushing blank ones. Needed to handle one or more speaking when meeting ends. if ((personNameBuffer != "") && (transcriptTextBuffer != "")) pushBufferToTranscript() // Save to chrome storage and send message to download transcript from background script @@ -272,100 +270,79 @@ const commonCSS = `background: rgb(255 255 255 / 10%); font-family: 'Google Sans',Roboto,Arial,sans-serif; box-shadow: rgba(0, 0, 0, 0.16) 0px 10px 36px 0px, rgba(0, 0, 0, 0.06) 0px 0px 0px 1px;`; -// Pushes any data in the buffer to transcript and tells background script to save it and download it -function unloadCallback() { - // To suppress further errors - hasMeetingEnded = true - // Push any data in the buffer variables to the transcript array, but avoid pushing blank ones. Handles one or more speaking when meeting ends. - if ((personNameBuffer != "") && (transcriptTextBuffer != "")) - pushBufferToTranscript() - // Send a message to save to chrome storage as well as download. Saving is offloaded to background script, since browser often aborts this long operation on unload - chrome.runtime.sendMessage( - { - type: "save_and_download", - transcript: transcript, - chatMessages: chatMessages, - meetingTitle: meetingTitle, - meetingStartTimeStamp: meetingStartTimeStamp, - }, - function (response) { - console.log(response) - }) -} - // Callback function to execute when transcription mutations are observed. function transcriber(mutationsList, observer) { // Delay for 1000ms to allow for text corrections by Meet. - setTimeout(() => { - mutationsList.forEach(mutation => { - try { - // CRITICAL DOM DEPENDENCY. Get all people in the transcript - const people = document.querySelector('.a4cQT').firstChild.firstChild.childNodes - // Begin parsing transcript - if (document.querySelector('.a4cQT')?.firstChild?.firstChild?.childNodes.length > 0) { - // Get the last person - const person = people[people.length - 1] - // CRITICAL DOM DEPENDENCY - const currentPersonName = person.childNodes[0].textContent - // CRITICAL DOM DEPENDENCY - const currentTranscriptText = person.childNodes[1].lastChild.textContent - - // Starting fresh in a meeting or resume from no active transcript - if (beforeTranscriptText == "") { - personNameBuffer = currentPersonName - timeStampBuffer = new Date().toLocaleString("default", timeFormat).toUpperCase() - beforeTranscriptText = currentTranscriptText - transcriptTextBuffer += currentTranscriptText - } - // Some prior transcript buffer exists - else { - // New person started speaking - if (personNameBuffer != currentPersonName) { - // Push previous person's transcript as a block - pushBufferToTranscript() - overWriteChromeStorage(["transcript"], false) - // Update buffers for next mutation and store transcript block timeStamp - beforeTranscriptText = currentTranscriptText - personNameBuffer = currentPersonName - timeStampBuffer = new Date().toLocaleString("default", timeFormat).toUpperCase() - transcriptTextBuffer = currentTranscriptText - } - // Same person speaking more - else { - // String subtraction to append only new characters to the buffer - transcriptTextBuffer += currentTranscriptText.substring(currentTranscriptText.indexOf(beforeTranscriptText) + beforeTranscriptText.length) - // Update buffers for next mutation - beforeTranscriptText = currentTranscriptText - } - } + mutationsList.forEach(mutation => { + try { + // CRITICAL DOM DEPENDENCY. Get all people in the transcript + const people = document.querySelector('.a4cQT').firstChild.firstChild.childNodes + // Begin parsing transcript + if (document.querySelector('.a4cQT')?.firstChild?.firstChild?.childNodes.length > 0) { + // Get the last person + const person = people[people.length - 1] + // CRITICAL DOM DEPENDENCY + const currentPersonName = person.childNodes[0].textContent + // CRITICAL DOM DEPENDENCY + const currentTranscriptText = person.childNodes[1].lastChild.textContent + + // Starting fresh in a meeting or resume from no active transcript + if (beforeTranscriptText == "") { + personNameBuffer = currentPersonName + timeStampBuffer = new Date().toLocaleString("default", timeFormat).toUpperCase() + beforeTranscriptText = currentTranscriptText + transcriptTextBuffer = currentTranscriptText } - // No people found in transcript DOM + // Some prior transcript buffer exists else { - // No transcript yet or the last person stopped speaking(and no one has started speaking next) - console.log("No active transcript") - // Push data in the buffer variables to the transcript array, but avoid pushing blank ones. - if ((personNameBuffer != "") && (transcriptTextBuffer != "")) { + // New person started speaking + if (personNameBuffer != currentPersonName) { + // Push previous person's transcript as a block pushBufferToTranscript() overWriteChromeStorage(["transcript"], false) + // Update buffers for next mutation and store transcript block timeStamp + beforeTranscriptText = currentTranscriptText + personNameBuffer = currentPersonName + timeStampBuffer = new Date().toLocaleString("default", timeFormat).toUpperCase() + transcriptTextBuffer = currentTranscriptText + } + // Same person speaking more + else { + transcriptTextBuffer = currentTranscriptText + // Update buffers for next mutation + beforeTranscriptText = currentTranscriptText + // If a person is speaking for a long time, Google Meet does not keep the entire text in the spans. Starting parts are automatically removed in an unpredictable way as the length increases and TranscripTonic will miss them. So we force remove a lengthy transcript node in a controlled way. Google Meet will add a fresh person node when we remove it and continue transcription. TranscripTonic picks it up as a new person and nothing is missed. + if (currentTranscriptText.length > 250) + person.remove() } - // Update buffers for the next person in the next mutation - beforePersonName = "" - beforeTranscriptText = "" - personNameBuffer = "" - transcriptTextBuffer = "" } - console.log(transcriptTextBuffer) - // console.log(transcript) - } catch (error) { - console.error(error) - if (isTranscriptDomErrorCaptured == false && hasMeetingEnded == false) { - console.log(reportErrorMessage) - showNotification(extensionStatusJSON_bug) + } + // No people found in transcript DOM + else { + // No transcript yet or the last person stopped speaking(and no one has started speaking next) + console.log("No active transcript") + // Push data in the buffer variables to the transcript array, but avoid pushing blank ones. + if ((personNameBuffer != "") && (transcriptTextBuffer != "")) { + pushBufferToTranscript() + overWriteChromeStorage(["transcript"], false) } - isTranscriptDomErrorCaptured = true + // Update buffers for the next person in the next mutation + beforePersonName = "" + beforeTranscriptText = "" + personNameBuffer = "" + transcriptTextBuffer = "" } - }) - }, 1000); + console.log(transcriptTextBuffer) + // console.log(transcript) + } catch (error) { + console.error(error) + if (isTranscriptDomErrorCaptured == false && hasMeetingEnded == false) { + console.log(reportErrorMessage) + showNotification(extensionStatusJSON_bug) + } + isTranscriptDomErrorCaptured = true + } + }) } // Callback function to execute when chat messages mutations are observed. diff --git a/extension/manifest.json b/extension/manifest.json index 3d27f67..22869e2 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,6 +1,6 @@ { "name": "TranscripTonic", - "version": "2.1.2", + "version": "2.1.3", "manifest_version": 3, "description": "Simple Google Meet transcripts. Private and open source.", "action": {