├── .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 |
42 | Join another meeting
43 |
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 |
41 | Join another meeting
42 |
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 |
74 |
75 |
76 |
85 |
86 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Symbl Personal Assistant Meeting App
2 |
3 |
4 | [](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 |
--------------------------------------------------------------------------------