master 47a02e5b4cb1 cached
6 files
11.5 KB
3.6k tokens
8 symbols
1 requests
Download .txt
Repository: AmanRaj1608/Google-Meet-Scheduler
Branch: master
Commit: 47a02e5b4cb1
Files: 6
Total size: 11.5 KB

Directory structure:
gitextract_1yhuxhtn/

├── .gitignore
├── README.md
├── google-meet.js
├── package.json
├── server.js
└── views/
    └── index.ejs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/node_modules
.env

================================================
FILE: README.md
================================================
<p align="center"><img src="https://cdn.shopify.com/s/files/1/2579/7072/articles/puppeteer-cover_1200x630.png?v=1521812467" align="center" width="250"></p>
<h2 align="center">Google Meet Scheduler</h2>
<p align="center"><b>Join's meet link for you 😴</b></p>

Bot for scheduling and entering google meet sessions automatically.

##### New Features

- Saving RAM: Using same browser for all meet pages
- Multiple meet logic added

### Installation Guide

1. Open terminal on your PC
2. Clone the repo `git clone https://github.com/AmanRaj1608/Google-Meet-Scheduler.git`
3. Go inside the project directory
4. Rename `.env-example` file to `.env` and replace your email and password there
5. Install dependencies `npm install`
6. Start the application `npm start`

Now the project has started on `localhost:3000`

### Usage Guide

Now when you visit the page you will see things to add

- Meet Link
- Start Time
- End time

Then submit and do what you wanted to, it will log in and join meet for you.

You can add more links there to add it to the queue.

### Requirements

- [Node.js](https://nodejs.org/en/download/) should be installed
- [Google Chrome](https://www.google.com/intl/en_in/chrome/) with version 70+
- Works only on windows (see [Issue #2](https://github.com/AmanRaj1608/Google-Meet-Scheduler/issues/8) for more info)

### If you want to see the whole process

On line `16` of `server.js` file you can see a variable name head=false;

If you want to see bot automatically opening the page and filling login values and joining meet link then you can set the headless as flase.

But while for deployment we need headless as true.

### Deployment

If you want to deploy your instance of app you need it to set it up properly.
The main problem on deployment is that after deployment it will be hosted on different IP and when bot tries to sign in Google will ask to login again with `one time password`.

More details here [Issue #1](https://github.com/AmanRaj1608/Google-Meet-Scheduler/issues/1)

I recommend using [digitalocean](https://m.do.co/c/92e13bfef66f)

### Todo

You can however deploy it by creating an API that will ask for OTP and while sign-in you give that info to the server.
This can be implemented as a new branch especially for deployment purpose

### How it works

Project is made using [Puppeteer](https://developers.google.com/web/tools/puppeteer) which is a Node library which provides a high-level API to control headless Chrome or Chromium. We open a chromium app on server where we can add create open tabs see browser versions and everything.

So here we are using `puppeteer-extra` and `puppeteer-extra-plugin-stealth` which helps in creating an instance of chrome where google don't able to detect that it is created by puppeteer. So using this plugin we can login into google without filling capcha.

---

<p align="center"> Made with ❤️ by <a href="https://amanraj.dev/">Aman Raj</a></p>
```


================================================
FILE: google-meet.js
================================================
// const puppeteer = require('puppeteer');
const puppeteer = require('puppeteer-extra')
const StealthPlugin = require('puppeteer-extra-plugin-stealth')

puppeteer.use(StealthPlugin())

class GoogleMeet {
	constructor(email, pass, head, strict) {
		this.email = email;
		this.pass = pass;
		this.head = head;
		this.strict = strict;
		this.browser;
		this.page = {};
		this.browserIsActive = false;
	}

	async createBrowser() {
		this.browser = await puppeteer.launch({
			headless: this.head,
			args: [
				'--no-sandbox',
				'--disable-setuid-sandbox',
				'--use-fake-ui-for-media-stream',
				'--disable-audio-output'
			],
		});
	}

	async accountLogin(newPage) {
		await newPage.goto('https://accounts.google.com/signin/v2/identifier?flowName=GlifWebSignIn&flowEntry=ServiceLogin');
		// Login Start
		await newPage.type("input#identifierId", this.email, {
			delay: 0
		})
		await newPage.click("div#identifierNext");

		await newPage.waitForTimeout(7000);

		await newPage.type("input[name=password]", this.pass, {
			delay: 0
		})
		await newPage.click("div#passwordNext");
		await newPage.waitForTimeout(5000);
	}

	async schedule(url, meetId) {
		try {
			// Open new browser only if not req
			if (!this.browserIsActive) {
				await this.createBrowser();
			}
			// open new tab on browser
			const newPage = await this.browser.newPage();

			if (!this.browserIsActive) {
				await this.accountLogin(newPage);
			}

			// open meet in tab
			await newPage.goto(url);
			console.log("inside meet page");
			await newPage.waitForTimeout(7000);
			try {
				await newPage.click("div.IYwVEf.HotEze.uB7U9e.nAZzG");
			} catch (e) {
				console.log("\naudio seems to disabled already", e.message);
			}
			await newPage.waitForTimeout(1000);
			try {
				await newPage.click("div.IYwVEf.HotEze.nAZzG");
			} catch (e) {
				console.log("\nvideo seems to be disabled already", e.message);
			}

			// sanity check (connect only if both audio and video are muted) :P
			if (this.strict) {
				let audio = await newPage.evaluate('document.querySelectorAll("div.sUZ4id")[0].children[0].getAttribute("data-is-muted")')
				let video = await newPage.evaluate('document.querySelectorAll("div.sUZ4id")[1].children[0].getAttribute("data-is-muted")')

				if (audio === "false" || video === "false") {
					console.log("Not joining meeting. We couldn't disable either audio or video from the device.\nYou may try again.")
					return
				}
				console.log("all set!!")
			}

			await newPage.waitForTimeout(1000);
			console.log('clicking on join');
			await newPage.click("span.NPEfkd.RveJvd.snByac");

			this.page[meetId] = newPage;
			this.browserIsActive = true;
			console.log("Successfully joined/Sent join request");
			return true;
		}
		catch (err) {
			console.log(err);
			this.browserIsActive = false;
			return false;
		}
	}

	async closeTab(ind) {
		await this.page[ind].close();
	}

	async closeBrowser() {
		await this.browser.close();
		this.browserIsActive = false;
	}

	getBrowserIsActive() {
		return this.browserIsActive;
	}
}

module.exports = GoogleMeet;


================================================
FILE: package.json
================================================
{
  "name": "attendance",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "author": "Aman Raj",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.19.0",
    "dotenv": "^10.0.0",
    "ejs": "^3.1.6",
    "express": "^4.17.1",
    "nodemon": "^2.0.12",
    "puppeteer": "^10.1.0",
    "puppeteer-extra": "^3.1.18",
    "puppeteer-extra-plugin-stealth": "^2.7.8"
  }
}

================================================
FILE: server.js
================================================
require('dotenv').config()
const express = require('express');
const path = require('path');
const GoogleMeet = require('./google-meet');

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// Values
let email = process.env.EMAIL;
let password = process.env.PASSWORD;

let head = false;
let strict = false;

meetObj = new GoogleMeet(email, password, head, strict);

// cache store
// can be moved to db
let url = {};
let ind = 0;

app.get('/', (req, res) => {
	res.render('index', { url, email, password })
});
app.post('/postlink', (req, res) => {
	ind++;
	url[ind] = {};
	url[ind].id = ind;
	url[ind].url = req.body.url;
	url[ind].startTime = Date.parse(req.body.startDate);
	url[ind].endTime = Date.parse(req.body.endDate);
	res.redirect("/");
});

const listener = app.listen(3000 || process.env.PORT, () => {

	setInterval(() => {
		// when no scheduled links
		if (Object.keys(url).length === 0) {
			if (meetObj.getBrowserIsActive())
				meetObj.closeBrowser();
			else
				return;
		}
		// check meet array every 10sec
		for (x in url) {
			// join period
			if (url[x].startTime < Date.now() && url[x].endTime > Date.now()) {
				console.log(`Request for joining meet ${url[x].url}`);
				meetObj.schedule(url[x].url, url[x].id);
				// hack: set above endTime so that it will not come for 
				// 				same meetId in this block
				url[x].startTime = url[x].endTime + 2000; 
			}
			// leave period
			if (url[x].endTime < Date.now()) {
				console.log(`Request for leaving meet ${url[x].url}`);
				meetObj.closeTab(url[x].id);
				delete url[x];
			}
		}
	}, 10000)

	console.log(`App listening on port ${listener.address().port}`)
})

================================================
FILE: views/index.ejs
================================================
<!doctype html>
<html lang="en">

<head>
	<!-- Required meta tags -->
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

	<!-- Bootstrap CSS -->
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
		integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

	<title>Google Meet Scheduler</title>
</head>

<body>
	<nav class="navbar navbar-dark bg-primary">
		<a class="navbar-brand" href="#">
			<img src="https://i.ytimg.com/vi/yNxPVj0hejg/hqdefault.jpg" width="60" height="40"
				class="d-inline-block align-top" alt="">
			Google Meet Scheduler
		</a>
	</nav>
	<div class="container" style="text-align: center; max-width: 500px;">
		<h1 class="display-4" style="font-size: 40px;">Schedule Google Meet</h1>
		<div>
			<% if(!password || 0===password.length || !email || 0===email.length) { %>
				<p style=" color : red"> Password or Email not set as environment variables or in <mark>.env</mark>
					file. Refer <a href=https://github.com/AmanRaj1608/Google-Meet-Scheduler#readme>here</a> for
					instructions.</p>
				<% } %>
		</div>
		<br>
		<form style="width: 100%;" method="POST" action="/postlink" enctype="application/x-www-form-urlencoded">
			<div class="form-group">
				<label for="url">Meet Url</label>
				<input name="url" type="text" class="form-control" id="url" placeholder="https://meet.google.com/kgh-hwtg-vus"
					required>
			</div>
			<div class="form-group">
				<label for="startDate">Class Start Time</label>
				<input name="startDate" type="datetime-local" class="form-control" id="startDate" required>
			</div>
			<div class="form-group">
				<label for="endDate">Class End Time</label>
				<input name="endDate" type="datetime-local" class="form-control" id="endDate" required>
			</div>
			<div class="form-group">
				<input type="submit" class="btn btn-primary" value="Schedule" id="submit" style="width: 100px">
			</div>
		</form>
		<br>
	</div>

	<div class="container" style="text-align: center; max-width: 850px;">
		<h1 class="display-4" style="font-size: 40px;">Scheduled</h1>
		<div class="card-columns">
			<% for(i in url) {%>
				<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
					<div class="card-header">Id: <%= i %>
					</div>
					<div class="card-body">
						<p class="card-title">meetUrl: <br>
							<%= url[i].url %>
						</p>
						<p class="card-title">startTime: <br>
							<%= new Date(url[i].startTime).toLocaleString().replace(',','') %>
						</p>
						<p class="card-title">endTime: <br>
							<%= new Date(url[i].endTime).toLocaleString().replace(',','') %>
						</p>
					</div>
				</div>
				<% } %>
		</div>
	</div>


	<!-- Optional JavaScript -->
	<!-- jQuery first, then Popper.js, then Bootstrap JS -->
	<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
		integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
		crossorigin="anonymous"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
		integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
		crossorigin="anonymous"></script>
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
		integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
		crossorigin="anonymous"></script>
</body>

</html>
Download .txt
gitextract_1yhuxhtn/

├── .gitignore
├── README.md
├── google-meet.js
├── package.json
├── server.js
└── views/
    └── index.ejs
Download .txt
SYMBOL INDEX (8 symbols across 1 files)

FILE: google-meet.js
  class GoogleMeet (line 7) | class GoogleMeet {
    method constructor (line 8) | constructor(email, pass, head, strict) {
    method createBrowser (line 18) | async createBrowser() {
    method accountLogin (line 30) | async accountLogin(newPage) {
    method schedule (line 47) | async schedule(url, meetId) {
    method closeTab (line 104) | async closeTab(ind) {
    method closeBrowser (line 108) | async closeBrowser() {
    method getBrowserIsActive (line 113) | getBrowserIsActive() {
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (13K chars).
[
  {
    "path": ".gitignore",
    "chars": 18,
    "preview": "/node_modules\n.env"
  },
  {
    "path": "README.md",
    "chars": 2933,
    "preview": "<p align=\"center\"><img src=\"https://cdn.shopify.com/s/files/1/2579/7072/articles/puppeteer-cover_1200x630.png?v=15218124"
  },
  {
    "path": "google-meet.js",
    "chars": 3076,
    "preview": "// const puppeteer = require('puppeteer');\nconst puppeteer = require('puppeteer-extra')\nconst StealthPlugin = require('p"
  },
  {
    "path": "package.json",
    "chars": 477,
    "preview": "{\n  \"name\": \"attendance\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"app.js\",\n  \"scripts\": {\n    \"start\": \"no"
  },
  {
    "path": "server.js",
    "chars": 1804,
    "preview": "require('dotenv').config()\nconst express = require('express');\nconst path = require('path');\nconst GoogleMeet = require("
  },
  {
    "path": "views/index.ejs",
    "chars": 3495,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n\t<!-- Required meta tags -->\n\t<meta charset=\"utf-8\">\n\t<meta name=\"viewport\" con"
  }
]

About this extraction

This page contains the full source code of the AmanRaj1608/Google-Meet-Scheduler GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (11.5 KB), approximately 3.6k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!