diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0a99e1a --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +GOOGLE_USERNAME="john@gmail.com" +GOOGLE_PASSWORD="password" diff --git a/.gitignore b/.gitignore index 3465ead..f9e9f95 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules/ +built/ docs/ +.env diff --git a/core/audio.js b/core/audio.js deleted file mode 100644 index 67d5156..0000000 --- a/core/audio.js +++ /dev/null @@ -1,24 +0,0 @@ -class Audio { - - constructor(page) { - // https://stackoverflow.com/questions/52464583/possible-to-get-puppeteer-audio-feed-and-or-input-audio-directly-to-puppeteer - // Could not get this to work yet - - this.page = page; - } - - async stream(source) { - - // Stream audio - - } - - async stop() { - - // Stop audio stream - } - - -} - -module.exports = Audio; diff --git a/core/audio.ts b/core/audio.ts new file mode 100644 index 0000000..fa707d8 --- /dev/null +++ b/core/audio.ts @@ -0,0 +1,19 @@ +class MeetAudio { + page: unknown; + constructor(page: unknown) { + // https://stackoverflow.com/questions/52464583/possible-to-get-puppeteer-audio-feed-and-or-input-audio-directly-to-puppeteer + // Could not get this to work yet + + this.page = page; + } + + async stream(_source: unknown) { + // Stream audio + } + + async stop() { + // Stop audio stream + } +} + +export default MeetAudio; diff --git a/core/authenticate.js b/core/authenticate.js deleted file mode 100644 index 4bd4b00..0000000 --- a/core/authenticate.js +++ /dev/null @@ -1,50 +0,0 @@ -// Main initializing function - -// Only logs in, however we can skip this by just waiting for the chat button or the leave meeting button. Then signing in can be done manually with headless mode disabled, and the package just automates the other stuff -async function auth({ meetingLink, email, pw }) { - - if (!meetingLink.startsWith("https://meet.google.com/")) {throw("Meeting Link isn't valid. Make sure it looks like 'https://meet.google.com/xyz-wxyz-xyz'!");} - if (!email.endsWith("@gmail.com")) {throw("Email isn't a Google Account");} - - this.meetingLink = meetingLink; this.email = email; - this.browser = await this.puppeteer.launch({ headless: false }); - this.page = await this.browser.newPage(); - this.ctx = await this.browser.defaultBrowserContext(); await this.ctx.overridePermissions('https://meet.google.com', ['microphone', 'camera', 'notifications']); - await this.page.goto(meetingLink); - // Authenticating with credentials - console.log("Logging in...") - try { - var signInButton = await this.page.waitForSelector('.NPEfkd', { visible: true, timeout: 10000 }); await signInButton.focus(); await signInButton.click(); - } catch (e) { - console.log(e) - // Sign In button is not visible, so we assume the page has already redirected, and is not accepting anonymous meeting members - Support for anonymous joining may be implemented in the future - } - var input = await this.page.waitForSelector('input[type=email]', { visible: true, timeout: 0 }); await input.focus(); - await this.page.keyboard.type(email); - await this.page.keyboard.press('Enter'); - var input = await this.page.waitForSelector('input[type=password]', { visible: true, timeout: 0 }); await input.focus(); - await this.page.keyboard.type(pw); - await this.page.keyboard.press('Enter'); - console.log("Authenticated successfully!"); - await this.screenshot('logged-in.png'); // Double check that the meet is about to be joined to. Quickest way to make sure that there aren't any prompts (Like Google's "confirm recovery email" prompt), that can leave the browser hanging. - // Although you can edit the package's code to fit your scenario, the easiest way to fix anything that leaves this program hanging, is to just run the package without headless mode. That way you can continue on any prompts or see issues fast. - // Join Google Meet - await this.page.waitForSelector('div[role=button]'); var join = await this.page.waitForSelector('.VfPpkd-vQzf8d', { visible: true, timeout: 0 }); - for (var i = 3; i > 0; i--) {await this.toggleMic(this.page); await this.toggleVideo(this.page);} // toggle mic and video 3 times because Google Meet glitches and leaves mic on if it's toggled as soon as page loads - await join.click(); - - // Beyond, is code separate from logging in. You could log in manually and just wait for the chat button to show up to start the bot, for example. - await this.page.waitForXPath('/html/body/div[1]/c-wiz/div[1]/div/div[9]/div[3]/div[10]/div[3]/div[3]/div/div/div[3]/span/button', { visible: true, timeout: 0 }); // wait for chat button - - await this.toggleMemberList(); await this.toggleChat(); - this.message.messageListener(this); this.member.memberListener(this); // Start listeners - this.isChatEnabled = this.chatEnabled; - this.Audio = new this.audio(this.page); - console.log("Meeting joined, and listeners are listening!"); - this.emit('ready'); - -} - -module.exports = { - auth: auth -} diff --git a/core/authenticate.ts b/core/authenticate.ts new file mode 100644 index 0000000..736c439 --- /dev/null +++ b/core/authenticate.ts @@ -0,0 +1,79 @@ +// Main initializing function + +// Only logs in, however we can skip this by just waiting for the chat button or the leave meeting button. Then signing in can be done manually with headless mode disabled, and the package just automates the other stuff +export default async function authenticate({ meetingLink, email, pw }) { + if (!meetingLink.startsWith("https://meet.google.com/")) { + throw "Meeting Link isn't valid. Make sure it looks like 'https://meet.google.com/xyz-wxyz-xyz'!"; + } + if (!email.endsWith("@gmail.com")) { + throw "Email isn't a Google Account"; + } + + this.meetingLink = meetingLink; + this.email = email; + this.browser = await this.puppeteer.launch({ headless: false }); + this.page = await this.browser.newPage(); + this.ctx = await this.browser.defaultBrowserContext(); + await this.ctx.overridePermissions("https://meet.google.com", [ + "microphone", + "camera", + "notifications", + ]); + await this.page.goto(meetingLink); + // Authenticating with credentials + console.log("Logging in..."); + try { + var signInButton = await this.page.waitForSelector(".NPEfkd", { + visible: true, + timeout: 10000, + }); + await signInButton.focus(); + await signInButton.click(); + } catch (e) { + console.log(e); + // Sign In button is not visible, so we assume the page has already redirected, and is not accepting anonymous meeting members - Support for anonymous joining may be implemented in the future + } + var input = await this.page.waitForSelector("input[type=email]", { + visible: true, + timeout: 0, + }); + await input.focus(); + await this.page.keyboard.type(email); + await this.page.keyboard.press("Enter"); + var input = await this.page.waitForSelector("input[type=password]", { + visible: true, + timeout: 0, + }); + await input.focus(); + await this.page.keyboard.type(pw); + await this.page.keyboard.press("Enter"); + console.log("Authenticated successfully!"); + await this.screenshot("logged-in.png"); // Double check that the meet is about to be joined to. Quickest way to make sure that there aren't any prompts (Like Google's "confirm recovery email" prompt), that can leave the browser hanging. + // Although you can edit the package's code to fit your scenario, the easiest way to fix anything that leaves this program hanging, is to just run the package without headless mode. That way you can continue on any prompts or see issues fast. + // Join Google Meet + await this.page.waitForSelector("div[role=button]"); + var join = await this.page.waitForSelector(".VfPpkd-vQzf8d", { + visible: true, + timeout: 0, + }); + for (var i = 3; i > 0; i--) { + await this.toggleMic(this.page); + await this.toggleVideo(this.page); + } // toggle mic and video 3 times because Google Meet glitches and leaves mic on if it's toggled as soon as page loads + await join.click(); + + // Beyond, is code separate from logging in. You could log in manually and just wait for the chat button to show up to start the bot, for example. + await this.page.waitForXPath( + "/html/body/div[1]/c-wiz/div[1]/div/div[9]/div[3]/div[10]/div[3]/div[3]/div/div/div[3]/span/button", + { visible: true, timeout: 0 }, + ); // wait for chat button + + await this.toggleMemberList(); + await this.toggleChat(); + this.message.messageListener(this); + this.member.memberListener(this); // Start listeners + this.isChatEnabled = this.chatEnabled; + this.Audio = new this.audio(this.page); + console.log("Meeting joined, and listeners are listening!"); + this.emit("ready"); +} diff --git a/core/meeting.js b/core/meeting.js deleted file mode 100644 index 77df10e..0000000 --- a/core/meeting.js +++ /dev/null @@ -1,54 +0,0 @@ -// Core meeting methods - -async function toggleMic() { - await this.page.keyboard.down('ControlLeft'); - await this.page.keyboard.press('KeyD'); - await this.page.keyboard.up('ControlLeft'); - this.isMicEnabled = !this.isMicEnabled; -} - -async function toggleVideo() { - await this.page.keyboard.down('ControlLeft'); - await this.page.keyboard.press('KeyE'); - await this.page.keyboard.up('ControlLeft'); - this.isVideoEnabled = !this.isVideoEnabled; -} - -async function toggleChat() { - var chatBtn = await this.page.waitForXPath('/html/body/div[1]/c-wiz/div[1]/div/div[9]/div[3]/div[10]/div[3]/div[3]/div/div/div[3]/span/button'); - await chatBtn.click(); -} - -async function toggleMemberList() { - var memberListBtn = await this.page.waitForXPath('/html/body/div[1]/c-wiz/div[1]/div/div[9]/div[3]/div[10]/div[3]/div[3]/div/div/div[2]/span/button'); - await memberListBtn.click(); -} - -async function chatEnabled() { - await this.page.waitForSelector('#bfTqV'); - var disabled = await this.page.evaluate(() => {disabled = document.querySelector('#bfTqV'); if (disabled.disabled === false) {return true;} else if (disabled.disabled === true) {return false;}}); - return disabled; -} - -async function sendMessage(message) { - if (await this.chatEnabled()) { - var chat = await this.page.waitForSelector('#bfTqV'); await chat.focus(); - await this.page.$eval('#bfTqV', (input, message) => {input.value = message; console.log(input); console.log(message)}, message); // replaced `await page.keyboard.type(message)`, because this is a little more instant - await this.page.keyboard.press('Enter'); - } -} - -async function screenshot(path) { - await this.page.screenshot({ path: path, fullPage: true }); -} - -module.exports = { - toggleMic: toggleMic, - toggleVideo: toggleVideo, - toggleChat: toggleChat, - toggleMemberList: toggleMemberList, - chatEnabled: chatEnabled, - sendMessage: sendMessage, - screenshot: screenshot, - -} diff --git a/core/meeting.ts b/core/meeting.ts new file mode 100644 index 0000000..31de546 --- /dev/null +++ b/core/meeting.ts @@ -0,0 +1,63 @@ +// Core meeting methods + +export async function toggleMic() { + await this.page.keyboard.down("ControlLeft"); + await this.page.keyboard.press("KeyD"); + await this.page.keyboard.up("ControlLeft"); + this.isMicEnabled = !this.isMicEnabled; +} + +export async function toggleVideo() { + await this.page.keyboard.down("ControlLeft"); + await this.page.keyboard.press("KeyE"); + await this.page.keyboard.up("ControlLeft"); + this.isVideoEnabled = !this.isVideoEnabled; +} + +export async function toggleChat() { + var chatBtn = await this.page.waitForXPath( + "/html/body/div[1]/c-wiz/div[1]/div/div[9]/div[3]/div[10]/div[3]/div[3]/div/div/div[3]/span/button", + ); + await chatBtn.click(); +} + +export async function toggleMemberList() { + var memberListBtn = await this.page.waitForXPath( + "/html/body/div[1]/c-wiz/div[1]/div/div[9]/div[3]/div[10]/div[3]/div[3]/div/div/div[2]/span/button", + ); + await memberListBtn.click(); +} + +export async function chatEnabled() { + await this.page.waitForSelector("#bfTqV"); + var disabled = await this.page.evaluate(() => { + disabled = document.querySelector("#bfTqV"); + if (disabled.disabled === false) { + return true; + } else if (disabled.disabled === true) { + return false; + } + }); + return disabled; +} + +export async function sendMessage(message) { + if (await this.chatEnabled()) { + var chat = await this.page.waitForSelector("#bfTqV"); + await chat.focus(); + await this.page.$eval( + "#bfTqV", + (input, message) => { + input.value = message; + console.log(input); + console.log(message); + }, + message, + ); // replaced `await page.keyboard.type(message)`, because this is a little more instant + await this.page.keyboard.press("Enter"); + } +} + +export async function screenshot(path: string) { + await this.page.screenshot({ path, fullPage: true }); +} diff --git a/core/member.js b/core/member.js deleted file mode 100644 index edad7dc..0000000 --- a/core/member.js +++ /dev/null @@ -1,67 +0,0 @@ -// Member Listener - -async function memberJoinListener(Meet) { - - while (true) { - await Meet.page.waitForSelector('.iLNCXe', { visible: true, timeout: 0 }); // wait for member to join - var member = await Meet.page.evaluate(() => { return document.querySelector('.iLNCXe').innerText.replace(' has joined', ''); }); - await Meet.emit('memberJoin', Meet.members[member]); - await Meet.page.waitForSelector('.iLNCXe', { hidden: true, timeout: 0 }); - } - -} - -async function memberLeaveListener(Meet) { - - while (true) { - members = Meet.members; // memberLeaveListener keeps own copy of member list (because when a member leaves, the list gets updated and memberLeaveListener doesn't get the member's info) - await Meet.page.waitForSelector('.aGJE1b', { visible: true, timeout: 0 }); // wait for member to leave - var member = await Meet.page.evaluate(() => { - member = document.querySelector('.aGJE1b'); - if (member.innerText.endsWith(' has left the meeting')) { - return member.innerText.replace(' has left the meeting', ''); - } else { - return null; - } - }) - if (member === null) {continue;} - await Meet.emit('memberLeave', members[member]); - await Meet.page.waitForSelector('.aGJE1b', { hidden: true, timeout: 0 }); - } - -} - -async function memberListener(Meet) { - - async function getMembers() { - var members = await Meet.page.evaluate(() => { - members = {}; - member_list = document.querySelector('div[role="list"]'); - for (var i = 0; i < member_list.children.length; i++) { - member = { - name: member_list.children[i].firstChild.lastChild.firstChild.firstChild.innerText, - icon: member_list.children[i].firstChild.firstChild.firstChild.src - } - members[member.name] = member; - } - return members; - }) - Meet.members = members; - } - await getMembers(); - - memberJoinListener(Meet); - memberLeaveListener(Meet); - - await Meet.page.exposeFunction('getMembers', getMembers); - - await Meet.page.evaluate(() => { - memberObserver = new MutationObserver(() => {getMembers();}); - memberObserver.observe(document.querySelector('div[role="list"]'), { subtree: true, childList: true }); - }) - -} - -module.exports = { - memberListener: memberListener -} diff --git a/core/member.ts b/core/member.ts new file mode 100644 index 0000000..413855a --- /dev/null +++ b/core/member.ts @@ -0,0 +1,73 @@ +// Member Listener + +async function memberJoinListener(Meet) { + while (true) { + await Meet.page.waitForSelector(".iLNCXe", { visible: true, timeout: 0 }); // wait for member to join + let member = await Meet.page.evaluate(() => { + const member = document.querySelector(".iLNCXe") as HTMLElement; + return member.innerText.replace(" has joined", ""); + }); + await Meet.emit("memberJoin", Meet.members[member]); + await Meet.page.waitForSelector(".iLNCXe", { hidden: true, timeout: 0 }); + } +} + +async function memberLeaveListener(Meet) { + while (true) { + let members = Meet.members; // memberLeaveListener keeps own copy of member list (because when a member leaves, the list gets updated and memberLeaveListener doesn't get the member's info) + await Meet.page.waitForSelector(".aGJE1b", { visible: true, timeout: 0 }); // wait for member to leave + let member = await Meet.page.evaluate(() => { + member = document.querySelector(".aGJE1b"); + if (member.innerText.endsWith(" has left the meeting")) { + return member.innerText.replace(" has left the meeting", ""); + } else { + return null; + } + }); + if (member === null) { + continue; + } + await Meet.emit("memberLeave", members[member]); + await Meet.page.waitForSelector(".aGJE1b", { hidden: true, timeout: 0 }); + } +} + +export async function memberListener(Meet) { + async function getMembers() { + let members = await Meet.page.evaluate(() => { + let mems = {}; + let member_list = document.querySelector('div[role="list"]'); + for (let i = 0; i < member_list.children.length; i++) { + let member = { + name: ( + member_list.children[i].firstChild.lastChild.firstChild + .firstChild as HTMLElement + ).innerText, + icon: ( + member_list.children[i].firstChild.firstChild + .firstChild as HTMLImageElement + ).src, + }; + mems[member.name] = member; + } + return mems; + }); + Meet.members = members; + } + await getMembers(); + + memberJoinListener(Meet); + memberLeaveListener(Meet); + + await Meet.page.exposeFunction("getMembers", getMembers); + + await Meet.page.evaluate(() => { + let memberObserver = new MutationObserver(() => { + getMembers(); + }); + memberObserver.observe(document.querySelector('div[role="list"]'), { + subtree: true, + childList: true, + }); + }); +} diff --git a/core/message.js b/core/message.js deleted file mode 100644 index 19aa845..0000000 --- a/core/message.js +++ /dev/null @@ -1,36 +0,0 @@ -// Message Listener - -async function messageListener(Meet) { - - async function getRecentMessage() { - var message = await Meet.page.evaluate(() => { - chat = document.querySelector('.z38b6').lastChild; - return { - author: chat.firstChild.firstChild.innerText, - time: chat.firstChild.lastChild.innerText, - content: chat.lastChild.lastChild.innerText - }; // See div.html - }) - if (message.author !== "You") { - await Meet.emit('message', message); - Meet.recentMessage = message; - return message; - } - } - - await Meet.page.waitForSelector('.GDhqjd', { timeout: 0 }); getRecentMessage(); - - await Meet.page.exposeFunction('getRecentMessage', getRecentMessage) - - await Meet.page.evaluate(() => { - // https://stackoverflow.com/questions/47903954/how-to-inject-mutationobserver-to-puppeteer - // https://stackoverflow.com/questions/54109078/puppeteer-wait-for-page-dom-updates-respond-to-new-items-that-are-added-after/54110446#54110446 - messageObserver = new MutationObserver(() => {getRecentMessage();}); - messageObserver.observe(document.querySelector('.z38b6'), { subtree: true, childList: true }); - }); - -} - -module.exports = { - messageListener: messageListener -} diff --git a/core/message.ts b/core/message.ts new file mode 100644 index 0000000..22c6c48 --- /dev/null +++ b/core/message.ts @@ -0,0 +1,36 @@ +// Message Listener + +export async function messageListener(Meet) { + async function getRecentMessage() { + var message = await Meet.page.evaluate(() => { + let chat = document.querySelector(".z38b6").lastChild; + return { + author: (chat.firstChild.firstChild as HTMLElement).innerText, + time: (chat.firstChild.lastChild as HTMLElement).innerText, + content: (chat.lastChild.lastChild as HTMLElement).innerText, + }; // See div.html + }); + if (message.author !== "You") { + await Meet.emit("message", message); + Meet.recentMessage = message; + return message; + } + } + + await Meet.page.waitForSelector(".GDhqjd", { timeout: 0 }); + getRecentMessage(); + + await Meet.page.exposeFunction("getRecentMessage", getRecentMessage); + + await Meet.page.evaluate(() => { + // https://stackoverflow.com/questions/47903954/how-to-inject-mutationobserver-to-puppeteer + // https://stackoverflow.com/questions/54109078/puppeteer-wait-for-page-dom-updates-respond-to-new-items-that-are-added-after/54110446#54110446 + let messageObserver = new MutationObserver(() => { + getRecentMessage(); + }); + messageObserver.observe(document.querySelector(".z38b6"), { + subtree: true, + childList: true, + }); + }); +} diff --git a/examples/start.js b/examples/start.js deleted file mode 100644 index 21afecf..0000000 --- a/examples/start.js +++ /dev/null @@ -1,39 +0,0 @@ -const { Meet } = require('../meet'); -const client = new Meet(); - -config = { meetingLink: 'https://meet.google.com/xyz-wxyz-xyz', email: '', pw: '' }; - -async function command(client, message) { - if (message.content.startsWith("!quote")) { - await client.sendMessage(`${message.author} said, "${message.content.replace("!quote ", "")}" at ${message.time}`); - } - -} - -(async () => { - - await client.once('ready', async () => { - console.log('ready'); - }) - - await client.login(config); - - await client.on('message', async (message) => { - command(client, message); - }) - - await client.on('memberJoin', async (member) => { - await client.sendMessage(`Welcome, ${member.name}!`); - }) - - await client.on('memberLeave', async (member) => { - await client.sendMessage(`Goodbye, ${member.name}!`); - }) - -})() - -/* - Async/await syntax is required if you need to execute specific actions with Puppteer or don't want to be limited to only the events already implemented. -*/ - -// If errors like "Node is detached" get thrown, restarting almost always fixes most errors diff --git a/examples/start.ts b/examples/start.ts new file mode 100644 index 0000000..907f772 --- /dev/null +++ b/examples/start.ts @@ -0,0 +1,44 @@ +import { Meet } from "../meet"; +const client = new Meet(); + +let config = { + meetingLink: "https://meet.google.com/xyz-wxyz-xyz", + email: process.env.GOOGLE_USERNAME, + pw: process.env.GOOGLE_PASSWORD, +}; + +async function command(client, message) { + if (message.content.startsWith("!quote")) { + await client.sendMessage( + `${message.author} said, "${message.content.replace("!quote ", "")}" at ${ + message.time + }`, + ); + } +} + +(async () => { + client.once("ready", async () => { + console.log("ready"); + }); + + await client.login(config); + + client.on("message", async (message) => { + command(client, message); + }); + + client.on("memberJoin", async (member) => { + await client.sendMessage(`Welcome, ${member.name}!`); + }); + + client.on("memberLeave", async (member) => { + await client.sendMessage(`Goodbye, ${member.name}!`); + }); +})(); + +/* + Async/await syntax is required if you need to execute specific actions with Puppteer or don't want to be limited to only the events already implemented. +*/ + +// If errors like "Node is detached" get thrown, restarting almost always fixes most errors diff --git a/meet.js b/meet.js deleted file mode 100644 index eae1b69..0000000 --- a/meet.js +++ /dev/null @@ -1,62 +0,0 @@ -const EventEmitter = require('events'); -const puppeteer = require('puppeteer-extra'); -const StealthPlugin = require('puppeteer-extra-plugin-stealth'); -puppeteer.use(StealthPlugin()); - -const authenticate = require('./core/authenticate'); -const meeting = require('./core/meeting'); -const message = require('./core/message'); -const member = require('./core/member'); -const audio = require('./core/audio'); // Not working - - -class Meet extends EventEmitter { - - constructor() { - super(); - console.log("Client created!") - - // Listeners (for use in login function) - this.message = message; - this.member = member; - this.audio = audio; - - this.meetingLink = undefined; - this.email = undefined - - this.puppeteer = puppeteer; - this.browser = undefined; - this.page = undefined; - this.ctx = undefined; - - this.isMicEnabled = true; - this.isVideoEnabled = true; - this.isChatEnabled = undefined; - - this.recentMessage = undefined; - this.members = undefined; - }; - - login = authenticate.auth; - - toggleMic = meeting.toggleMic; - toggleVideo = meeting.toggleVideo; - toggleChat = meeting.toggleChat; - toggleMemberList = meeting.toggleMemberList; - chatEnabled = meeting.chatEnabled; - sendMessage = meeting.sendMessage; - screenshot = meeting.screenshot; - -} - -module.exports.Meet = Meet; - -/* Notes */ - -// Various XPaths and element class names or ids are not explained throughout this source; there's probably a better way to have a permanent selector to a specific element though - -// The Audio part of this package has not yet been implemented - -// This code can be improved in many ways, but I wrote this during the beginning of Covid lockdown; I've only now decided to add a license and create a repository to publish - -// This package aims to be similar to the Discord JS library diff --git a/meet.ts b/meet.ts new file mode 100644 index 0000000..8c9e874 --- /dev/null +++ b/meet.ts @@ -0,0 +1,76 @@ +import { EventEmitter } from "events"; +import puppeteer from "puppeteer-extra"; +import StealthPlugin from "puppeteer-extra-plugin-stealth"; + +import authenticate from "./core/authenticate"; +import * as meeting from "./core/meeting"; +import * as message from "./core/message"; +import * as member from "./core/member"; +import audio from "./core/audio"; // not working + +type puppeteer = typeof puppeteer; + +export class Meet extends EventEmitter { + message: typeof message; + member: typeof member; + audio: typeof audio; + + meetingLink: string | undefined; + email: string | undefined; + + puppeteer: puppeteer; + browser: undefined; + page: undefined; + ctx: undefined; + + isMicEnabled: boolean; + isVideoEnabled: boolean; + isChatEnabled: boolean | undefined; + + recentMessage: string | undefined; + members: string[] | undefined; + constructor() { + super(); + console.log("Client created!"); + + // Listeners (for use in login function) + this.message = message; + this.member = member; + this.audio = audio; + + this.meetingLink = undefined; + this.email = undefined; + + this.puppeteer = puppeteer.use(StealthPlugin()); + this.browser = undefined; + this.page = undefined; + this.ctx = undefined; + + this.isMicEnabled = true; + this.isVideoEnabled = true; + this.isChatEnabled = undefined; + + this.recentMessage = undefined; + this.members = undefined; + } + + login = authenticate; + + toggleMic = meeting.toggleMic; + toggleVideo = meeting.toggleVideo; + toggleChat = meeting.toggleChat; + toggleMemberList = meeting.toggleMemberList; + chatEnabled = meeting.chatEnabled; + sendMessage = meeting.sendMessage; + screenshot = meeting.screenshot; +} + +/* Notes */ + +// Various XPaths and element class names or ids are not explained throughout this source; there's probably a better way to have a permanent selector to a specific element though + +// The Audio part of this package has not yet been implemented + +// This code can be improved in many ways, but I wrote this during the beginning of Covid lockdown; I've only now decided to add a license and create a repository to publish + +// This package aims to be similar to the Discord JS library diff --git a/package-lock.json b/package-lock.json index 07743e7..7def4a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "puppeteer": "^13.7.0", "puppeteer-extra": "^3.2.3", "puppeteer-extra-plugin-stealth": "^2.9.0" + }, + "devDependencies": { + "typescript": "^5.3.3" } }, "node_modules/@types/debug": { @@ -836,6 +839,19 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", @@ -1507,6 +1523,12 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, + "typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true + }, "unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", diff --git a/package.json b/package.json index a3f9f1b..9f20e39 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,8 @@ "puppeteer": "^13.7.0", "puppeteer-extra": "^3.2.3", "puppeteer-extra-plugin-stealth": "^2.9.0" + }, + "devDependencies": { + "typescript": "^5.3.3" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3d6605e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./built", + "allowJs": true, + "target": "es5", + "esModuleInterop": true + }, + "include": ["./**/*"] +}