From 5cc0116163c702312481a9b4bc59896cc0decebe Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 27 Feb 2020 15:48:55 +0100 Subject: [PATCH 01/11] Skeleton of the zoom integration using the new zoom API --- integrations/zoom.js | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 integrations/zoom.js diff --git a/integrations/zoom.js b/integrations/zoom.js new file mode 100644 index 0000000..cf262af --- /dev/null +++ b/integrations/zoom.js @@ -0,0 +1,49 @@ +const fetch = require('node-fetch'); + +module.exports = async function(robot, kredits) { + + function createContributionFor (participant, meeting) { + // TODO + } + + function getContributorByZoomUserId(userId) { + return Contributor.findByAccount({ zoomId: userId }); + } + + function getMeetingParticipants(meetingUUID) { + fetch(`https://api.zoom.us/v2/past_meetings/${meetingUUID}/participants`) + .then(response => response.json()) + .then(json => json.participants) + } + + function handleZoomMeetingEnded(data) { + const meetingUUID = data.uuid; + const topic = data.topic; + const duration = data.duration; + + const meeting = { + // TODO + } + + if (duration < 15) { + robot.logger.info('[hubot-kredits] ignoring short calls'); + return; + } + const participants = await getMeetingParticipants(meetingUUID); + participants.forEach(p => { + createContributionFor(p, meeting); + }); + } + + robot.router.post('/incoming/kredits/zoom'+process.env.KREDITS_WEBHOOK_TOKEN), (req, res) => { + let data = req.body; + const eventName = data.event; + const payload = data.payload; + + if (eventName === 'meeting.ended') { + handleZoomMeetingEnded(payload); + } + + res.sendStatus(200); + }) +} From 70ea031b31ef5f8111824cb378065cbddaebb5a3 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Wed, 15 Apr 2020 21:29:21 +0200 Subject: [PATCH 02/11] Zoom integration using the JWT API --- integrations/zoom.js | 74 +++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index cf262af..3603cab 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -1,47 +1,85 @@ const fetch = require('node-fetch'); module.exports = async function(robot, kredits) { + const Contributor = kredits.Contributor; + const Contribution = kredits.Contribution; - function createContributionFor (participant, meeting) { - // TODO + const kreditsContributionAmount = 500; + const kreditsContributionKind = 'community'; + + const zoomAccessToken = process.env.KREDITS_ZOOM_JWT; + + const walletTransactionCount = await kredits.provider.getTransactionCount(kredits.signer.address); + let nonce = walletTransactionCount; + + function createContributionFor(participant, meeting) { + const displayName = participant.name; + + return getContributorByZoomDisplayName(displayName) + .then(contributor => { + let contribution = { + contributorId: contributor.id, + contributorIpfsHash: contributor.ipfsHash, + amount: kreditsContributionAmount, + kind: kreditsContributionKind, + description: `Team meeting: ${meeting.topic}`, + date: meeting.end_time.split('T')[0], + time: meeting.end_time.split('T')[1] + } + + return Contribution.addContribution(contribution, { nonce: nonce++ }) + .catch(error => { + robot.logger.error(`[hubot-kredits] Adding contribution failed:`, error); + }); + }) } - function getContributorByZoomUserId(userId) { - return Contributor.findByAccount({ zoomId: userId }); + function getContributorByZoomDisplayName(displayName) { + return Contributor.findByAccount({ site: 'zoom.us', username: displayName }); + } + + function request(path) { + return fetch( + `https://api.zoom.us/v2${path}`, + {headers: {authorization: `Bearer ${zoomAccessToken}`}} + ); } function getMeetingParticipants(meetingUUID) { - fetch(`https://api.zoom.us/v2/past_meetings/${meetingUUID}/participants`) + return request(`/past_meetings/${meetingUUID}/participants`) .then(response => response.json()) .then(json => json.participants) } - function handleZoomMeetingEnded(data) { - const meetingUUID = data.uuid; - const topic = data.topic; - const duration = data.duration; + function getMeetingDetails(meetingUUID) { + return request(`/past_meetings/${meetingUUID}`) + .then(r => r.json()); + } - const meeting = { - // TODO - } + async function handleZoomMeetingEnded(data) { + const meetingDetails = await getMeetingDetails(data.uuid); + const participants = await getMeetingParticipants(data.uuid); - if (duration < 15) { - robot.logger.info('[hubot-kredits] ignoring short calls'); + if (meetingDetails.duration < 15 || meetingDetails.participants_count < 3) { + robot.logger.info(`[hubot-kredits] ignoring meeting: uuid:${data.uuid} duration:${meetingDetails.duration} participants_count:${meetingDetails.participants_count}`); return; } - const participants = await getMeetingParticipants(meetingUUID); participants.forEach(p => { - createContributionFor(p, meeting); + createContributionFor(p, meetingDetails) + .then(tx => { + robot.logger.info(`[hubot-kredits] contribution created: ${tx.hash}`); + }) }); } - robot.router.post('/incoming/kredits/zoom'+process.env.KREDITS_WEBHOOK_TOKEN), (req, res) => { + robot.router.post('/incoming/kredits/zoom'+process.env.KREDITS_WEBHOOK_TOKEN, (req, res) => { let data = req.body; const eventName = data.event; const payload = data.payload; + const object = payload.object; if (eventName === 'meeting.ended') { - handleZoomMeetingEnded(payload); + handleZoomMeetingEnded(object); } res.sendStatus(200); From 110c4384e0b537c7fa3ae2b1f4960eb64dbb2643 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Wed, 15 Apr 2020 21:51:02 +0200 Subject: [PATCH 03/11] Autoload zoom integration --- index.js | 2 ++ integrations/zoom.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index e8f7d6b..4b5d2ef 100644 --- a/index.js +++ b/index.js @@ -152,6 +152,8 @@ module.exports = async function(robot) { require('./integrations/github')(robot, kredits); require('./integrations/gitea')(robot, kredits); + require('./integrations/zoom')(robot, kredits); + if (typeof process.env.KREDITS_MEDIAWIKI_URL !== 'undefined') { require('./integrations/mediawiki')(robot, kredits); } diff --git a/integrations/zoom.js b/integrations/zoom.js index 3603cab..b0a27f3 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -72,7 +72,7 @@ module.exports = async function(robot, kredits) { }); } - robot.router.post('/incoming/kredits/zoom'+process.env.KREDITS_WEBHOOK_TOKEN, (req, res) => { + robot.router.post('/incoming/kredits/zoom/'+process.env.KREDITS_WEBHOOK_TOKEN, (req, res) => { let data = req.body; const eventName = data.event; const payload = data.payload; From c4ef8de018d61bdd7aa11bc631735cdd47b0bd32 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 16 Apr 2020 12:08:06 +0200 Subject: [PATCH 04/11] Nicer log messages Co-Authored-By: Sebastian Kippe --- integrations/zoom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index b0a27f3..8ef68d5 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -61,13 +61,13 @@ module.exports = async function(robot, kredits) { const participants = await getMeetingParticipants(data.uuid); if (meetingDetails.duration < 15 || meetingDetails.participants_count < 3) { - robot.logger.info(`[hubot-kredits] ignoring meeting: uuid:${data.uuid} duration:${meetingDetails.duration} participants_count:${meetingDetails.participants_count}`); + robot.logger.info(`[hubot-kredits] Ignoring zoom call ${data.uuid} (duration: ${meetingDetails.duration}, participants_count: ${meetingDetails.participants_count})`); return; } participants.forEach(p => { createContributionFor(p, meetingDetails) .then(tx => { - robot.logger.info(`[hubot-kredits] contribution created: ${tx.hash}`); + robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); }) }); } From 8f961bb102553b8df1f34c5ed303544ff0a88e6f Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 16 Apr 2020 12:08:50 +0200 Subject: [PATCH 05/11] Apply suggestions from code review Co-Authored-By: Sebastian Kippe --- integrations/zoom.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index 8ef68d5..3cc9b98 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -1,8 +1,7 @@ const fetch = require('node-fetch'); module.exports = async function(robot, kredits) { - const Contributor = kredits.Contributor; - const Contribution = kredits.Contribution; + const { Contributor, Contribution } = kredits; const kreditsContributionAmount = 500; const kreditsContributionKind = 'community'; @@ -17,7 +16,7 @@ module.exports = async function(robot, kredits) { return getContributorByZoomDisplayName(displayName) .then(contributor => { - let contribution = { + const contribution = { contributorId: contributor.id, contributorIpfsHash: contributor.ipfsHash, amount: kreditsContributionAmount, From 164782bd25ac1a5b3c75362ef80d2e132b99671d Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Thu, 16 Apr 2020 12:18:52 +0200 Subject: [PATCH 06/11] Only load Zoom integration when JWT configured --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4b5d2ef..61cd980 100644 --- a/index.js +++ b/index.js @@ -152,7 +152,9 @@ module.exports = async function(robot) { require('./integrations/github')(robot, kredits); require('./integrations/gitea')(robot, kredits); - require('./integrations/zoom')(robot, kredits); + if (typeof process.env.KREDITS_ZOOM_JWT !== 'undefined') { + require('./integrations/zoom')(robot, kredits); + } if (typeof process.env.KREDITS_MEDIAWIKI_URL !== 'undefined') { require('./integrations/mediawiki')(robot, kredits); From e7f8723f6e99d6b462297eb1f098f3cb9dbd3a3b Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 16 Apr 2020 16:32:39 +0200 Subject: [PATCH 07/11] Make sure zoom participants are unique to make sure we only create one contribution per participants --- integrations/zoom.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index 3cc9b98..bacc340 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -11,8 +11,7 @@ module.exports = async function(robot, kredits) { const walletTransactionCount = await kredits.provider.getTransactionCount(kredits.signer.address); let nonce = walletTransactionCount; - function createContributionFor(participant, meeting) { - const displayName = participant.name; + function createContributionFor(displayName, meeting) { return getContributorByZoomDisplayName(displayName) .then(contributor => { @@ -21,7 +20,7 @@ module.exports = async function(robot, kredits) { contributorIpfsHash: contributor.ipfsHash, amount: kreditsContributionAmount, kind: kreditsContributionKind, - description: `Team meeting: ${meeting.topic}`, + description: 'Team/Community Call', date: meeting.end_time.split('T')[0], time: meeting.end_time.split('T')[1] } @@ -63,12 +62,13 @@ module.exports = async function(robot, kredits) { robot.logger.info(`[hubot-kredits] Ignoring zoom call ${data.uuid} (duration: ${meetingDetails.duration}, participants_count: ${meetingDetails.participants_count})`); return; } - participants.forEach(p => { - createContributionFor(p, meetingDetails) + const names = Array.from(new Set(participants.map(p => p.name))); + for(const displayName of names) { + await createContributionFor(displayName, meetingDetails) .then(tx => { robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); - }) - }); + }); + }; } robot.router.post('/incoming/kredits/zoom/'+process.env.KREDITS_WEBHOOK_TOKEN, (req, res) => { From 98ff61ab0a6365c8e02b2461ff177c55e103cfc6 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 16 Apr 2020 17:03:03 +0200 Subject: [PATCH 08/11] Add handling of missing zoom profiles --- integrations/zoom.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index bacc340..b051bbb 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -1,6 +1,11 @@ const fetch = require('node-fetch'); module.exports = async function(robot, kredits) { + + function messageRoom(message) { + robot.messageRoom(process.env.KREDITS_ROOM, message); + } + const { Contributor, Contribution } = kredits; const kreditsContributionAmount = 500; @@ -15,6 +20,11 @@ module.exports = async function(robot, kredits) { return getContributorByZoomDisplayName(displayName) .then(contributor => { + if (!contributor) { + robot.logger.error(`[hubot-kredits] Contributor not found: Zoom display name: ${displayName}`); + messageRoom(`I tried to add a contribution for zoom user ${displayName}, but did not find a matchig contributor profile.`); + return; + } const contribution = { contributorId: contributor.id, contributorIpfsHash: contributor.ipfsHash, @@ -25,7 +35,7 @@ module.exports = async function(robot, kredits) { time: meeting.end_time.split('T')[1] } - return Contribution.addContribution(contribution, { nonce: nonce++ }) + return Contribution.add(contribution, { nonce: nonce++ }) .catch(error => { robot.logger.error(`[hubot-kredits] Adding contribution failed:`, error); }); @@ -66,7 +76,9 @@ module.exports = async function(robot, kredits) { for(const displayName of names) { await createContributionFor(displayName, meetingDetails) .then(tx => { - robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); + if (tx) { + robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); + } }); }; } From 7f653f23ce5a450e35c8a7e8f6f16f5c3810ad81 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 16 Apr 2020 17:08:26 +0200 Subject: [PATCH 09/11] typo --- integrations/zoom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index b051bbb..a5c2cb0 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -22,7 +22,7 @@ module.exports = async function(robot, kredits) { .then(contributor => { if (!contributor) { robot.logger.error(`[hubot-kredits] Contributor not found: Zoom display name: ${displayName}`); - messageRoom(`I tried to add a contribution for zoom user ${displayName}, but did not find a matchig contributor profile.`); + messageRoom(`I tried to add a contribution for zoom user ${displayName}, but did not find a matching contributor profile.`); return; } const contribution = { From 60ed69746020ae37b9669fed75e5d178186439b3 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 16 Apr 2020 17:17:40 +0200 Subject: [PATCH 10/11] Add comment when tx is undefined We createContributionFor() simply returns if no contributor is found. --- integrations/zoom.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index a5c2cb0..7968ff3 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -74,12 +74,11 @@ module.exports = async function(robot, kredits) { } const names = Array.from(new Set(participants.map(p => p.name))); for(const displayName of names) { - await createContributionFor(displayName, meetingDetails) - .then(tx => { - if (tx) { - robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); - } - }); + const tx = createContributionFor(displayName, meetingDetails) + // if the contributor is not found we do not get a transaction object here + if (tx) { + robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); + } }; } From 960dcb55de417d9862432af68014eea73c8ebc50 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Thu, 16 Apr 2020 17:33:53 +0200 Subject: [PATCH 11/11] Moar await --- integrations/zoom.js | 54 ++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/integrations/zoom.js b/integrations/zoom.js index 7968ff3..817ca31 100644 --- a/integrations/zoom.js +++ b/integrations/zoom.js @@ -16,30 +16,32 @@ module.exports = async function(robot, kredits) { const walletTransactionCount = await kredits.provider.getTransactionCount(kredits.signer.address); let nonce = walletTransactionCount; - function createContributionFor(displayName, meeting) { + async function createContributionFor (displayName, meeting) { + const contributor = await getContributorByZoomDisplayName(displayName); - return getContributorByZoomDisplayName(displayName) - .then(contributor => { - if (!contributor) { - robot.logger.error(`[hubot-kredits] Contributor not found: Zoom display name: ${displayName}`); - messageRoom(`I tried to add a contribution for zoom user ${displayName}, but did not find a matching contributor profile.`); - return; - } - const contribution = { - contributorId: contributor.id, - contributorIpfsHash: contributor.ipfsHash, - amount: kreditsContributionAmount, - kind: kreditsContributionKind, - description: 'Team/Community Call', - date: meeting.end_time.split('T')[0], - time: meeting.end_time.split('T')[1] - } + if (!contributor) { + robot.logger.info(`[hubot-kredits] Contributor not found: Zoom display name: ${displayName}`); + messageRoom(`I tried to add a contribution for zoom user ${displayName}, but did not find a matching contributor profile.`); + return Promise.resolve(); + } - return Contribution.add(contribution, { nonce: nonce++ }) - .catch(error => { - robot.logger.error(`[hubot-kredits] Adding contribution failed:`, error); - }); + const contribution = { + contributorId: contributor.id, + contributorIpfsHash: contributor.ipfsHash, + amount: kreditsContributionAmount, + kind: kreditsContributionKind, + description: 'Team/Community Call', + date: meeting.end_time.split('T')[0], + time: meeting.end_time.split('T')[1] + } + + return Contribution.add(contribution, { nonce: nonce++ }) + .then(tx => { + robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); }) + .catch(error => { + robot.logger.error(`[hubot-kredits] Adding contribution for Zoom call failed:`, error); + }); } function getContributorByZoomDisplayName(displayName) { @@ -72,13 +74,11 @@ module.exports = async function(robot, kredits) { robot.logger.info(`[hubot-kredits] Ignoring zoom call ${data.uuid} (duration: ${meetingDetails.duration}, participants_count: ${meetingDetails.participants_count})`); return; } + const names = Array.from(new Set(participants.map(p => p.name))); - for(const displayName of names) { - const tx = createContributionFor(displayName, meetingDetails) - // if the contributor is not found we do not get a transaction object here - if (tx) { - robot.logger.info(`[hubot-kredits] Contribution created: ${tx.hash}`); - } + + for (const displayName of names) { + await createContributionFor(displayName, meetingDetails); }; }