From 7026eacb12a236f543394eb97bb32b81935a8d60 Mon Sep 17 00:00:00 2001 From: Heidi Date: Thu, 14 May 2026 21:09:18 +0100 Subject: [PATCH] fix --- README.md | 4 +- webhook.cjs | 113 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index fa3840a..223e440 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,8 @@ loads the updated listener: pm2 reload tssbot-webhook --update-env ``` -The webhook listener reads `.env` on startup. To send a Discord notification -whenever the listener starts or restarts, set: +The webhook listener reads `.env` on startup. To send Discord notifications for +listener restarts and GitHub push deploy start/success/failure events, set: ```sh DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... diff --git a/webhook.cjs b/webhook.cjs index a83ad79..0b73bc8 100644 --- a/webhook.cjs +++ b/webhook.cjs @@ -52,6 +52,28 @@ function json(res, status, body) { res.end(JSON.stringify(body)) } +function safeJsonParse(rawBody) { + try { + return JSON.parse(rawBody.toString('utf8')) + } catch { + return null + } +} + +function shortSha(value) { + return value ? String(value).slice(0, 7) : 'unknown' +} + +function branchName(ref) { + return String(ref || '').replace(/^refs\/heads\//, '') || 'unknown' +} + +function truncate(value, maxLength = 900) { + const text = String(value || '') + if (text.length <= maxLength) return text + return `${text.slice(0, maxLength - 3)}...` +} + function verifySignature(rawBody, signature) { if (!SECRET) return true if (!signature || !signature.startsWith('sha256=')) return false @@ -169,24 +191,77 @@ function postDiscordWebhook(payload) { function notifyDiscordRestart() { const startedAt = new Date() - postDiscordWebhook({ - username: 'tssbot webhook', - embeds: [ - { - title: 'Webhook listener restarted', - color: 0x00f2ff, - fields: [ - { name: 'Host', value: os.hostname(), inline: true }, - { name: 'Port', value: String(PORT), inline: true }, - { name: 'Restart targets', value: RESTART_TARGETS.join(', ') || 'none', inline: false }, - ], - timestamp: startedAt.toISOString(), - }, + sendDiscordEmbed({ + title: 'Webhook listener restarted', + color: 0x00f2ff, + fields: [ + { name: 'Host', value: os.hostname(), inline: true }, + { name: 'Port', value: String(PORT), inline: true }, + { name: 'Restart targets', value: RESTART_TARGETS.join(', ') || 'none', inline: false }, ], + timestamp: startedAt.toISOString(), }) } -async function deploy() { +function sendDiscordEmbed(embed) { + return postDiscordWebhook({ + username: 'tssbot webhook', + embeds: [embed], + }) +} + +function deployFields(push) { + const headCommit = push?.head_commit + const fields = [ + { name: 'Host', value: os.hostname(), inline: true }, + { name: 'Branch', value: branchName(push?.ref), inline: true }, + { name: 'Commit', value: shortSha(push?.after || headCommit?.id), inline: true }, + { name: 'Restart targets', value: RESTART_TARGETS.join(', ') || 'none', inline: false }, + ] + + if (push?.sender?.login) { + fields.push({ name: 'Sender', value: push.sender.login, inline: true }) + } + + if (headCommit?.message) { + fields.push({ name: 'Message', value: truncate(headCommit.message), inline: false }) + } + + return fields +} + +async function notifyDeployStarted(push) { + await sendDiscordEmbed({ + title: 'GitHub push deploy started', + color: 0xf4ee3e, + fields: deployFields(push), + timestamp: new Date().toISOString(), + }) +} + +async function notifyDeployCompleted(push) { + await sendDiscordEmbed({ + title: 'GitHub push deploy completed', + color: 0x00f2ff, + fields: deployFields(push), + timestamp: new Date().toISOString(), + }) +} + +async function notifyDeployFailed(push, error) { + await sendDiscordEmbed({ + title: 'GitHub push deploy failed', + color: 0xe82517, + fields: [ + ...deployFields(push), + { name: 'Error', value: truncate(error?.message || error), inline: false }, + ], + timestamp: new Date().toISOString(), + }) +} + +async function deploy(push) { + await notifyDeployStarted(push) await run('git', ['pull', '--ff-only']) await ensureBuildDependencies() await run('npm', ['run', 'build']) @@ -194,6 +269,8 @@ async function deploy() { for (const target of RESTART_TARGETS) { await run('pm2', ['reload', target, '--update-env']) } + + await notifyDeployCompleted(push) } http @@ -212,6 +289,7 @@ http req.on('data', (chunk) => chunks.push(chunk)) req.on('end', () => { const rawBody = Buffer.concat(chunks) + const push = safeJsonParse(rawBody) if (!verifySignature(rawBody, req.headers['x-hub-signature-256'])) { json(res, 401, { error: 'Invalid signature' }) @@ -231,9 +309,12 @@ http deploying = true json(res, 202, { accepted: true, restart_targets: RESTART_TARGETS }) - deploy() + deploy(push) .then(() => console.log('GitHub push deploy completed')) - .catch((error) => console.error('GitHub push deploy failed:', error)) + .catch((error) => { + console.error('GitHub push deploy failed:', error) + notifyDeployFailed(push, error) + }) .finally(() => { deploying = false })