Skip to content

Commit 5ca1453

Browse files
committedOct 4, 2024
refactor: code cleanup
1 parent 305fe92 commit 5ca1453

File tree

1 file changed

+160
-135
lines changed

1 file changed

+160
-135
lines changed
 

‎index.js

+160-135
Original file line numberDiff line numberDiff line change
@@ -3,176 +3,201 @@ import github from '@actions/github';
33
import fetch from 'node-fetch';
44

55
/**
6-
* Stylizes a markdown body into an appropriate embed message style.
7-
* Remove Carriage Return character to reduce size
8-
* Remove HTML comments (commonly added by 'Generate release notes' button)
9-
* Better URL linking for common Github links: PRs, Issues, Compare
10-
* Redundant whitespace and newlines removed, keeping at max 2 to provide space between paragraphs
11-
* Trim leading/trailing whitespace
12-
* If reduce_headings:
13-
* H3s converted to bold and underlined
14-
* H2s converted to bold
15-
* @param {string} description
6+
* Removes carriage return characters.
7+
* @param {string} text The input text.
8+
* @returns {string} The text without carriage return characters.
169
*/
17-
const formatDescription = (description) => {
18-
let edit = description
19-
.replace(/\r/g, '')
20-
.replace(/<!--.*?-->/gs, '')
21-
.replace(
22-
new RegExp(
23-
"https://github.com/(.+)/(.+)/(issues|pull|commit|compare)/(\\S+)",
24-
"g"
25-
),
26-
(match, user, repo, type, id) => {
27-
return `[${getTypePrefix(type) + id}](${match})`
28-
}
29-
)
30-
.replace(/\n\s*\n/g, (ws) => {
31-
const nlCount = (ws.match(/\n/g) || []).length
32-
return nlCount >= 2 ? '\n\n' : '\n'
33-
})
34-
.replace(/@(\S+)/g, (match, name) => { return `[@${name}](https://github.com/${name})` })
35-
.trim()
10+
const removeCarriageReturn = (text) => text.replace(/\r/g, '');
3611

37-
if (core.getBooleanInput('reduce_headings')) {
38-
edit = edit
39-
.replace(/^###\s+(.+)$/gm, '**__$1__**')
40-
.replace(/^##\s+(.+)$/gm, '**$1**')
41-
}
12+
/**
13+
* Removes HTML comments.
14+
* @param {string} text The input text.
15+
* @returns {string} The text without HTML comments.
16+
*/
17+
const removeHTMLComments = (text) => text.replace(/<!--.*?-->/gs, '');
4218

43-
return edit
44-
}
19+
/**
20+
* Reduces redundant newlines and spaces.
21+
* Keeps a maximum of 2 newlines to provide spacing between paragraphs.
22+
* @param {string} text The input text.
23+
* @returns {string} The text with reduced newlines.
24+
*/
25+
const reduceNewlines = (text) => text.replace(/\n\s*\n/g, (ws) => {
26+
const nlCount = (ws.match(/\n/g) || []).length;
27+
return nlCount >= 2 ? '\n\n' : '\n';
28+
});
29+
30+
/**
31+
* Converts @mentions to GitHub profile links.
32+
* @param {string} text The input text.
33+
* @returns {string} The text with @mentions converted to links.
34+
*/
35+
const convertMentionsToLinks = (text) => text.replace(/@(\S+)/g, (match, name) => `[@${name}](https://github.com/${name})`);
4536

4637
/**
47-
* Get a prefix to use for Github link display
48-
* @param {'issues' | 'pull' | 'commit' | 'compare'} type
38+
* Reduces headings to a smaller format if 'reduce_headings' is enabled.
39+
* Converts H3 to bold+underline, H2 to bold.
40+
* @param {string} text The input text.
41+
* @returns {string} The text with reduced heading sizes.
4942
*/
50-
function getTypePrefix (type) {
51-
switch (type) {
52-
case 'issues':
53-
return 'Issue #'
54-
case 'pull':
55-
return 'PR #'
56-
case 'commit':
57-
return 'Commit #'
58-
case 'compare':
59-
return ''
60-
default:
61-
return '#'
43+
const reduceHeadings = (text) => text
44+
.replace(/^###\s+(.+)$/gm, '**__$1__**') // Convert H3 to bold + underline
45+
.replace(/^##\s+(.+)$/gm, '**$1**'); // Convert H2 to bold
46+
47+
/**
48+
* Stylizes a markdown body into an appropriate embed message style.
49+
* @param {string} description The description to format.
50+
* @returns {string} The formatted description.
51+
*/
52+
const formatDescription = (description) => {
53+
let edit = removeCarriageReturn(description);
54+
edit = removeHTMLComments(edit);
55+
edit = reduceNewlines(edit);
56+
edit = convertMentionsToLinks(edit);
57+
edit = edit.trim();
58+
59+
if (core.getBooleanInput('reduce_headings')) {
60+
edit = reduceHeadings(edit);
6261
}
63-
}
62+
63+
return edit;
64+
};
6465

6566
/**
66-
* Gets the max description length if set to a valid number,
67-
* otherwise the default of 4096
67+
* Gets the max description length, defaulting to 4096 if not set or invalid.
68+
* @returns {number} The max description length.
6869
*/
69-
function getMaxDescription () {
70+
const getMaxDescription = () => {
7071
try {
71-
const max = core.getInput('max_description')
72-
if (typeof max === 'string' && max.length) {
73-
// 4096 is max for Embed Description
74-
// https://discord.com/developers/docs/resources/channel#embed-object-embed-limits
75-
return Math.min(parseInt(max, 10), 4096)
72+
const max = core.getInput('max_description');
73+
if (max && !isNaN(max)) {
74+
return Math.min(parseInt(max, 10), 4096);
7675
}
7776
} catch (err) {
78-
core.warning(`max_description not a valid number: ${err}`)
77+
core.warning(`Invalid max_description: ${err}`);
7978
}
80-
return 4096
81-
}
79+
return 4096;
80+
};
8281

8382
/**
84-
* Get the context of the action, returns a GitHub Release payload.
83+
* Get the context of the action, returning a GitHub Release payload.
84+
* @returns {object} The context with release details.
8585
*/
86-
function getContext () {
87-
const payload = github.context.payload;
88-
86+
const getContext = () => {
87+
const { release } = github.context.payload;
8988
return {
90-
body: payload.release.body,
91-
name: payload.release.name,
92-
html_url: payload.release.html_url
93-
}
94-
}
89+
body: release.body,
90+
name: release.name,
91+
html_url: release.html_url
92+
};
93+
};
9594

9695
/**
97-
*
98-
* @param {string} str
99-
* @param {number} maxLength
100-
* @param {string} [url]
101-
* @param {boolean} [clipAtLine=false]
96+
* Limits the string to a maximum length, optionally adding a URL or clipping at a newline.
97+
* @param {string} str The string to limit.
98+
* @param {number} maxLength The maximum allowed length.
99+
* @param {string} [url] Optional URL for linking the truncated text.
100+
* @param {boolean} [clipAtLine=false] Whether to clip at the nearest newline.
101+
* @returns {string} The limited string.
102102
*/
103-
function limit(str, maxLength, url, clipAtLine) {
104-
clipAtLine ??= false
105-
if (str.length <= maxLength)
106-
return str
107-
let replacement = clipAtLine ? '\n…' : '…'
108-
if (url) {
109-
replacement = `${clipAtLine ? '\n' : ''}([…](${url}))`
110-
}
111-
maxLength = maxLength - replacement.length
112-
str = str.substring(0, maxLength)
103+
const limitString = (str, maxLength, url, clipAtLine = false) => {
104+
if (str.length <= maxLength) return str;
113105

114-
const lastNewline = str.search(new RegExp(`[^${clipAtLine ? '\n' : '\s'}]*$`))
106+
const replacement = url
107+
? `${clipAtLine ? '\n' : ''}([…](${url}))`
108+
: (clipAtLine ? '\n…' : '…');
109+
110+
maxLength -= replacement.length;
111+
str = str.substring(0, maxLength);
112+
113+
const lastNewline = str.search(new RegExp(`[^${clipAtLine ? '\n' : '\s'}]*$`));
115114
if (lastNewline > -1) {
116-
str = str.substring(0, lastNewline)
115+
str = str.substring(0, lastNewline);
117116
}
118117

119-
return str + replacement
120-
}
118+
return str + replacement;
119+
};
121120

122121
/**
123-
* Handles the action.
124-
* Get inputs, creates a stylized response webhook, and sends it to the channel.
122+
* Builds the embed message for the Discord webhook.
123+
* @param {string} name The title or name of the release.
124+
* @param {string} html_url The URL of the release.
125+
* @param {string} description The formatted description of the release.
126+
* @returns {object} The embed message to send in the webhook.
125127
*/
126-
async function run () {
127-
const webhookUrl = core.getInput('webhook_url');
128-
const color = core.getInput('color');
129-
const username = core.getInput('username');
130-
const avatarUrl = core.getInput('avatar_url');
131-
const content = core.getInput('content');
132-
const footerTitle = core.getInput('footer_title');
133-
const footerIconUrl = core.getInput('footer_icon_url');
134-
const footerTimestamp = core.getInput('footer_timestamp');
135-
136-
if (!webhookUrl) return core.setFailed('webhook_url not set. Please set it.');
128+
const buildEmbedMessage = (name, html_url, description) => {
129+
const embedMsg = {
130+
title: limitString(name, 256),
131+
url: html_url,
132+
color: core.getInput('color'),
133+
description: limitString(description, Math.min(getMaxDescription(), 6000 - name.length)),
134+
footer: {}
135+
};
137136

138-
const {body, html_url, name} = getContext();
137+
if (core.getInput('footer_title')) {
138+
embedMsg.footer.text = limitString(core.getInput('footer_title'), 2048);
139+
}
140+
if (core.getInput('footer_icon_url')) {
141+
embedMsg.footer.icon_url = core.getInput('footer_icon_url');
142+
}
143+
if (core.getInput('footer_timestamp') === 'true') {
144+
embedMsg.timestamp = new Date().toISOString();
145+
}
139146

140-
const description = formatDescription(body);
147+
return embedMsg;
148+
};
141149

142-
let embedMsg = {
143-
title: limit(name, 256),
144-
url: html_url,
145-
color: color,
146-
description: description,
147-
footer: {}
150+
/**
151+
* Sends the webhook request to Discord.
152+
* @param {string} webhookUrl The URL of the Discord webhook.
153+
* @param {object} requestBody The payload to send in the webhook.
154+
*/
155+
const sendWebhook = async (webhookUrl, requestBody) => {
156+
try {
157+
const response = await fetch(`${webhookUrl}?wait=true`, {
158+
method: 'POST',
159+
body: JSON.stringify(requestBody),
160+
headers: { 'Content-Type': 'application/json' }
161+
});
162+
const data = await response.json();
163+
core.info(JSON.stringify(data));
164+
} catch (err) {
165+
core.setFailed(err.message);
148166
}
167+
};
149168

150-
if (footerTitle != '') embedMsg.footer.text = limit(footerTitle, 2048);
151-
if (footerIconUrl != '') embedMsg.footer.icon_url = footerIconUrl;
152-
if (footerTimestamp == 'true') embedMsg.timestamp = new Date().toISOString();
169+
/**
170+
* Builds the request body for the Discord webhook.
171+
* @param {object} embedMsg The embed message to include in the request body.
172+
* @returns {object} The request body for the webhook.
173+
*/
174+
const buildRequestBody = (embedMsg) => {
175+
return {
176+
embeds: [embedMsg],
177+
...(core.getInput('username') && { username: core.getInput('username') }),
178+
...(core.getInput('avatar_url') && { avatar_url: core.getInput('avatar_url') }),
179+
...(core.getInput('content') && { content: core.getInput('content') })
180+
};
181+
};
153182

154-
let embedSize = embedMsg.title.length + (embedMsg.footer?.text?.length ?? 0)
155-
embedMsg.description = limit(embedMsg.description, Math.min(getMaxDescription(), 6000 - embedSize), embedMsg.url, true)
156183

157-
let requestBody = {
158-
embeds: [embedMsg]
159-
}
184+
/**
185+
* Main function to handle the action: get inputs, format the message, and send the webhook.
186+
*/
187+
const run = async () => {
188+
const webhookUrl = core.getInput('webhook_url');
189+
if (!webhookUrl) return core.setFailed('webhook_url not set.');
190+
191+
const { body, html_url, name } = getContext();
192+
const description = formatDescription(body);
193+
194+
const embedMsg = buildEmbedMessage(name, html_url, description);
195+
196+
const requestBody = buildRequestBody(embedMsg);
160197

161-
if (username != '') requestBody.username = username;
162-
if (avatarUrl != '') requestBody.avatar_url = avatarUrl;
163-
if (content != '') requestBody.content = content;
164-
165-
const url = `${webhookUrl}?wait=true`;
166-
fetch(url, {
167-
method: 'post',
168-
body: JSON.stringify(requestBody),
169-
headers: { 'Content-Type': 'application/json' }
170-
})
171-
.then(res => res.json())
172-
.then(data => core.info(JSON.stringify(data)))
173-
.catch(err => core.info(err))
174-
}
198+
await sendWebhook(webhookUrl, requestBody);
199+
};
175200

176201
run()
177-
.then(() => {core.info('Action completed successfully')})
178-
.catch(err => {core.setFailed(err.message)})
202+
.then(() => core.info('Action completed successfully'))
203+
.catch(err => core.setFailed(err.message));

0 commit comments

Comments
 (0)
Please sign in to comment.