feat: added additional description formatting (#23)

remove: removed HTML comments added by the 'Generate release notes' button
feat: description trimming
style: reduce consecutive whitespace/newlines into a minimum of 2 to allow separation in paragraphs
style: parse common Github URLs to more appropriate display
feat: added max_description option
feat: added reduce_headings option
This commit is contained in:
Ryan Hullah
2023-12-11 11:18:02 -05:00
committed by GitHub
parent 254bf79196
commit 8ca9da2ca8
3 changed files with 121 additions and 34 deletions
+4 -2
View File
@@ -8,8 +8,8 @@ A GitHub action that parses a GitHub release and posts it to a Discord channel a
## Configuration ## Configuration
| Variable | Required | Default | Description | | Variable | Required | Default | Description |
|-----------------|----------|----------------------------------------------------------------------------------------------------------------|--------------------------------------------| |-----------------|----------|-------------------------------------------------------------------------------------------------------|-------------------------------------------------|
| webhook_url | ✔ | | Discord's webhook url. Use GH repo secrets.| | webhook_url | ✔ | | Discord's webhook url. Use GH repo secrets. |
| color | ❌ | "2105893" | Decimal color value for embed. | | color | ❌ | "2105893" | Decimal color value for embed. |
| username | ❌ | | String username for webhook. | | username | ❌ | | String username for webhook. |
| avatar_url | ❌ | | String url to webhook avatar picture. | | avatar_url | ❌ | | String url to webhook avatar picture. |
@@ -17,6 +17,8 @@ A GitHub action that parses a GitHub release and posts it to a Discord channel a
| footer_title | ❌ | | String title for the webhook footer. | | footer_title | ❌ | | String title for the webhook footer. |
| footer_icon_url | ❌ | | String url for the webhook footer picture. | | footer_icon_url | ❌ | | String url for the webhook footer picture. |
| footer_timestamp| ❌ | | Boolean to enable footer timestamp. | | footer_timestamp| ❌ | | Boolean to enable footer timestamp. |
| max_description | ❌ | "4096" | Max length for the description. |
| reduce_headings | ❌ | false | Converts H3 to bold, h2 to bold & underline. |
## Example Usage ## Example Usage
+8
View File
@@ -27,6 +27,14 @@ inputs:
footer_timestamp: footer_timestamp:
description: Timestamp for the footer. description: Timestamp for the footer.
required: false required: false
max_description:
description: Max length for the description.
required: false
default: '4096'
reduce_headings:
description: Converts H3 to bold, h2 to bold & underline.
required: false
default: 'false'
runs: runs:
using: 'node16' using: 'node16'
main: 'index.js' main: 'index.js'
+100 -23
View File
@@ -4,45 +4,119 @@ import fetch from 'node-fetch';
/** /**
* Stylizes a markdown body into an appropriate embed message style. * Stylizes a markdown body into an appropriate embed message style.
* H3s converted to bold and underlined. * Remove HTML comments (commonly added by 'Generate release notes' button)
* H2s converted to bold. * Better URL linking for common Github links: PRs, Issues, Compare
* Redundant whitespace and newlines removed. * Redundant whitespace and newlines removed, keeping at max 2 to provide space between paragraphs
* @param description * Trim leading/trailing whitespace
* @returns {*} * If reduce_headings:
* H3s converted to bold and underlined
* H2s converted to bold
* @param {string} description
*/ */
const formatDescription = (description) => { const formatDescription = (description) => {
return description let edit = description
.replace(/### (.*?)\n/g,function (substring) { .replace(/<!--.*?-->/gs, '')
const newString = substring.slice(4).replace(/(\r\n|\n|\r)/gm, "") .replace(
return `**__${newString}__**` new RegExp(
"https://github.com/(.+)/(.+)/(issues|pull|commit|compare)/(\\S+)",
"g"
),
(match, user, repo, type, id) => {
return `[${getTypePrefix(type) + id}](${match})`
}
)
.replace(/\n\s*\n/g, (ws) => {
const nlCount = (ws.match(/\n/g) || []).length
return nlCount >= 2 ? '\n\n' : '\n'
}) })
.replace(/## (.*?)\n/g,function (substring) { .trim()
const newString = substring.slice(3).replace(/(\r\n|\n|\r)/gm, "")
return `**${newString}**` if (core.getBooleanInput('reduce_headings')) {
}) edit = edit
.replace(/\n\s*\n/g, '\n') .replace(/^###\s+(.+)$/gm, '**__$1__**')
.replace(/^##\s+(.+)$/gm, '**$1**')
}
return edit
}
/**
* Get a prefix to use for Github link display
* @param {'issues' | 'pull' | 'commit' | 'compare'} type
*/
function getTypePrefix (type) {
switch (type) {
case 'issues':
return 'Issue #'
case 'pull':
return 'PR #'
case 'commit':
return 'Commit #'
case 'compare':
return ''
default:
return '#'
}
}
/**
* Gets the max description length if set to a valid number,
* otherwise the default of 4096
*/
function getMaxDescription () {
try {
const max = core.getInput('max_description')
if (typeof max === 'string' && max.length) {
// 4096 is max for Embed Description
// https://discord.com/developers/docs/resources/channel#embed-object-embed-limits
return Math.min(parseInt(max, 10), 4096)
}
} catch (err) {
core.warning(`max_description not a valid number: ${err}`)
}
return 4096
} }
/** /**
* Get the context of the action, returns a GitHub Release payload. * Get the context of the action, returns a GitHub Release payload.
* @returns {Promise<{html_url, body: (*|string), name: string}>}
*/ */
async function getContext () { function getContext () {
const payload = github.context.payload; const payload = github.context.payload;
return { return {
body: payload.release.body.length < 1500 body: payload.release.body,
? payload.release.body
: payload.release.body.substring(0, 1500) + ` ([...](${payload.release.html_url}))`,
name: payload.release.name, name: payload.release.name,
html_url: payload.release.html_url html_url: payload.release.html_url
} }
} }
/**
*
* @param {string} str
* @param {number} maxLength
* @param {string=} url
*/
function limit(str, maxLength, url) {
if (str.length <= maxLength)
return str
let replacement = '…'
if (url) {
replacement = `([${replacement}](${url}))`
}
maxLength = maxLength - replacement.length
str = str.substring(0, maxLength)
const lastWhitespace = str.search(/[^\s]*$/)
if (lastWhitespace > -1) {
str = str.substring(0, lastWhitespace)
}
return str + replacement
}
/** /**
* Handles the action. * Handles the action.
* Get inputs, creates a stylized response webhook, and sends it to the channel. * Get inputs, creates a stylized response webhook, and sends it to the channel.
* @returns {Promise<void>}
*/ */
async function run () { async function run () {
const webhookUrl = core.getInput('webhook_url'); const webhookUrl = core.getInput('webhook_url');
@@ -56,22 +130,25 @@ async function run () {
if (!webhookUrl) return core.setFailed('webhook_url not set. Please set it.'); if (!webhookUrl) return core.setFailed('webhook_url not set. Please set it.');
const {body, html_url, name} = await getContext(); const {body, html_url, name} = getContext();
const description = formatDescription(body); const description = formatDescription(body);
let embedMsg = { let embedMsg = {
title: name, title: limit(name, 256),
url: html_url, url: html_url,
color: color, color: color,
description: description, description: description,
footer: {} footer: {}
} }
if (footerTitle != '') embedMsg.footer.text = footerTitle; if (footerTitle != '') embedMsg.footer.text = limit(footerTitle, 2048);
if (footerIconUrl != '') embedMsg.footer.icon_url = footerIconUrl; if (footerIconUrl != '') embedMsg.footer.icon_url = footerIconUrl;
if (footerTimestamp == 'true') embedMsg.timestamp = new Date().toISOString(); if (footerTimestamp == 'true') embedMsg.timestamp = new Date().toISOString();
let embedSize = embedMsg.title.length + (embedMsg.footer?.text?.length ?? 0)
embedMsg.description = limit(embedMsg.description, Math.min(getMaxDescription(), 6000 - embedSize), embedMsg.url)
let requestBody = { let requestBody = {
embeds: [embedMsg] embeds: [embedMsg]
} }