Skip to content

Commit f5b515a

Browse files
authored
Merge pull request #83 from vivek-nexus/v3.2.13
V3.2.13
2 parents 9fdcc6d + 856d947 commit f5b515a

8 files changed

Lines changed: 184 additions & 104 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# TranscripTonic
22
Simple Google Meet transcripts. Private and open source.
3-
> Zoom and Teams transcripts in beta. <a href="https://github.com/vivek-nexus/transcriptonic/wiki/Zoom-and-Teams-beta-testing" target="_blank">Learn
3+
> Teams and Zoom transcripts in beta. <a href="https://github.com/vivek-nexus/transcriptonic/wiki/Zoom-and-Teams-beta-testing" target="_blank">Learn
44
more</a>.
55
66
![marquee-large](/assets/marquee-large.png)

extension-unpacked.zip

435 Bytes
Binary file not shown.

extension/background.js

Lines changed: 107 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,23 @@ chrome.runtime.onMessage.addListener(function (messageUnTyped, sender, sendRespo
135135
})
136136
}
137137

138+
if (message.type === "enable_beta") {
139+
enableBeta().then((message) => {
140+
/** @type {ExtensionResponse} */
141+
const response = { success: true, message: message }
142+
sendResponse(response)
143+
})
144+
.catch((error) => {
145+
// Fails with error codes: not defined
146+
const parsedError = /** @type {ErrorObject} */ (error)
147+
148+
/** @type {ExtensionResponse} */
149+
const response = { success: false, message: parsedError }
150+
sendResponse(response)
151+
})
152+
}
153+
154+
138155
return true
139156
})
140157

@@ -179,19 +196,13 @@ chrome.runtime.onUpdateAvailable.addListener(() => {
179196

180197
// Register content scripts whenever runtime permission is provided by the user
181198
chrome.permissions.onAdded.addListener((event) => {
182-
if (event.origins?.includes("https://*.zoom.us/*") && event.origins?.includes("https://teams.live.com/*") && event.origins?.includes("https://teams.microsoft.com/*")) {
183-
registerContentScripts()
184-
}
199+
registerContentScripts()
185200
})
186201

187202

188203
chrome.runtime.onInstalled.addListener(() => {
189-
// Re-register content scripts whenever extension is installed or updated, provided permissions are available
190-
chrome.permissions.getAll().then((permissions) => {
191-
if (permissions.origins?.includes("https://*.zoom.us/*") && permissions.origins?.includes("https://teams.live.com/*") && permissions.origins?.includes("https://teams.microsoft.com/*")) {
192-
registerContentScripts(false)
193-
}
194-
})
204+
// Re-register content scripts whenever extension is installed or updated, provided permissions are available. Suppress notification for silent background operation.
205+
registerContentScripts(false)
195206

196207
// Set defaults values
197208
chrome.storage.sync.get(["autoPostWebhookAfterMeeting", "operationMode", "webhookBodyType", "webhookUrl"], function (resultSyncUntyped) {
@@ -606,79 +617,106 @@ function recoverLastMeeting() {
606617
})
607618
}
608619

620+
function enableBeta() {
621+
return new Promise((resolve, reject) => {
622+
chrome.permissions.request({
623+
origins: ["https://*.zoom.us/*", "https://teams.live.com/*", "https://teams.microsoft.com/*"],
624+
permissions: ["notifications"]
625+
}).then((granted) => {
626+
if (granted) {
627+
resolve("Permissions granted")
628+
}
629+
else {
630+
reject("Permissions denied")
631+
}
632+
}).catch((error) => {
633+
console.error(error)
634+
reject("Could not enable Teams and Zoom transcripts")
635+
})
636+
})
637+
}
638+
609639
/**
610640
* @param {boolean} [showNotification]
611641
*/
612642
function registerContentScripts(showNotification = true) {
613643
return new Promise((resolve, reject) => {
614-
chrome.scripting
615-
.getRegisteredContentScripts()
616-
.then((scripts) => {
617-
let isContentZoomRegistered = false
618-
let isContentTeamsRegistered = false
619-
scripts.forEach((script) => {
620-
if (script.id === "content-zoom") {
621-
isContentZoomRegistered = true
622-
console.log("Zoom content script already registered")
623-
}
624-
if (script.id === "content-teams") {
625-
isContentTeamsRegistered = true
626-
console.log("Teams content script already registered")
627-
}
628-
})
644+
chrome.permissions.getAll().then((permissions) => {
645+
if (permissions.origins?.includes("https://*.zoom.us/*") && permissions.origins?.includes("https://teams.live.com/*") && permissions.origins?.includes("https://teams.microsoft.com/*")) {
646+
chrome.scripting
647+
.getRegisteredContentScripts()
648+
.then((scripts) => {
649+
let isContentZoomRegistered = false
650+
let isContentTeamsRegistered = false
651+
scripts.forEach((script) => {
652+
if (script.id === "content-zoom") {
653+
isContentZoomRegistered = true
654+
console.log("Zoom content script already registered")
655+
}
656+
if (script.id === "content-teams") {
657+
isContentTeamsRegistered = true
658+
console.log("Teams content script already registered")
659+
}
660+
})
629661

630-
if (isContentTeamsRegistered && isContentTeamsRegistered) {
631-
resolve("Zoom and Teams content scripts already registered")
632-
return
633-
}
662+
if (isContentTeamsRegistered && isContentTeamsRegistered) {
663+
resolve("Teams and Zoom content scripts already registered")
664+
return
665+
}
634666

635-
const promises = []
667+
const promises = []
636668

637-
if (!isContentZoomRegistered) {
638-
const zoomRegistrationPromise = chrome.scripting.registerContentScripts([{
639-
id: "content-zoom",
640-
js: ["content-zoom.js"],
641-
matches: ["https://*.zoom.us/*"],
642-
runAt: "document_end",
643-
}])
644-
promises.push(zoomRegistrationPromise)
645-
}
669+
if (!isContentZoomRegistered) {
670+
const zoomRegistrationPromise = chrome.scripting.registerContentScripts([{
671+
id: "content-zoom",
672+
js: ["content-zoom.js"],
673+
matches: ["https://*.zoom.us/*"],
674+
runAt: "document_end",
675+
}])
676+
promises.push(zoomRegistrationPromise)
677+
}
646678

647-
if (!isContentTeamsRegistered) {
648-
const teamsRegistrationPromise = chrome.scripting.registerContentScripts([{
649-
id: "content-teams",
650-
js: ["content-teams.js"],
651-
matches: ["https://teams.live.com/*", "https://teams.microsoft.com/*"],
652-
runAt: "document_end",
653-
}])
654-
promises.push(teamsRegistrationPromise)
655-
}
679+
if (!isContentTeamsRegistered) {
680+
const teamsRegistrationPromise = chrome.scripting.registerContentScripts([{
681+
id: "content-teams",
682+
js: ["content-teams.js"],
683+
matches: ["https://teams.live.com/*", "https://teams.microsoft.com/*"],
684+
runAt: "document_end",
685+
}])
686+
promises.push(teamsRegistrationPromise)
687+
}
656688

657-
Promise.all(promises)
658-
.then(() => {
659-
console.log("Both Zoom and Teams content scripts registered successfully.")
660-
resolve("Zoom and Teams content scripts registered")
661-
662-
if (showNotification) {
663-
chrome.permissions.contains({
664-
permissions: ["notifications"]
665-
}).then((hasPermission) => {
666-
if (hasPermission) {
667-
chrome.notifications.create({
668-
type: "basic",
669-
iconUrl: "icon.png",
670-
title: "Enabled! Join Zoom/Teams meetings on the browser",
671-
message: "Refresh any existing Zoom/Teams pages"
689+
Promise.all(promises)
690+
.then(() => {
691+
console.log("Both Teams and Zoom content scripts registered successfully.")
692+
resolve("Teams and Zoom content scripts registered")
693+
694+
if (showNotification) {
695+
chrome.permissions.contains({
696+
permissions: ["notifications"]
697+
}).then((hasPermission) => {
698+
if (hasPermission) {
699+
chrome.notifications.create({
700+
type: "basic",
701+
iconUrl: "icon.png",
702+
title: "Enabled! Join Zoom/Teams meetings on the browser",
703+
message: "Refresh any existing Zoom/Teams pages"
704+
})
705+
}
672706
})
673707
}
674708
})
675-
}
676-
})
677-
.catch((error) => {
678-
// This block runs if EITHER Zoom OR Teams registration fails.
679-
console.error("One or more content script registrations failed.", error)
680-
reject("Failed to register one or more content scripts")
709+
.catch((error) => {
710+
// This block runs if EITHER Zoom OR Teams registration fails.
711+
console.error("One or more content script registrations failed.", error)
712+
reject("Failed to register one or more content scripts")
713+
})
681714
})
682-
})
715+
}
716+
else {
717+
reject("Insufficient permissions")
718+
return
719+
}
720+
})
683721
})
684722
}

extension/content-zoom.js

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/// <reference path="../types/chrome.d.ts" />
33
/// <reference path="../types/index.js" />
44

5-
let isZoomRunning = false
5+
let isZoomInjected = false
66

77
setInterval(() => {
88
// Meeting page
@@ -11,13 +11,13 @@ setInterval(() => {
1111

1212
// On the meeting page and main zoom function is not running, inject it
1313
// This won't cause multiple main zoom injections into the current meeting because when the previous meeting ends, all UI elements are gone, destroying the corresponding event listeners
14-
if (isZoomUrlMatching && !isZoomRunning) {
14+
if (isZoomUrlMatching && !isZoomInjected) {
1515
zoom()
16-
isZoomRunning = true
16+
isZoomInjected = true
1717
}
1818
// Set flag to false when meetings ends and the tab navigates to a non matching URL, or simply the current URL is a non meeting URL
1919
if (!isZoomUrlMatching) {
20-
isZoomRunning = false
20+
isZoomInjected = false
2121
}
2222
}, 2000)
2323

@@ -120,7 +120,7 @@ function zoom() {
120120

121121
// CRITICAL DOM DEPENDENCY. Wait until the meeting end icon appears, used to detect meeting start
122122
if (iframeDOM) {
123-
waitForElement(iframeDOM, "#audioOptionMenu").then(() => {
123+
waitForElement(iframe, "#audioOptionMenu").then(() => {
124124
console.log("Meeting started")
125125
/** @type {ExtensionMessage} */
126126
const message = {
@@ -140,7 +140,7 @@ function zoom() {
140140

141141
// **** REGISTER TRANSCRIPT LISTENER **** //
142142
// Wait for transcript node to be visible. When user is waiting in meeting lobbing for someone to let them in, the call end icon is visible, but the captions icon is still not visible.
143-
waitForElement(iframeDOM, ".live-transcription-subtitle__box").then((element) => {
143+
waitForElement(iframe, ".live-transcription-subtitle__box").then((element) => {
144144
console.log("Found captions container")
145145
// CRITICAL DOM DEPENDENCY. Grab the transcript element.
146146
const transcriptTargetNode = element
@@ -446,33 +446,38 @@ function zoom() {
446446
* @description Provides a visual cue to indicate the extension is actively working.
447447
*/
448448
function pulseStatus() {
449-
const statusActivityCSS = `position: fixed;
449+
try {
450+
const statusActivityCSS = `position: fixed;
450451
top: 0px;
451452
width: 100%;
452453
height: 4px;
453454
z-index: 100;
454455
transition: background-color 0.3s ease-in
455456
`
456-
const iframe = /** @type {HTMLIFrameElement} */ (document.querySelector("#webclient"))
457-
const iframeDOM = iframe.contentDocument
457+
const iframe = /** @type {HTMLIFrameElement} */ (document.querySelector("#webclient"))
458+
const iframeDOM = iframe.contentDocument
459+
460+
if (iframeDOM) {
461+
/** @type {HTMLDivElement | null}*/
462+
let activityStatus = iframeDOM.querySelector(`#transcriptonic-status`)
463+
if (!activityStatus) {
464+
let html = iframeDOM.querySelector("html")
465+
activityStatus = iframeDOM.createElement("div")
466+
activityStatus.setAttribute("id", "transcriptonic-status")
467+
activityStatus.style.cssText = `background-color: #2A9ACA; ${statusActivityCSS}`
468+
html?.appendChild(activityStatus)
469+
}
470+
else {
471+
activityStatus.style.cssText = `background-color: #2A9ACA; ${statusActivityCSS}`
472+
}
458473

459-
if (iframeDOM) {
460-
/** @type {HTMLDivElement | null}*/
461-
let activityStatus = iframeDOM.querySelector(`#transcriptonic-status`)
462-
if (!activityStatus) {
463-
let html = iframeDOM.querySelector("html")
464-
activityStatus = iframeDOM.createElement("div")
465-
activityStatus.setAttribute("id", "transcriptonic-status")
466-
activityStatus.style.cssText = `background-color: #2A9ACA; ${statusActivityCSS}`
467-
html?.appendChild(activityStatus)
468-
}
469-
else {
470-
activityStatus.style.cssText = `background-color: #2A9ACA; ${statusActivityCSS}`
474+
setTimeout(() => {
475+
activityStatus.style.cssText = `background-color: transparent; ${statusActivityCSS}`
476+
}, 3000)
471477
}
472-
473-
setTimeout(() => {
474-
activityStatus.style.cssText = `background-color: transparent; ${statusActivityCSS}`
475-
}, 3000)
478+
}
479+
catch (error) {
480+
console.error(error)
476481
}
477482
}
478483

@@ -490,20 +495,27 @@ function zoom() {
490495

491496
/**
492497
* @description Efficiently waits until the element of the specified selector and textContent appears in the DOM. Polls only on animation frame change
493-
* @param {Document} iframe
498+
* @param {HTMLIFrameElement | Document} iframe
494499
* @param {string} selector
495500
* @param {string | RegExp} [text]
496501
*/
497502
async function waitForElement(iframe, selector, text) {
503+
let document
504+
if (iframe instanceof Document) {
505+
document = iframe
506+
}
507+
else {
508+
document = /** @type {Document} */ (iframe.contentDocument)
509+
}
498510
if (text) {
499511
// loops for every animation frame change, until the required element is found
500-
while (!Array.from(iframe.querySelectorAll(selector)).find(element => element.textContent === text)) {
512+
while (!Array.from(document.querySelectorAll(selector)).find(element => element.textContent === text)) {
501513
await new Promise((resolve) => requestAnimationFrame(resolve))
502514
}
503515
}
504516
else {
505517
// loops for every animation frame change, until the required element is found
506-
while (!iframe.querySelector(selector)) {
518+
while (!document.querySelector(selector)) {
507519
await new Promise((resolve) => requestAnimationFrame(resolve))
508520
}
509521
}
@@ -539,7 +551,7 @@ function zoom() {
539551
text.innerHTML = extensionStatusJSON.message
540552

541553
// Remove banner once transcript is on
542-
waitForElement(iframeDOM, ".live-transcription-subtitle__box").then(() => {
554+
waitForElement(iframe, ".live-transcription-subtitle__box").then(() => {
543555
obj.style.display = "none"
544556
})
545557
}

0 commit comments

Comments
 (0)