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 ================================================

Google Meet Scheduler

Join's meet link for you 😴

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. ---

Made with ❤️ by Aman Raj

``` ================================================ 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 ================================================ Google Meet Scheduler

Schedule Google Meet

<% if(!password || 0===password.length || !email || 0===email.length) { %>

Password or Email not set as environment variables or in .env file. Refer here for instructions.

<% } %>


Scheduled

<% for(i in url) {%>
Id: <%= i %>

meetUrl:
<%= url[i].url %>

startTime:
<%= new Date(url[i].startTime).toLocaleString().replace(',','') %>

endTime:
<%= new Date(url[i].endTime).toLocaleString().replace(',','') %>

<% } %>