├── .gitignore ├── .env ├── styles.css ├── package.json ├── LICENSE ├── success.html ├── error.html ├── app.js ├── index.html ├── README.md └── zoomParser.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | APP_ID= 2 | APP_SECRET= -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3, 4 | h4, 5 | p, 6 | label { 7 | color: white; 8 | } 9 | 10 | html, 11 | body { 12 | background-color: #1a1f36; 13 | } 14 | 15 | #header { 16 | background-color: #4f566b; 17 | } 18 | 19 | #form { 20 | background-color: #4f566b; 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "join-meeting", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "body-parser": "^1.19.0", 13 | "dotenv": "^8.2.0", 14 | "express": "^4.17.1", 15 | "lodash": "^4.17.15", 16 | "path": "^0.12.7", 17 | "request": "^2.88.2", 18 | "request-promise": "^4.2.5", 19 | "symbl-node": "^1.0.3", 20 | "url": "^0.11.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 rammer.ai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Symbl AI Personal Assistant 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 24 | 25 |
26 |
27 |
28 |

29 | Success! Symbl is now dialing into your meeting. 30 |

31 |
32 |

33 | At the end of the call, Symbl will send you an email with the 34 | conversation summary. 35 |

36 |
37 | 44 |
45 |
46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Symbl AI Personal Assistant 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 24 | 25 |
26 |
27 |
28 |

29 | Something went wrong, make sure that 30 |
    31 |
  • You are using the correct App ID and App Secret
  • 32 |
  • You have entered in the correct Zoom Meeting Invite
  • 33 |
  • You have Symbl Minutes available for consumption
  • 34 |
35 |

36 | 43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample inbound integration showing how to use Twilio Flex 3 | * with Symbl's websocket API as the inbound audio stream 4 | */ 5 | 6 | /* import necessary modules for the web-socket API */ 7 | require("dotenv").config(); 8 | const express = require("express"); 9 | const app = express(); 10 | const server = require("http").createServer(app); 11 | var path = require("path"); 12 | const sdk = require("symbl-node").sdk; 13 | const bodyParser = require("body-parser"); 14 | const zoomParser = require("./zoomParser"); 15 | 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | app.use(express.static(__dirname + "/")); 18 | 19 | app.get("/", (req, res) => { 20 | res.sendFile(path.join(__dirname + "/index.html")); 21 | }); 22 | 23 | app.post("/join", (req, res) => { 24 | const sample = req.body.meetingInvite; 25 | const meetingName = req.body.meetingName; 26 | const parser = zoomParser(); 27 | 28 | if (parser.isValid(sample)) { 29 | const result = parser.parse(sample); 30 | result.then((data) => { 31 | sdk 32 | .init({ 33 | appId: process.env.APP_ID, 34 | appSecret: process.env.APP_SECRET, 35 | basePath: "https://api.symbl.ai", 36 | }) 37 | .then(() => { 38 | console.log("SDK Initialized"); 39 | sdk 40 | .startEndpoint({ 41 | endpoint: { 42 | type: "pstn", 43 | phoneNumber: data.joiningDetails[0].phoneNumbers[0], 44 | dtmf: data.joiningDetails[0].dtmf, 45 | }, 46 | actions: [ 47 | { 48 | invokeOn: "stop", 49 | name: "sendSummaryEmail", 50 | parameters: { 51 | emails: [req.body.email], 52 | }, 53 | }, 54 | ], 55 | data: { 56 | session: { 57 | name: meetingName, 58 | }, 59 | }, 60 | }) 61 | .then((connection) => { 62 | const connectionId = connection.connectionId; 63 | console.log("Successfully connected.", connectionId); 64 | res.sendFile(path.join(__dirname + "/success.html")); 65 | }) 66 | .catch((err) => { 67 | console.error("Error while starting the connection", err); 68 | res.sendFile(path.join(__dirname + "/error.html")); 69 | }); 70 | }) 71 | .catch((err) => { 72 | console.error("Error in SDK initialization.", err); 73 | res.sendFile(path.join(__dirname + "/error.html")); 74 | }); 75 | }); 76 | } else { 77 | res.sendFile(path.join(__dirname + "/error.html")); 78 | } 79 | }); 80 | 81 | var port = process.env.PORT || 5000; 82 | 83 | app.listen(port, function () { 84 | console.log("Symbl Personal Assistant app listening on port " + port + "!"); 85 | }); 86 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Symbl AI Personal Assistant 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 24 | 25 |
26 |
27 |
37 |
38 |
39 | 40 | 48 |
49 |
50 | 51 | 58 |
59 |
60 | 61 | 68 |
69 | 70 |
71 |
72 |
73 |
74 | 75 | 76 | 85 | 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symbl Personal Assistant Meeting App 2 | 3 | 4 | [![Telephony](https://img.shields.io/badge/Symbl-Telephony-brightgreen)](https://docs.symbl.ai/docs/telephony/overview/post-api) 5 | 6 | Symbl's APIs empower developers to enable: 7 | - **Real-time** analysis of free-flowing discussions to automatically surface highly relevant summary discussion topics, contextual insights, suggestive action items, follow-ups, decisions, and questions. 8 | - **Voice APIs** that makes it easy to add AI-powered conversational intelligence to either [telephony][telephony] or [WebSocket][websocket] interfaces. 9 | - **Conversation APIs** that provide a REST interface for managing and processing your conversation data. 10 | - **Summary UI** with a fully customizable and editable reference experience that indexes a searchable transcript and shows generated actionable insights, topics, timecodes, and speaker information. 11 | 12 |
13 | 14 | ## Enable Symbl for Zoom Meetings 15 | 16 |
17 | 18 | * [Introduction](#introduction) 19 | * [Pre-requisites](#pre-requisites) 20 | * [Setup and Deploy](#setupanddeploy) 21 | * [Dependencies](#dependencies) 22 | * [Community](#community) 23 | 24 | ## Introduction 25 | 26 | This is a sample app that lets you invite Symbl to your Zoom meeting by providing a zoom invite. 27 | 28 | ## Pre-requisites 29 | 30 | * JS ES6+ 31 | * Node.js 32 | * npm (or your favorite package manager) 33 | * Zoom Account [Zoom](https://zoom.us/signup) 34 | 35 | ## Setup and Deploy 36 | The first step to getting setup is to [sign up][signup]. 37 | 38 | Update the .env file with the following: 39 | 1. Your App Id that you can get from [Platform](https://platform.symbl.ai) 40 | 2. Your App Secret that you can get from [Platform](https://platform.symbl.ai) 41 | 42 | Run the follwing npm commands: 43 | 1. `npm install` to download all the node modules 44 | 2. `node app.js` to start the node server 45 | 46 | Navigate to localhost:5000 to view the app 47 | 1. Enter the email address you would like the meeting summary sent to 48 | 2. Enter a Meeting Name Identifier 49 | 3. Paste the full meeting invite for the Zoom meeting you wish to connect to. Ex. 50 | ``` 51 | Symbl is inviting you to a scheduled Zoom meeting. 52 | 53 | Topic: Symbl Personal Meeting Room 54 | 55 | Join Zoom Meeting 56 | https://us02web.zoom.us/j/55555555?pwd= 57 | 58 | Meeting ID: 555 5555 555 59 | Passcode: 55555 60 | One tap mobile 61 | +16699009128,,2323522600# US (San Jose) 62 | +12532158782,,2323522600# US (Tacoma) 63 | 64 | Dial by your location 65 | +1 669 900 9128 US (San Jose) 66 | +1 253 215 8782 US (Tacoma) 67 | +1 346 248 7799 US (Houston) 68 | +1 646 558 8656 US (New York) 69 | +1 301 715 8592 US (Washington D.C) 70 | +1 312 626 6799 US (Chicago) 71 | Meeting ID: 232 352 2600 72 | Find your local number: https://us02web.zoom.us/u/kz2YbGRTL 73 | 74 | Join by SIP 75 | 5555555555@zoomcrc.com 76 | 77 | Join by H.323 78 | 162.255.37.11 (US West) 79 | 162.255.36.11 (US East) 80 | 81 | Passcode: 555555 82 | ``` 83 | 4. Submit and Symbl will join via Telephony API dial in from PSTN 12015947998 84 | 85 | ## Dependencies 86 | 87 | ```json 88 | "dependencies": { 89 | "body-parser": "^1.19.0", 90 | "dotenv": "^8.2.0", 91 | "express": "^4.17.1", 92 | "lodash": "^4.17.15", 93 | "path": "^0.12.7", 94 | "request": "^2.88.2", 95 | "request-promise": "^4.2.5", 96 | "symbl-node": "^1.0.3", 97 | "url": "^0.11.0" 98 | } 99 | ``` 100 | 101 | ## Community 102 | 103 | If you have any questions, feel free to reach out to us at devrelations@symbl.ai, through our Community [Slack][slack], or [developer community][developer_community] 104 | 105 | This guide is actively developed, and we love to hear from you! Please feel free to [create an issue][issues] or [open a pull request][pulls] with your questions, comments, suggestions and feedback. If you liked our integration guide, please star our repo! 106 | 107 | This library is released under the [MIT License][license] 108 | 109 | [license]: LICENSE.txt 110 | [telephony]: https://docs.symbl.ai/docs/telephony/overview/post-api 111 | [websocket]: https://docs.symbl.ai/docs/streamingapi/overview/introduction 112 | [developer_community]: https://community.symbl.ai/?_ga=2.134156042.526040298.1609788827-1505817196.1609788827 113 | [signup]: https://platform.symbl.ai/?_ga=2.63499307.526040298.1609788827-1505817196.1609788827 114 | [issues]: https://github.com/symblai/symbl-for-zoom/issues 115 | [pulls]: https://github.com/symblai/symbl-for-zoom/pulls 116 | [slack]: https://join.slack.com/t/symbldotai/shared_invite/zt-4sic2s11-D3x496pll8UHSJ89cm78CA -------------------------------------------------------------------------------- /zoomParser.js: -------------------------------------------------------------------------------- 1 | const request = require("request-promise"); 2 | const url = require("url"); 3 | const { get } = require("lodash"); 4 | 5 | const regex = /(.*).*[.,\s].*Meeting ID:[\s]*([\d\s]+)?/gm; 6 | const phoneNumberRegex = /[+][\s,0-9].*/gm; 7 | const urlRegex = /.*https:\/\/.*[.]*zoom.us\/j\/(\d+)/gm; 8 | const passwordRegex = /Passcode:[\s]*([\d]+)/gim; 9 | const joiningInstructionsRegex = /Joining instructions:(.*)/gim; 10 | 11 | const standardPhoneNumbers = { US: ["+16465588656", "+14086380968"] }; 12 | 13 | const keys = ["Meeting ID:", "Joining instructions", "zoom.us/j/"]; 14 | 15 | const getPhoneNumbers = async (content, resetRegex = true) => { 16 | if (resetRegex) { 17 | phoneNumberRegex.lastIndex = 0; 18 | } 19 | 20 | let match; 21 | const phoneNumbers = []; 22 | let country = null; 23 | 24 | while ((match = phoneNumberRegex.exec(content)) !== null) { 25 | const splitStr = match[0].replace(/[\\,]+/g, " ").split(" "); 26 | //Add check for length of phone number 27 | splitStr[0].length > 9 && 28 | phoneNumbers.push(splitStr[0].trim().replace(/ /g, "")); 29 | if (splitStr.length >= 3 && !country) country = splitStr[2].trim(); 30 | } 31 | 32 | return { 33 | country: country, 34 | phoneNumbers: phoneNumbers, 35 | }; 36 | }; 37 | 38 | const getJoiningInstructions = async (joiningInstructionsUrl) => { 39 | if (joiningInstructionsUrl) { 40 | const zoomJoiningInstructionsUrl = get( 41 | url.parse(joiningInstructionsUrl, true), 42 | "query.q", 43 | null 44 | ); 45 | 46 | if (zoomJoiningInstructionsUrl) { 47 | const requestOptions = { 48 | uri: zoomJoiningInstructionsUrl, 49 | method: "GET", 50 | resolveWithFullResponse: true, 51 | }; 52 | 53 | const response = await request(requestOptions); 54 | if (response && response.statusCode === 200) { 55 | return response.body; 56 | } else { 57 | return null; 58 | } 59 | } else { 60 | logger.info("Joining Instructions URL not found", { 61 | joiningInstructionsUrl, 62 | }); 63 | } 64 | } 65 | 66 | return null; 67 | }; 68 | 69 | const isNumeric = (str) => { 70 | return /^\d+$/.test(str); 71 | }; 72 | 73 | const getPasswordFromPayload = async (payload, resetRegex = true) => { 74 | if (resetRegex) { 75 | passwordRegex.lastIndex = 0; 76 | } 77 | 78 | let meetingPasswordData; 79 | while ((meetingPasswordData = passwordRegex.exec(payload)) != null) { 80 | if ( 81 | meetingPasswordData && 82 | meetingPasswordData.length === 2 && 83 | isNumeric(meetingPasswordData[1]) 84 | ) { 85 | return meetingPasswordData[1]; 86 | } 87 | } 88 | }; 89 | 90 | const constructJoiningDetails = async ({ 91 | country, 92 | phoneNumbers, 93 | content, 94 | meetingPassword, 95 | meetingId, 96 | }) => { 97 | let extraStandardNumbers = standardPhoneNumbers[country] || []; 98 | 99 | extraStandardNumbers = extraStandardNumbers.filter( 100 | (number) => !phoneNumbers.includes(number) 101 | ); 102 | 103 | if (extraStandardNumbers.length > 0) { 104 | extraStandardNumbers.forEach((number) => phoneNumbers.push(number)); 105 | } 106 | 107 | const dtmfFromUrl = urlRegex.exec(content); 108 | let dtmf = 109 | dtmfFromUrl && dtmfFromUrl.length === 2 110 | ? dtmfFromUrl[1].trim().includes("?") 111 | ? dtmfFromUrl[1].trim().split("?")[0].concat("#") 112 | : dtmfFromUrl[1].trim().concat("#") 113 | : meetingId.trim().replace(/ /g, "").concat("#"); 114 | 115 | if (meetingPassword) { 116 | dtmf = dtmf.concat(`,,${meetingPassword}#`); 117 | } 118 | 119 | return { 120 | joiningDetails: [ 121 | { 122 | country, 123 | phoneNumbers, 124 | dtmf, 125 | }, 126 | ], 127 | }; 128 | }; 129 | 130 | const zoomParser = () => { 131 | const patternToParser = {}; 132 | patternToParser[keys[0]] = async (content) => { 133 | if (content) { 134 | const result = regex.exec(content); 135 | 136 | if (result && result.length === 3) { 137 | let { country, phoneNumbers } = await getPhoneNumbers(content, false); 138 | 139 | let meetingPassword = await getPasswordFromPayload(content, false); 140 | 141 | const joiningInstructionsUrl = get( 142 | joiningInstructionsRegex.exec(content), 143 | "[1]", 144 | null 145 | ); 146 | let joiningInstructions = null; 147 | 148 | if (joiningInstructionsUrl) 149 | joiningInstructions = await getJoiningInstructions( 150 | joiningInstructionsUrl 151 | ); 152 | 153 | if ( 154 | (!phoneNumbers || phoneNumbers.length <= 0) && 155 | joiningInstructions 156 | ) { 157 | const data = await getPhoneNumbers(joiningInstructions); 158 | 159 | phoneNumbers = data.phoneNumbers; 160 | country = data.country; 161 | } 162 | 163 | if (!meetingPassword) { 164 | meetingPassword = await getPasswordFromPayload(joiningInstructions); 165 | } 166 | 167 | if (!country) country = "US"; 168 | 169 | return await constructJoiningDetails({ 170 | country, 171 | phoneNumbers, 172 | meetingPassword, 173 | content, 174 | meetingId: result[2], 175 | }); 176 | } 177 | } 178 | return null; 179 | }; 180 | 181 | patternToParser[keys[1]] = async (content) => { 182 | if (content) { 183 | let joiningInstructions = null; 184 | const joiningInstructionsUrl = get( 185 | joiningInstructionsRegex.exec(content), 186 | "[1]", 187 | null 188 | ); 189 | 190 | if (joiningInstructionsUrl) { 191 | joiningInstructions = await getJoiningInstructions( 192 | joiningInstructionsUrl 193 | ); 194 | if (joiningInstructions) { 195 | let meetingPassword = await getPasswordFromPayload( 196 | joiningInstructions, 197 | false 198 | ); 199 | let { phoneNumbers, country } = await getPhoneNumbers( 200 | joiningInstructions, 201 | false 202 | ); 203 | 204 | if (!country) country = "US"; 205 | 206 | const meetingId = get(regex.exec(joiningInstructions), "[2]", null); 207 | return await constructJoiningDetails({ 208 | country, 209 | phoneNumbers, 210 | meetingPassword, 211 | content: joiningInstructions, 212 | meetingId, 213 | }); 214 | } 215 | } 216 | } 217 | 218 | return null; 219 | }; 220 | 221 | patternToParser[keys[2]] = async (content) => { 222 | if (content) { 223 | const result = urlRegex.exec(content.trim()); 224 | if (result && result.length === 2) { 225 | let dtmf = result[1].trim(); 226 | if (dtmf.includes("?")) { 227 | dtmf = dtmf.split("?")[0]; 228 | } 229 | 230 | dtmf = dtmf.concat("#"); 231 | 232 | const meetingPasswordData = passwordRegex.exec(content); 233 | let meetingPassword = 234 | meetingPasswordData && meetingPasswordData.length === 2 235 | ? meetingPasswordData[1] 236 | : null; 237 | if (meetingPassword) { 238 | dtmf = dtmf.concat(`,,${meetingPassword}#`); 239 | } 240 | 241 | return { 242 | joiningDetails: [ 243 | { 244 | country: "US", 245 | phoneNumbers: standardPhoneNumbers["US"], 246 | dtmf, 247 | }, 248 | ], 249 | }; 250 | } 251 | return null; 252 | } 253 | }; 254 | 255 | return { 256 | isValid(content) { 257 | return content && keys.filter((key) => content.includes(key)).length > 0; 258 | }, 259 | 260 | async parse(content) { 261 | if (content) { 262 | let parser = null; 263 | keys.some((key, index) => { 264 | if (content.toLowerCase().includes(key.toLowerCase())) { 265 | parser = patternToParser[keys[index]]; 266 | return true; 267 | } 268 | 269 | return false; 270 | }); 271 | 272 | if (parser) { 273 | regex.lastIndex = 0; 274 | urlRegex.lastIndex = 0; 275 | phoneNumberRegex.lastIndex = 0; 276 | passwordRegex.lastIndex = 0; 277 | joiningInstructionsRegex.lastIndex = 0; 278 | 279 | return await parser(content); 280 | } 281 | } else { 282 | return null; 283 | } 284 | }, 285 | }; 286 | }; 287 | 288 | // const sample = "Aditya W is inviting you to a scheduled Zoom meeting.\n" + 289 | // "\n" + 290 | // "Join Zoom Meeting\n" + 291 | // "https://zoom.us/j/996291789\n" + 292 | // "\n" + 293 | // "One tap mobile\n" + 294 | // "+16465588656,,996291787# US (New York)\n" + 295 | // "+14086380968,,996291787# US (San Jose)\n" + 296 | // "\n" + 297 | // "Dial by your location\n" + 298 | // " +1 646 558 8656 US (New York)\n" + 299 | // " +1 408 638 0968 US (San Jose)\n" + 300 | // "Meeting ID: 996 291 787" + 301 | // "Find your local number: https://zoom.us/u/acLLoulOb"; 302 | // const parser = zoomParser(); 303 | // if (parser.isValid(sample)) { 304 | // const res = parser.parse(sample); 305 | // console.log(res); 306 | // } 307 | 308 | module.exports = zoomParser; 309 | 310 | // const sample = `Hi there, 311 | // 312 | // Toshish Jawale is inviting you to a scheduled Zoom meeting. 313 | // 314 | // Join from PC, Mac, Linux, iOS or Android: https://zoom.us/j/476452611 315 | // 316 | // Or iPhone one-tap : 317 | // US: +16465588665,,476452611# or +14086380986,,476452611# 318 | // Or Telephone: 319 | // Dial(for higher quality, dial a number based on your current location): 320 | // US: +1 646 558 8665 or +1 408 638 0986 321 | // Meeting ID: 476 452 611 322 | // International numbers available: https://zoom.us/u/yeuSXkkN`; 323 | // 324 | // const parser = zoomParser(); 325 | // if (parser.isValid(sample)) { 326 | // const res = parser.parse(sample); 327 | // console.log(res); 328 | // } 329 | // 330 | 331 | // const sample1 = `You have been invited to the following event.\n\nTitle: Again + with pass+ pro\nWhen: Mon Apr 6, 2020 4pm – 5pm India Standard Time - Kolkata\n\nJoining info: Join Zoom Meeting\nhttps://zoom.us/j/6237467470?pwd=dmdZUGVCZGRQMGpaTzVFanFzY0ZmUT09 (ID: 6237467470, password: abhay)\n\nJoin by phone\n(US) +1 312-626-6799\n\nJoining instructions: https://www.google.com/url?q=https://applications.zoom.us/addon/invitation/detail?meetingUuid%3DM6vt6D0HTyO52941hhamBg%253D%253D%26signature%3Daa6880d40d38c68cbbc61fd1caad26b3dd428a9ee160ff8be2f6b74dc116c85a&sa=D&usg=AOvVaw3ur1l5U69uEB2MWcCPJcLx 332 | // \n\nJoining notes: Password: abhay\n\nCalendar: meetinginsights@meet-hub.symbl.ai\nWho:\n * abhay.dalvi@symbl.ai - organizer\n * arjun.chouhan@symbl.ai\n * meetinginsights@meet-hub.symbl.ai - optional\n\nYour attendance is optional.\n\nEvent details: https://www.google.com/calendar/event?action=VIEW&eid=NmswbG40MnEyMTJlZXZkamlvOW1raHAzNTAgbWVldGluZ2luc2lnaHRzQG1lZXQtaHViLnN5bWJsLmFp&tok=MjAjYWJoYXkuZGFsdmlAc3ltYmwuYWkwNTc3NGUwZTdjZjUwOWRjOGE2M2EzYzFkYmU3ZjAxYzYyMjdlZWJh&ctz=Asia%2FKolkata&hl=en&es=0\n\nInvitation from Google Calendar: https://www.google.com/calendar/\n\nYou are receiving this courtesy email at the account meetinginsights@meet-hub.symbl.ai because you are an attendee of this event.\n\nTo stop receiving future updates for this event, decline this event. Alternatively you can sign up for a Google account at https://www.google.com/calendar/ and control your notification settings for your entire calendar.\n\nForwarding this invitation could allow any recipient to send a response to the organizer and be added to the guest list, or invite others regardless of their own invitation status, or to modify your RSVP. Learn more at https://support.google.com/calendar/answer/37135#forwarding\n" {"htmlDecodedContent":"You have been invited to the`; 333 | 334 | // const sample1 = `Zoom logo 335 | // Arjun Chouhan is inviting you to a scheduled Zoom meeting. 336 | // 337 | // Topic: This is a calendar meeting 338 | // Time: Apr 6, 2020 01:44 PM India 339 | // 340 | // Join Zoom Meeting 341 | // https://zoom.us/j/222349200?pwd=VGFKNUZUMmYrT2NIbTFUdkxCc2hVdz09 342 | // 343 | // Meeting ID: 222 349 200 344 | // Password: gh7zfz 345 | // 346 | // One tap mobile 347 | // +12532158782,,222349200# US 348 | // +13017158592,,222349200# US 349 | // 350 | // Dial by your location 351 | // +1 253 215 8782 US 352 | // +1 301 715 8592 US 353 | // +1 312 626 6799 US (Chicago) 354 | // +1 346 248 7799 US (Houston) 355 | // +1 646 558 8656 US (New York) 356 | // +1 669 900 9128 US (San Jose) 357 | // Meeting ID: 222 349 200 358 | // Password: 526261 359 | // Find your local number: https://zoom.us/u/aNEntYu01`; 360 | // 361 | // if (parser.isValid(sample1)) { 362 | // parser.parse(sample1).then(res => { 363 | // console.log(res, res.joiningDetails[0].phoneNumbers) 364 | // }); 365 | // } 366 | --------------------------------------------------------------------------------