diff --git a/bots/google_meet_bot_adapter/google_meet_ui_methods.py b/bots/google_meet_bot_adapter/google_meet_ui_methods.py index a76cf0811..0452f8093 100644 --- a/bots/google_meet_bot_adapter/google_meet_ui_methods.py +++ b/bots/google_meet_bot_adapter/google_meet_ui_methods.py @@ -491,6 +491,7 @@ def fill_out_name_input(self): except TimeoutException as e: self.look_for_blocked_element("name_input") self.look_for_login_required_element("name_input") + self.check_if_meeting_is_found() if self.google_meet_bot_login_session and self.join_now_button_is_present(): logger.info("This is a signed in bot and name input is not present but the join now button is present. Assuming name input is not present because we don't need to fill it out, so returning.") diff --git a/bots/teams_bot_adapter/teams_chromedriver_payload.js b/bots/teams_bot_adapter/teams_chromedriver_payload.js index 693b516bf..a4bb0cb3a 100644 --- a/bots/teams_bot_adapter/teams_chromedriver_payload.js +++ b/bots/teams_bot_adapter/teams_chromedriver_payload.js @@ -1,3 +1,95 @@ +(function () { + const CONV_HOST_REGEX = /(^|\.)teams\.microsoft\.(com|us)$/; + const CONV_PATH_REGEX = /\/conv\/[^/]+\/?$/; + + let installed = false; + let done = false; + + function isTargetUrl(input) { + try { + const raw = typeof input === 'string' ? input : (input instanceof Request ? input.url : String(input)); + const url = new URL(raw, window.location.href); + return CONV_HOST_REGEX.test(url.hostname) && CONV_PATH_REGEX.test(url.pathname); + } catch (_) { + return false; + } + } + + // Telemetry must never break the intercepted fetch. + function safeSendJson(message) { + try { window.ws?.sendJson(message); } catch (_) {} + } + + function getMethod(input, init) { + if (init?.method) return String(init.method).toUpperCase(); + if (input instanceof Request) return input.method.toUpperCase(); + return 'GET'; + } + + // Returns the rewritten body string, or null if the body isn't a conversation join request. + function rewriteBody(bodyText) { + let parsed; + try { parsed = JSON.parse(bodyText); } catch (_) { return null; } + if (!parsed?.conversationRequest) return null; + const v = parsed.participantPropertyBag?.participantScreening?.value; + if (!v) return null; + + const shouldOverrideIsBot = v.isBot === true && v.source === 'JoinShield'; + if (shouldOverrideIsBot) { + v.isBot = false; + } + safeSendJson({ + type: 'JoinShieldBypassActivated', + value: v, + wasIsBotOverridden: shouldOverrideIsBot, + }); + return shouldOverrideIsBot ? JSON.stringify(parsed) : bodyText; + } + + window.installJoinShieldBypass = function () { + if (done || installed) return true; + installed = true; + + const previousFetch = window.fetch; + + function uninstall() { + done = true; + // If something wrapped fetch after us, we can't unwind it; the wrapper becomes a passthrough. + if (window.fetch === joinShieldBypassFetch) { + window.fetch = previousFetch; + } + } + + function joinShieldBypassFetch(input, init) { + if (done || getMethod(input, init) !== 'POST' || !isTargetUrl(input)) { + return previousFetch.apply(this, arguments); + } + + // Only synchronous string bodies are rewritten; anything else (e.g. a Request object) + // passes through untouched so the original fetch timing and semantics are preserved. + if (typeof init?.body !== 'string') { + return previousFetch.apply(this, arguments); + } + + let newBody = null; + try { newBody = rewriteBody(init.body); } catch (err) { reportError(err); } + if (newBody === null) return previousFetch.apply(this, arguments); + uninstall(); + return previousFetch.call(this, input, { ...init, body: newBody }); + } + + function reportError(err) { + safeSendJson({ + type: 'JoinShieldBypassError', + error: err?.message || String(err), + }); + } + + window.fetch = joinShieldBypassFetch; + return true; + }; + })(); + (() => { let PAYLOAD = null; diff --git a/bots/teams_bot_adapter/teams_ui_methods.py b/bots/teams_bot_adapter/teams_ui_methods.py index 8abe6f92f..fdf363d08 100644 --- a/bots/teams_bot_adapter/teams_ui_methods.py +++ b/bots/teams_bot_adapter/teams_ui_methods.py @@ -129,6 +129,16 @@ def set_display_name_to_allow(self, display_name): # Tell the injected payload to allow this exact display name through Teams' validation regex. self.driver.execute_script("return window.setDisplayNameToAllowForTeamsNameValidationBypass?.(arguments[0]);", display_name) + def install_join_shield_bypass(self): + if self.teams_bot_login_is_available and self.teams_bot_login_should_be_used: + logger.info("Not installing JoinShield bypass because we are signed in") + return + + if self.driver.execute_script("return window.installJoinShieldBypass?.();"): + logger.info("Installed JoinShield bypass") + else: + logger.warning("Could not install JoinShield bypass") + def fill_out_name_input(self): num_attempts = 60 logger.info("Waiting for the name input field...") @@ -526,6 +536,7 @@ def attempt_to_join_meeting(self): logger.info("Waiting for the Join now button...") join_button = self.locate_element(step="join_button", condition=EC.presence_of_element_located((By.CSS_SELECTOR, '[data-tid="prejoin-join-button"]')), wait_time_seconds=10) logger.info("Clicking the Join now button...") + self.install_join_shield_bypass() self.click_element(join_button, "join_button") # Start polling participants diff --git a/version.json b/version.json index c153d5f30..21dff1d57 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "1.82.5" + "version": "1.82.6" }