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, minecraft_version, modrinth_mods } = config const { token } = api const cacheFile = path.join(__dirname, "../cache/mod_versions.json") const client = new Client ({ intents: [GatewayIntentBits.Guilds] }) const LoadCache = async () => { try { if (await fs.pathExists(cacheFile)) return JSON.parse(await fs.readFile(cacheFile, "utf8")) } catch (err) { console.error("[Cache] Failed to read cache:", 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 cache:", err) } } const GetModrinthData = async slug => { try { const r = await axios.get(`https://api.modrinth.com/v2/project/${slug}/version`) return r.data } catch (err) { console.error(`[Modrinth] Failed for ${slug}:`, err.message) return null } } const CheckMods = async () => { const cache = await LoadCache() const newCache = { ...cache } const updatedNotCompatible = [] const newlyCompatible = [] const compatibleUpdated = [] for (const slug of modrinth_mods) { const versions = await GetModrinthData(slug) if (!versions) continue const fabric = versions.filter(v => v.loaders.includes("fabric")) if (!fabric.length) continue const latest = fabric.sort((a, b) => new Date(b.date_published) - new Date(a.date_published) )[0] const latestId = latest.id const compatible = latest.game_versions.includes(minecraft_version) const cachedId = cache[slug] if (!cachedId) newCache[slug] = latestId if (latestId === cachedId) continue const previouslyCompatible = fabric.some(v => v.id === cachedId && v.game_versions.includes(minecraft_version) ) if (!compatible) updatedNotCompatible.push(slug) else if (compatible && !previouslyCompatible) newlyCompatible.push(slug) else if (compatible && previouslyCompatible) compatibleUpdated.push(slug) newCache[slug] = latestId } await SaveCache(newCache) return { updatedNotCompatible, newlyCompatible, compatibleUpdated } } const SendUpdate = async (channel, updatedNotCompatible, newlyCompatible, compatibleUpdated) => { if (!updatedNotCompatible.length && !newlyCompatible.length && !compatibleUpdated.length) return let msg = `**Modrinth Update Check — ${minecraft_version}**\n\n` if (updatedNotCompatible.length) { msg += `**❗ Updated but NOT compatible:**\n` for (const s of updatedNotCompatible) msg += `❗ ${s}\n` msg += `\n` } if (newlyCompatible.length) { msg += `**🎉 NOW Compatible:**\n` for (const s of newlyCompatible) msg += `🎉 ${s}\n` msg += `\n` } if (compatibleUpdated.length) { msg += `**🟢 Updated (still compatible):**\n` for (const s of compatibleUpdated) msg += `🟢 ${s}\n` msg += `\n` } await channel.send ({ content: msg, allowedMentions: { parse: [] }, flags: 4 }) console.log("[Discord] Sent update.") } client.once(Events.ClientReady, async c => { console.log(`[OK] Logged in as ${c.user.tag}`) const channel = await client.channels.fetch(channel_id).catch(() => null) if (!channel) { console.error("Invalid channel_id:", channel_id) return } const { updatedNotCompatible, newlyCompatible, compatibleUpdated } = await CheckMods() await SendUpdate(channel, updatedNotCompatible, newlyCompatible, compatibleUpdated) setInterval(async () => { const { updatedNotCompatible, newlyCompatible, compatibleUpdated } = await CheckMods() await SendUpdate(channel, updatedNotCompatible, newlyCompatible, compatibleUpdated) }, 60 * 60 * 1000) }) client.login(token)