watch releases and post them in the thread, pin message when create a thread

This commit is contained in:
Maufeat
2025-11-25 22:54:18 +01:00
parent ae4ad46e6e
commit cd336618b5
4 changed files with 165 additions and 2 deletions

View File

@@ -0,0 +1 @@
[]

View File

@@ -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);

View File

@@ -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;

View File

@@ -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.
});
// 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);
}