Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heimdall prompts users of the zoom command to post meeting notes after the meeting #56

Merged
merged 10 commits into from
Jun 21, 2019
4 changes: 3 additions & 1 deletion env-var.list
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
HUBOT_HOST=$HUBOT_HOST
GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID
GITHUB_CLIENT_SECRET=$GITHUB_CLIENT_SECRET
HUBOT_FLOWDOCK_API_TOKEN=$HUBOT_FLOWDOCK_API_TOKEN
HUBOT_FLOWDOCK_API_TOKEN=$HUBOT_FLOWDOCK_API_TOKEN
ZOOM_API_KEY=$ZOOM_API_KEY
ZOOM_API_SECRET=$ZOOM_API_SECRET
kb0rg marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 14 additions & 1 deletion lib/zoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const API_BASE_URL = "https://api.zoom.us/v2",
APP_BASE_URL = "zoommtg://zoom.us"
const URLs = {
meetings: `${API_BASE_URL}/users/{userId}/meetings`,
meetingDetail: `${API_BASE_URL}/meetings/{meetingId}`,
users: `${API_BASE_URL}/users`,
appJoin: `${APP_BASE_URL}/join?action=join&confno={meetingId}`,
}
Expand All @@ -32,6 +33,18 @@ async function getSession(apiKey: string, apiSecret: string) {
}
}

async function getMeetingDetails(sessionToken: string, meetingId: string) {
try {
const response = await axios.get(
URLs.meetingDetail.replace(/{meetingId}/, meetingId),
{ params: { access_token: sessionToken } },
)
return response.data
} catch (err) {
throw `Something went wrong getting meeting details: ${util.inspect(err)}.`
}
}

enum UserType {
Basic = 1,
Pro,
Expand Down Expand Up @@ -144,4 +157,4 @@ class Account {
}
}

export { getSession, Session }
export { getSession, Session, getMeetingDetails }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make a note to refactor this one and pull it into Session. I think that + refactoring to async/await below can be one bundle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created issue #62 for the refactoring follow-up

131 changes: 131 additions & 0 deletions scripts/zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,96 @@ const zoom = require("../lib/zoom"),
/** @type zoom.Session */
let ZOOM_SESSION = null

const INTERVAL_DELAY = 15 * 1000
const MEETING_START_TIMEOUT_DELAY = 10 * 60 * 1000 // we'll only watch this long if meeting doesn't start
const MEETING_DURATION_TIMEOUT_DELAY = 60 * 60 * 1000 // max mtg watch duration

function isMeetingStarted(meeting) {
return zoom
.getMeetingDetails(ZOOM_SESSION.token, meeting.id)
.then(meetingDetail => {
if ("status" in meetingDetail && meetingDetail.status === "started") {
return true
} else {
return false
}
})
}

function isMeetingFinished(meeting, meetingDidStart) {
return zoom
.getMeetingDetails(ZOOM_SESSION.token, meeting.id)
.then(meetingDetail => {
if (
"status" in meetingDetail &&
meetingDetail.status == "waiting" &&
meetingDidStart === true
) {
return true
} else {
return false
}
})
}

function watchMeeting(meeting) {
let meetingStartedPromise = new Promise((resolve, reject) => {
let startIntervalId = setInterval(function() {
isMeetingStarted(meeting)
.then(isStarted => {
if (isStarted === true) {
clearInterval(startIntervalId)
clearTimeout(startTimeoutId)
resolve(true)
}
})
.catch(err => {
reject(
`Something went wrong setting up START watch interval: ${util.inspect(
err,
)}`,
)
return
})
}, INTERVAL_DELAY)
let startTimeoutId = setTimeout(() => {
clearInterval(startIntervalId)
resolve(false)
}, MEETING_START_TIMEOUT_DELAY)
})
return meetingStartedPromise.then(meetingStartStatus => {
let meetingFinishedPromise = new Promise((resolve, reject) => {
if (meetingStartStatus === false) {
resolve("never started")
return
}
let endIntervalId = setInterval(function() {
isMeetingFinished(meeting, meetingStartStatus)
.then(isFinished => {
if (isFinished === true) {
clearInterval(endIntervalId)
clearTimeout(endTimeoutId)
resolve(true)
}
})
.catch(err => {
reject(
`Something went wrong setting up END watch interval: ${util.inspect(
err,
)}`,
)
return
})
}, INTERVAL_DELAY)
let endTimeoutId = setTimeout(() => {
clearInterval(endIntervalId)
resolve(null)
}, MEETING_DURATION_TIMEOUT_DELAY)
})
return meetingFinishedPromise
})
}

module.exports = function(robot) {
zoom
.getSession(process.env["ZOOM_API_KEY"], process.env["ZOOM_API_SECRET"])
Expand All @@ -34,6 +124,7 @@ module.exports = function(robot) {
res.send(
`All set; open in [the app](${meeting.app_url}) or [your browser](${meeting.join_url})!`,
)
return meeting
})
.catch(err => {
robot.logger.error(
Expand All @@ -42,5 +133,45 @@ module.exports = function(robot) {
)
res.send("Uh-oh, there was an issue finding an available meeting :(")
})
.then(meeting => {
robot.logger.info(`Start watching meeting: ${meeting.id}`)
return watchMeeting(meeting)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

watchMeeting could fluently be named waitForMeetingEnd, which would make this read waitForMeetingEnd(...).then(do stuff).

.then(finalMeetingStatus => {
if (!finalMeetingStatus) {
// if finalMeetingStatus is null, the meeting exceeded the timeout.
// We assume the meeting still happened, so we still want to reply
res.send(
`@${res.message.user.name} Don't forget to post meeting notes when your call ends!`,
)
robot.logger.info(
`Stopped watching, meeting still going: ${meeting.id}`,
)
} else if (finalMeetingStatus === "never started") {
// log, send flowdock note but no `@` mention
robot.logger.info(
`This meeting looks like it never started: ${meeting.id}`,
)
res.send(
`Looks like you didn't need this meeting, after all. If do you still need a zoom, please start a new one :)`,
)
} else {
// otherwise, send flowdock prompt
res.send(`@${res.message.user.name} Please post call notes!`)
robot.logger.info(
`Stopped watching, meeting ended: ${meeting.id}`,
)
}
})
.catch(err => {
robot.logger.error(
`Failed to fetch meeting details for ${meeting.id}. ERR:`,
util.inspect(err),
)
// We assume the meeting still happened, so reply (but without `@`)
res.send(
`Something went wrong watching the meeting; don't forget to post meeting notes when your call ends!`,
)
})
})
})
}