Skip to content

Instantly share code, notes, and snippets.

@a-c-m
Created July 9, 2026 08:14
Show Gist options
  • Select an option

  • Save a-c-m/3cb6c2cb1a9e977aec9723a35c7278ee to your computer and use it in GitHub Desktop.

Select an option

Save a-c-m/3cb6c2cb1a9e977aec9723a35c7278ee to your computer and use it in GitHub Desktop.
Auto join google meetings
// ==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