diff --git a/src/chromium-review.ts b/src/chromium-review.ts index b75dad8..52e76c7 100644 --- a/src/chromium-review.ts +++ b/src/chromium-review.ts @@ -1,12 +1,7 @@ -import { WebClient } from '@slack/web-api'; +import { MessageAttachment } from '@slack/bolt'; import fetch from 'node-fetch'; -export async function handleChromiumReviewUnfurl( - url: string, - message_ts: string, - channel: string, - client: WebClient, -) { +export async function handleChromiumReviewUnfurl(url: string): Promise { const match = /^https:\/\/chromium-review\.googlesource\.com\/c\/([a-z0-9]+)\/([a-z0-9]+)\/\+\/([0-9]+)/g.exec( url, ); @@ -29,39 +24,29 @@ export async function handleChromiumReviewUnfurl( ? message.substr(subject.length + 1).trim() : message; - const unfurl = await client.chat.unfurl({ - channel, - ts: message_ts, - unfurls: { - [url]: { - color: '#4D394B', - author_name: owner.name, - author_icon: - owner.avatars && owner.avatars.length - ? owner.avatars[owner.avatars.length - 1].url - : ':void', - author_link: `https://chromium-review.googlesource.com/q/author:${encodeURIComponent( - owner.email, - )}`, - fallback: `[${niceRepo}] #${cl} ${subject}`, - title: `#${cl} ${subject}`, - title_link: url, - footer_icon: 'https://chromium-review.googlesource.com/favicon.ico', - text: messageWithoutSubject, - footer: ``, - ts: `${new Date(date).getTime()}`, - // TODO: Labels? CQ status? - // fields: [{ + return { + color: '#4D394B', + author_name: owner.name, + author_icon: + owner.avatars && owner.avatars.length + ? owner.avatars[owner.avatars.length - 1].url + : ':void', + author_link: `https://chromium-review.googlesource.com/q/author:${encodeURIComponent( + owner.email, + )}`, + fallback: `[${niceRepo}] #${cl} ${subject}`, + title: `#${cl} ${subject}`, + title_link: url, + footer_icon: 'https://chromium-review.googlesource.com/favicon.ico', + text: messageWithoutSubject, + footer: ``, + ts: `${new Date(date).getTime()}`, + // TODO: Labels? CQ status? + // fields: [{ - // }] - }, - }, - }); - if (unfurl.status !== 200 || !unfurl.ok) { - console.error('Failed to unfurl', unfurl); - } - return true; + // }] + }; } - return false; + return null; } diff --git a/src/crbug.ts b/src/crbug.ts index 2142cf4..e44a328 100644 --- a/src/crbug.ts +++ b/src/crbug.ts @@ -1,4 +1,4 @@ -import { WebClient } from '@slack/web-api'; +import { MessageAttachment } from '@slack/bolt'; import fetch from 'node-fetch'; import { Policy, ConstantBackoff } from 'cockatiel'; @@ -83,14 +83,9 @@ const notNull = (arr: (T | null)[]) => { return arr.filter(Boolean) as T[]; }; -export async function handleChromiumBugUnfurl( - url: string, - message_ts: string, - channel: string, - client: WebClient, -) { +export async function handleChromiumBugUnfurl(url: string): Promise { const bugIdentifier = parseBugIdentifier(url); - if (!bugIdentifier) return false; + if (!bugIdentifier) return null; const headers = await getMonorailHeaders(); const monorailQuery = JSON.stringify({ @@ -109,73 +104,62 @@ export async function handleChromiumBugUnfurl( headers, body: monorailQuery, }); - if ((await response).status !== 200) return false; - if ((await commentResponse).status !== 200) return false; + if ((await response).status !== 200) return null; + if ((await commentResponse).status !== 200) return null; const { issue }: { issue: MonorailIssue } = JSON.parse((await (await response).text()).slice(4)); const { comments }: { comments: MonorailComments } = JSON.parse( (await (await commentResponse).text()).slice(4), ); - const unfurl = await client.chat.unfurl({ - channel, - ts: message_ts, - unfurls: { - [url]: { - color: issue.statusRef.meansOpen ? '#36B37E' : '#FF5630', - author_name: issue.reporterRef.displayName, - // author_icon: owner.avatars && owner.avatars.length ? owner.avatars[owner.avatars.length - 1].url : ':void', - author_link: `https://bugs.chromium.org/u/${issue.reporterRef.userId}/`, - fallback: `[${issue.projectName}] #${issue.localId} ${issue.summary}`, - title: `#${issue.localId} ${issue.summary}`, - title_link: url, - footer_icon: 'https://bugs.chromium.org/static/images/monorail.ico', - text: comments[0].content, - footer: ``, - ts: `${issue.openedTimestamp * 1000}`, - fields: notNull([ - issue.componentRefs && issue.componentRefs.length - ? { - title: 'Components', - value: issue.componentRefs - .map( - (ref) => - `• /g, '→')}\`>`, - ) - .join('\n'), - short: true, - } - : null, - issue.labelRefs && issue.labelRefs.length - ? { - title: 'Labels', - value: issue.labelRefs - .map( - (ref) => - `• `, - ) - .join('\n'), - short: true, - } - : null, - { - title: 'Comments', - value: `${comments.length}`, + return { + color: issue.statusRef.meansOpen ? '#36B37E' : '#FF5630', + author_name: issue.reporterRef.displayName, + author_link: `https://bugs.chromium.org/u/${issue.reporterRef.userId}/`, + fallback: `[${issue.projectName}] #${issue.localId} ${issue.summary}`, + title: `#${issue.localId} ${issue.summary}`, + title_link: url, + footer_icon: 'https://bugs.chromium.org/static/images/monorail.ico', + text: comments[0].content, + footer: ``, + ts: `${issue.openedTimestamp * 1000}`, + fields: notNull([ + issue.componentRefs && issue.componentRefs.length + ? { + title: 'Components', + value: issue.componentRefs + .map( + (ref) => + `• /g, + '→', + )}\`>`, + ) + .join('\n'), short: true, - }, - ]), + } + : null, + issue.labelRefs && issue.labelRefs.length + ? { + title: 'Labels', + value: issue.labelRefs + .map( + (ref) => + `• `, + ) + .join('\n'), + short: true, + } + : null, + { + title: 'Comments', + value: `${comments.length}`, + short: true, }, - }, - }); - if (unfurl.status !== 200 || !unfurl.ok) { - console.error('Failed to unfurl', unfurl); - } - - return true; + ]), + }; } diff --git a/src/crsource.ts b/src/crsource.ts index 9528b13..02cfbb9 100644 --- a/src/crsource.ts +++ b/src/crsource.ts @@ -1,4 +1,4 @@ -import { WebClient } from '@slack/web-api'; +import { MessageAttachment } from '@slack/bolt'; import fetch from 'node-fetch'; type GrimoireMeta = { @@ -117,19 +117,14 @@ function removeOverIndent(contents: string): string { return lines.map((l) => l.slice(minIndent)).join('\n'); } -export async function handleChromiumSourceUnfurl( - url: string, - message_ts: string, - channel: string, - client: WebClient, -) { +export async function handleChromiumSourceUnfurl(url: string): Promise { const parsed = new URL(url); - if (parsed.hostname !== 'source.chromium.org') return false; + if (parsed.hostname !== 'source.chromium.org') return null; const match = /^https:\/\/source\.chromium\.org\/([a-z0-9]+)\/([a-z0-9]+)\/([a-z0-9]+)\/\+\/([a-z0-9]+):([^;]+)(?:;l=([0-9]+(?:-[0-9]+)?))?/.exec( url, ); - if (!match) return false; + if (!match) return null; const [, parent, project, projectKey, branch, fileName, lineRange] = match; @@ -148,25 +143,14 @@ export async function handleChromiumSourceUnfurl( } } - const unfurl = await client.chat.unfurl({ - channel, - ts: message_ts, - unfurls: { - [url]: { - color: '#00B8D9', - fallback: `[${project}/${projectKey}] ${fileName}`, - title: fileName, - title_link: url, - footer_icon: 'https://www.gstatic.com/devopsconsole/images/oss/favicons/oss-96x96.png', - text: `\`\`\`\n${maybeTruncate(contents)}\n\`\`\``, - footer: ``, - mrkdwn_in: ['text'], - }, - }, - }); - if (unfurl.status !== 200 || !unfurl.ok) { - console.error('Failed to unfurl', unfurl); - } - - return true; + return { + color: '#00B8D9', + fallback: `[${project}/${projectKey}] ${fileName}`, + title: fileName, + title_link: url, + footer_icon: 'https://www.gstatic.com/devopsconsole/images/oss/favicons/oss-96x96.png', + text: `\`\`\`\n${maybeTruncate(contents)}\n\`\`\``, + footer: ``, + mrkdwn_in: ['text'], + }; } diff --git a/src/index.ts b/src/index.ts index 48f9948..68fae80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,15 @@ -import { App } from '@slack/bolt'; +import { App, MessageAttachment } from '@slack/bolt'; import { handleChromiumReviewUnfurl } from './chromium-review'; import { handleChromiumBugUnfurl } from './crbug'; import { handleChromiumSourceUnfurl } from './crsource'; const app = new App({ - token: process.env.SLACK_TOKEN, + authorize: async () => ({ + botToken: process.env.SLACK_TOKEN, + botId: process.env.SLACK_BOT_ID, + }), signingSecret: process.env.SLACK_SIGNING_SECRET, - botId: process.env.SLACK_BOT_ID, }); app.event('link_shared', async ({ client, body }) => { @@ -16,13 +18,36 @@ app.event('link_shared', async ({ client, body }) => { // Do not unfurl if there are more than three links, we're nice like that if (links.length > 3) return; - for (const { url } of links) { - if (await handleChromiumReviewUnfurl(url, message_ts, channel, client)) return; - if (await handleChromiumBugUnfurl(url, message_ts, channel, client)) return; - if (await handleChromiumSourceUnfurl(url, message_ts, channel, client)) return; + const linkUnfurls: Record = {}; + + // Unfurl all the links at the same time + await Promise.all( + links.map(async ({ url }) => { + const unfurls = await Promise.all([ + handleChromiumReviewUnfurl(url), + handleChromiumBugUnfurl(url), + handleChromiumSourceUnfurl(url), + ]); + const validUnfurls = unfurls.filter((unfurl) => !!unfurl) as MessageAttachment[]; + if (validUnfurls.length > 1) { + console.error('More than one unfurler responded to a given URL', { url }); + } else if (validUnfurls.length === 1) { + linkUnfurls[url] = validUnfurls[0]; + } + }), + ); + + const unfurl = await client.chat.unfurl({ + channel, + ts: message_ts, + unfurls: linkUnfurls, + }); + + if (!unfurl.ok) { + console.error('Failed to unfurl', { unfurl, linkUnfurls }); } }); -app.start(process.env.PORT ? parseInt(process.env.PORT, 10) : 8080).then(() => { +app.start(process.env.PORT ? parseInt(process.env.PORT, 10) : 8080).then((server) => { console.log('Chromium Unfurler listening...'); });