Skip to content

Commit

Permalink
Merge pull request #15 from vivek-nexus/v2.1.3
Browse files Browse the repository at this point in the history
v2.1.3
  • Loading branch information
vivek-nexus committed Jun 20, 2024
2 parents 5d02279 + 6476fd2 commit e61165d
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 104 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<br />
<br />
Expand Down
38 changes: 25 additions & 13 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
155 changes: 66 additions & 89 deletions extension/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down

0 comments on commit e61165d

Please sign in to comment.