Created
July 9, 2026 08:14
-
-
Save a-c-m/3cb6c2cb1a9e977aec9723a35c7278ee to your computer and use it in GitHub Desktop.
Auto join google meetings
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 Google Meet Auto-Join & Auto-Close | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.2 | |
| // @description Auto-clicks the join button on Google Meet, and closes the tab 5 seconds after leaving a meeting. | |
| // @author You | |
| // @match https://meet.google.com/* | |
| // @grant none | |
| // @run-at document-idle | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| // ── Configuration ────────────────────────────────────────────────────────── | |
| const JOIN_POLL_INTERVAL_MS = 1000; // How often to look for the join button | |
| const JOIN_TIMEOUT_MS = 60000; // Stop trying to join after this long | |
| const LEAVE_CLOSE_DELAY_MS = 5000; // Delay before closing tab after leaving | |
| const LEFT_POLL_INTERVAL_MS = 2000; // How often to check if we've left | |
| // ────────────────────────────────────────────────────────────────────────── | |
| let joinAttempted = false; | |
| let leftDetected = false; | |
| let joinIntervalId = null; | |
| let leftIntervalId = null; | |
| let closeTimeoutId = null; | |
| // Selectors for the "join" button. Google Meet uses slightly different | |
| // label text depending on the context (pre-join lobby vs. mid-meeting join). | |
| const JOIN_BUTTON_TEXTS = [ | |
| 'join now', | |
| 'join meeting', | |
| 'ask to join', | |
| 'join', | |
| ]; | |
| // Text that appears on the post-meeting screen. | |
| const LEAVE_INDICATOR_TEXTS = [ | |
| "you've left the meeting", | |
| "you left the meeting", | |
| 'return to home screen', | |
| 'rejoin', | |
| ]; | |
| // ── Helpers ──────────────────────────────────────────────────────────────── | |
| /** | |
| * Finds a visible, clickable join button by inspecting button text content. | |
| * Returns the element or null. | |
| */ | |
| function findJoinButton() { | |
| const buttons = document.querySelectorAll('button, [role="button"]'); | |
| for (const btn of buttons) { | |
| const text = (btn.innerText || btn.textContent || '').trim().toLowerCase(); | |
| if (JOIN_BUTTON_TEXTS.includes(text)) { | |
| // Make sure the button is actually visible | |
| const rect = btn.getBoundingClientRect(); | |
| if (rect.width > 0 && rect.height > 0) { | |
| return btn; | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Returns true when the page contains text indicating the user has left. | |
| */ | |
| function hasLeftMeeting() { | |
| const bodyText = document.body.innerText.toLowerCase(); | |
| return LEAVE_INDICATOR_TEXTS.some(indicator => bodyText.includes(indicator)); | |
| } | |
| /** | |
| * Log helper – prefixes all messages so they're easy to spot in DevTools. | |
| */ | |
| function log(msg) { | |
| console.log(`[Meet Auto-Join] ${msg}`); | |
| } | |
| // ── Join logic ───────────────────────────────────────────────────────────── | |
| function startJoinPolling() { | |
| const deadline = Date.now() + JOIN_TIMEOUT_MS; | |
| joinIntervalId = setInterval(() => { | |
| if (joinAttempted) { | |
| clearInterval(joinIntervalId); | |
| return; | |
| } | |
| if (Date.now() > deadline) { | |
| log('Timed out waiting for join button.'); | |
| clearInterval(joinIntervalId); | |
| return; | |
| } | |
| const btn = findJoinButton(); | |
| if (btn) { | |
| log(`Found join button: "${btn.innerText.trim()}" — clicking.`); | |
| btn.click(); | |
| joinAttempted = true; | |
| clearInterval(joinIntervalId); | |
| // Start watching for the post-meeting screen | |
| startLeftPolling(); | |
| } | |
| }, JOIN_POLL_INTERVAL_MS); | |
| } | |
| // ── Leave / close logic ──────────────────────────────────────────────────── | |
| function startLeftPolling() { | |
| leftIntervalId = setInterval(() => { | |
| if (leftDetected) { | |
| clearInterval(leftIntervalId); | |
| return; | |
| } | |
| if (hasLeftMeeting()) { | |
| leftDetected = true; | |
| clearInterval(leftIntervalId); | |
| log(`Left-meeting screen detected. Closing tab in ${LEAVE_CLOSE_DELAY_MS / 1000}s…`); | |
| closeTimeoutId = setTimeout(() => { | |
| log('Closing tab now.'); | |
| window.close(); | |
| // window.close() only works when the tab was opened by a script. | |
| // As a fallback for Brave, navigate to a blank page. | |
| setTimeout(() => { | |
| if (!window.closed) { | |
| log('window.close() had no effect — navigating to blank page.'); | |
| window.location.href = 'about:blank'; | |
| } | |
| }, 500); | |
| }, LEAVE_CLOSE_DELAY_MS); | |
| } | |
| }, LEFT_POLL_INTERVAL_MS); | |
| } | |
| // ── Entry point ──────────────────────────────────────────────────────────── | |
| // Only run on actual meeting URLs (e.g. /abc-defg-hij), not the landing page. | |
| const meetingCodePattern = /^\/[a-z]{3}-[a-z]{4}-[a-z]{3}(\/.*)?$/i; | |
| if (!meetingCodePattern.test(window.location.pathname)) { | |
| log('Not a meeting URL — script idle.'); | |
| return; | |
| } | |
| log('Meeting URL detected — waiting for join button…'); | |
| startJoinPolling(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment