From cd336618b50d4a3daeae88785abb34d103fb9816 Mon Sep 17 00:00:00 2001 From: Maufeat Date: Tue, 25 Nov 2025 22:54:18 +0100 Subject: [PATCH] watch releases and post them in the thread, pin message when create a thread --- data/pr_build_releases.json | 1 + src/client.js | 2 + src/functions/pr_build_watcher.js | 152 ++++++++++++++++++++++++++++++ src/functions/release_watcher.js | 12 ++- 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 data/pr_build_releases.json create mode 100644 src/functions/pr_build_watcher.js diff --git a/data/pr_build_releases.json b/data/pr_build_releases.json new file mode 100644 index 0000000..60b0742 --- /dev/null +++ b/data/pr_build_releases.json @@ -0,0 +1 @@ +[] diff --git a/src/client.js b/src/client.js index 29a5913..f686424 100644 --- a/src/client.js +++ b/src/client.js @@ -4,6 +4,7 @@ import { Client, GatewayIntentBits, Collection, Events } from 'discord.js'; import parseLog from './functions/log_parser.js'; import initializeReleaseWatcher from './functions/release_watcher.js'; import initializeDescriptionWatcher from './functions/description_watcher.js'; +import initializePRBuildWatcher from './functions/pr_build_watcher.js'; import { commands } from './functions/commands.js'; const { DISCORD_TOKEN } = process.env; @@ -32,6 +33,7 @@ client.once('ready', () => { // Release Watcher disabled for now, after the RAID have to setup again initializeReleaseWatcher(client); initializeDescriptionWatcher(client); + initializePRBuildWatcher(client); }); client.on('messageCreate', parseLog); diff --git a/src/functions/pr_build_watcher.js b/src/functions/pr_build_watcher.js new file mode 100644 index 0000000..10d2c0b --- /dev/null +++ b/src/functions/pr_build_watcher.js @@ -0,0 +1,152 @@ +import fetch from 'node-fetch'; +import { ChannelType, EmbedBuilder } from 'discord.js'; +import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +//forgot to store releases already posted, incase of a restart don't report +const TRACKED_RELEASES_FILE = join(__dirname, '../../data/pr_build_releases.json'); + +const { + CHANNEL_ID, + GITHUB_REPO: REPO = 'Eden-CI/PR', + GITHUB_TOKEN, + POLL_INTERVAL_MS, +} = process.env; + +const POLL_INTERVAL = parseInt(POLL_INTERVAL_MS) || 10 * 60 * 1000; +let isCheckingReleases = false; +let lastCheckedReleaseIds = new Set(); + +// Load tracked releases from file +function loadTrackedReleases() { + try { + if (existsSync(TRACKED_RELEASES_FILE)) { + const data = readFileSync(TRACKED_RELEASES_FILE, 'utf-8'); + const ids = JSON.parse(data); + return new Set(ids); + } + } catch (e) { + console.error('[PR Build Watcher] Error loading tracked releases:', e); + } + return new Set(); +} + +function saveTrackedReleases(releaseIds) { + try { + const dataDir = dirname(TRACKED_RELEASES_FILE); + if (!existsSync(dataDir)) { + mkdirSync(dataDir, { recursive: true }); + } + const data = JSON.stringify(Array.from(releaseIds), null, 2); + writeFileSync(TRACKED_RELEASES_FILE, data, 'utf-8'); + } catch (e) { + console.error('[PR Build Watcher] Error saving tracked releases:', e); + } +} + +// Initialize from file +lastCheckedReleaseIds = loadTrackedReleases(); + +function disabled() { + console.log('[PR Build Watcher] Service is disabled due to missing environment variables.'); + console.log('[PR Build Watcher][DEBUG] CHANNEL_ID = ' + CHANNEL_ID + ' GITHUB_TOKEN = ' + GITHUB_TOKEN); +} + +async function postReleaseToThread(release, channel, prNumber) { + try { + // Find thread with the name "Build {PRNMBER}" + const threadName = `Build ${prNumber}`; + + const activeThreads = await channel.threads.fetchActive(); + const archivedThreads = await channel.threads.fetchArchived({ limit: 100 }); + + let targetThread = activeThreads.threads.find(t => t.name === threadName); + if (!targetThread) { + targetThread = archivedThreads.threads.find(t => t.name === threadName); + } + + if (!targetThread) { + console.log(`[PR Build Watcher] Thread "${threadName}" not found, skipping release ${release.tag_name}`); + return; + } + + if (targetThread.locked || targetThread.archived) { + console.log(`[PR Build Watcher] Thread "${threadName}" is ${targetThread.locked ? 'locked' : 'archived'}, skipping release ${release.tag_name}`); + return; + } + + const embed = new EmbedBuilder() + .setColor(0x00FF00) + .setTitle(`New Build for PR ${prNumber}`) + .setDescription(`A new build for PR ${prNumber} has been uploaded!`) + .addFields( + { name: 'Release Name', value: release.name || release.tag_name, inline: false }, + { name: 'Link', value: `[View Release](${release.html_url})`, inline: false } + ) + .setTimestamp(new Date(release.published_at)) + .setFooter({ text: 'Eden CI Release' }); + + await targetThread.send({ embeds: [embed] }); + console.log(`[PR Build Watcher] Posted release ${release.tag_name} to thread "${threadName}"`); + } catch (e) { + console.error(`[PR Build Watcher] Error posting release ${release.tag_name} to thread:`, e); + } +} + +async function checkPRBuilds(client) { + if (isCheckingReleases) { + console.log('[PR Build Watcher] Skipping check: a check is already in progress.'); + return; + } + isCheckingReleases = true; + try { + const res = await fetch(`https://api.github.com/repos/${REPO}/releases`, { + headers: { 'User-Agent': 'release-watcher, EDEN', 'Authorization': `token ${GITHUB_TOKEN}` } + }); + + if (res.status === 403) { + console.error('[PR Build Watcher] GitHub API error: Rate limit or access denied.'); + return; + } + if (!res.ok) throw new Error(`GitHub API responded with status ${res.status}`); + + const allReleases = await res.json(); + if (!Array.isArray(allReleases)) return; + + const channel = await client.channels.fetch(CHANNEL_ID); + if (channel.type !== ChannelType.GuildForum) { + console.error(`[PR Build Watcher] Channel ${CHANNEL_ID} is not a Forum Channel.`); + return; + } + + for (const release of allReleases) { + if (lastCheckedReleaseIds.has(release.id)) continue; + const prNumber = release.tag_name.split('-')[0]; + await postReleaseToThread(release, channel, prNumber); + lastCheckedReleaseIds.add(release.id); + } + if (lastCheckedReleaseIds.size > 100) { + const idsArray = Array.from(lastCheckedReleaseIds); + lastCheckedReleaseIds = new Set(idsArray.slice(-100)); + } + saveTrackedReleases(lastCheckedReleaseIds); + } catch (e) { + console.error('[PR Build Watcher] Error in checkPRBuilds:', e); + } finally { + isCheckingReleases = false; + } +} + +function initializePRBuildWatcher(client) { + console.log(`[PR Build Watcher] Service initialized. Watching ${REPO} every ${POLL_INTERVAL / 1000} seconds.`); + console.log(`[PR Build Watcher] Loaded ${lastCheckedReleaseIds.size} previously tracked releases.`); + checkPRBuilds(client); + setInterval(() => checkPRBuilds(client), POLL_INTERVAL); +} + +const isEnabled = CHANNEL_ID && GITHUB_TOKEN; +const moduleToExport = isEnabled ? initializePRBuildWatcher : disabled; + +export default moduleToExport; diff --git a/src/functions/release_watcher.js b/src/functions/release_watcher.js index e522451..b064c4d 100644 --- a/src/functions/release_watcher.js +++ b/src/functions/release_watcher.js @@ -35,13 +35,21 @@ async function announceRelease(release, channel, baseTag) { let content = `**${title} - ${release.name}**\n\n${desc}\n\nšŸ”— [Go to pull request](${prUrl})\n\nšŸ“ [Go to downloads](${release.html_url})`; if (content.length > 2000) content = content.slice(0, 1997) + '...'; - await channel.threads.create({ + const thread = await channel.threads.create({ name: title, autoArchiveDuration: 10080, message: { content, flags: MessageFlags.SuppressEmbeds }, appliedTags: ['1396925576765374655'] // Tag ID for "Needs Testing" in builds forum. }); - console.log(`[Release Watcher] Created thread for ${title}`); + + // Pin the starter message + const starterMessage = await thread.fetchStarterMessage(); + if (starterMessage) { + await starterMessage.pin(); + console.log(`[Release Watcher] Created and pinned thread for ${title}`); + } else { + console.log(`[Release Watcher] Created thread for ${title}`); + } } catch (e) { console.error(`[Release Watcher] Error announcing release ${release.tag_name}:`, e); }