const { Client, Events, GatewayIntentBits } = require("discord.js") const axios = require("axios") const fs = require("fs-extra") const path = require("path") const config = require("../config/config.json") const api = require("../config/api.json") const { channel_id, check_interval_minutes, minecraft_version, fabric_loader_version, loader_meta_version, modrinth_mods } = config const { token } = api const cacheFile = path.join(__dirname, "../cache/mod_versions.json") const client = new Client ({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages ] }) const LoadCache = async () => { try { if (await fs.pathExists(cacheFile)) return JSON.parse(await fs.readFile(cacheFile, "utf8")) } catch (err) { console.error("[Cache] Failed to load:", err) } return {} } const SaveCache = async cache => { try { await fs.ensureDir(path.dirname(cacheFile)) await fs.writeFile(cacheFile, JSON.stringify(cache, null, 2)) } catch (err) { console.error("[Cache] Failed to save:", err) } } const GetModVersions = async slug => { try { const response = await axios.get(`https://api.modrinth.com/v2/project/${slug}/version`) return response.data } catch (err) { console.error(`[Modrinth] Failed for ${slug}:`, err.message) return null } } const CheckMods = async () => { const cache = await LoadCache() const newCache = { ...cache } const noCompatible = [] const newlyCompatible = [] const stillCompatibleUpdate = [] for (const slug of modrinth_mods) { const versions = await GetModVersions(slug) if (!versions) continue const latest = versions.sort((a, b) => new Date(b.date_published) - new Date(a.date_published))[0] const compatible = versions.find(v => v.game_versions.includes(minecraft_version)) || null const cached = cache[slug] || { compatible: null, latest: null } newCache[slug] = { compatible: compatible ? compatible.id : null, latest: latest.id } if (!compatible) { noCompatible.push(slug) continue } if (!cached.compatible && compatible) { newlyCompatible.push(slug) continue } if (latest.id !== cached.latest) stillCompatibleUpdate.push(slug) } await SaveCache(newCache) return { noCompatible, newlyCompatible, stillCompatibleUpdate } } const SendUpdate = async (channel, noCompatible, newlyCompatible, stillCompatibleUpdate) => { if (!noCompatible.length && !newlyCompatible.length && !stillCompatibleUpdate.length) return let msg = `**Modrinth Compatibility — ${minecraft_version}**\n\n` if (noCompatible.length) { msg += `**❌ No compatible version available:**\n` for (const slug of noCompatible) msg += `❌ ${slug}\n` msg += `\n` } if (newlyCompatible.length) { msg += `**🎉 Now Compatible:**\n` for (const slug of newlyCompatible) msg += `🎉 ${slug}\n` msg += `\n` } if (stillCompatibleUpdate.length) { msg += `**🟢 Updated (still compatible):**\n` for (const slug of stillCompatibleUpdate) msg += `🟢 ${slug}\n` msg += `\n` } await channel.send ({ content: msg, allowedMentions: { parse: [] } }) console.log("[Discord] Update message sent.") } client.once(Events.ClientReady, async c => { console.log(`[OK] Logged in as ${c.user.tag}`) let channel = null try { channel = await client.channels.fetch(channel_id) console.log("Fetched channel:", channel.name, "type:", channel.type) } catch { console.error("❌ Failed to fetch channel:", channel_id) return } const { noCompatible, newlyCompatible, stillCompatibleUpdate } = await CheckMods() await SendUpdate(channel, noCompatible, newlyCompatible, stillCompatibleUpdate) setInterval(async () => { const { noCompatible, newlyCompatible, stillCompatibleUpdate } = await CheckMods() await SendUpdate(channel, noCompatible, newlyCompatible, stillCompatibleUpdate) }, check_interval_minutes * 60 * 1000) }) client.login(token)