class TheRipperPremiumHUB { constructor() { this.outdatedModules = {}; this.announcementsHtml = ""; this._debug = false; this.converter = new showdown.Converter(); this.init(); } get repositoryIndex() { return { megapack: "megapack", "vtt-desktop-client": "fvtt-player-client", levels: "Levels", enhancedcombathud: "enhancedcombathud", "automated-evocations": "automated-evocations", betterroofs: "Better-Roofs", "combat-tracker-dock": "combat-tracker-dock", bossbar: "Boss-Bar", combatbooster: "Combat-Booster", "fuzzy-foundry": "fuzzy-foundry", "hover-distance": "hover-distance", "hurry-up": "hurry-up", levelsautocover: "levelsautocover", levelsvolumetrictemplates: "levelsvolumetrictemplates", patrol: "Patrol", "damage-numbers": "damage-numbers", smarttarget: "Smart-Target", splatter: "splatter", "dnd-randomizer": "dnd-randomizer", "tile-sort": "tile-sort", "tile-scroll": "tile-scroll", "token-z": "token-z", "foundry-taskbar": "foundry-taskbar", quickdraw: "quickdraw", "config-presets": "config-presets", "light-switch": "light-switch", "filepicker-plus": "filepicker-plus", "progress-tracker": "progress-tracker", "situational-shortcuts": "situational-shortcuts", "quick-doors": "quick-doors", "image-context": "image-context", "inactive-tokens-lmao": "inactive-tokens-lmao", "levels-3d-preview": "levels-3d-preview", choices: "choices", mmm: "Maxwell-s-Manual-of-Malicious-Maladies", socketmacros: "socketmacros", tokenflip: "tokenflip", "token-notes": "token-notes", "wall-height": "wall-height", canvas3dcompendium: "canvas3dcompendium", canvas3dtokencompendium: "canvas3dtokencompendium", "theripper-premium-hub": "theripper-premium-hub", "levels-layer-effects": "levels-layer-effects", "three-actor-portrait": "three-actor-portrait", mastercrafted: "mastercrafted", gatherer: "gatherer", "camera-dock": "camera-dock", "macro-wheel": "macro-wheel", }; } async init() { this.moduleData = await this.fetchData(); this.announcements = this.moduleData.announcements; delete this.moduleData.announcements; this.getOutdatedModules(); if (game.settings.get("theripper-premium-hub", "autoCheck")) { this.displayOutdated(false); } this.displayAnnouncements(); } async getForgeData(moduleId) { return await fetch(`https://forge-vtt.com/api/bazaar/package/${moduleId}`) .then((response) => response.json()) .then((data) => data) } getOutdatedModules() { const checkDisabled = game.settings.get("theripper-premium-hub", "checkDisabled"); for (let [k, v] of Object.entries(this.moduleData)) { const installedModule = game.modules.get(k); if (!installedModule) continue; if (!checkDisabled && !installedModule?.active) continue; if (isNewerVersion(v.version, installedModule.version) || this._debug) { this.outdatedModules[k] = v; this.outdatedModules[k].title = installedModule.title; this.outdatedModules[k].currentVersion = installedModule.version; } } } async displayOutdated(notify = false) { if (Object.keys(this.outdatedModules).length === 0) return notify ? ui.notifications.info(`No outdated modules found.`) : null; let displayUpdated = {}; if (!notify) { const viewedUpdates = game.settings.get("theripper-premium-hub", "viewedUpdates"); for (let [k, v] of Object.entries(this.outdatedModules)) { if (viewedUpdates[k] && v.version === viewedUpdates[k]) continue; displayUpdated[k] = v; } } else { displayUpdated = this.outdatedModules; } if (Object.keys(displayUpdated).length === 0) return null; const html = await renderTemplate("modules/theripper-premium-hub/templates/modlist.hbs", displayUpdated); Dialog.prompt({ title: "TheRipper93 Premium HUB - Updates Available!", content: html, rejectClose: false, callback: () => { const viewedUpdates = game.settings.get("theripper-premium-hub", "viewedUpdates"); for (let [k, v] of Object.entries(this.outdatedModules)) { viewedUpdates[k] = v.version; } game.settings.set("theripper-premium-hub", "viewedUpdates", viewedUpdates); }, close: () => {}, }); } async displayAnnouncements() { const announcements = this.announcements; if (!announcements) return; const ids = Object.keys(announcements); const viewedAnnouncements = game.settings.get("theripper-premium-hub", "viewedAnnouncements") ?? ""; const allViewed = ids.every((id) => viewedAnnouncements.includes(id)); const html = await renderTemplate("modules/theripper-premium-hub/templates/announcements.hbs", announcements); this.announcementsHtml = html; if (allViewed && !this._debug) return; game.settings.set("theripper-premium-hub", "viewedAnnouncements", ids.join(",")); Dialog.prompt({ title: "TheRipper93 Premium HUB - Announcement!", content: html + `

Want to support me and get access to premium modules?


Check out my Website

`, rejectClose: false, callback: () => {}, close: () => {}, }); } async fetchData() { const modules = game.modules.filter((v) => v.authors && v.authors.some((a) => a.name === "theripper93")); const announcements = await this.fetchAnnouncements(); const promises = modules.map((m) => this.fetchModuleData(m)); const obj = {}; await Promise.all(promises).then((data) => { data.forEach((d) => { if (d) { obj[d.id] = d; } }); }); obj.announcements = announcements.announcements; return obj; } async fetchModuleData(module) { const checkDisabled = game.settings.get("theripper-premium-hub", "checkDisabled"); if (!checkDisabled && !module.active) return null; const isPremium = module?.manifest?.includes("foundryvtt"); const id = module.id; const owner = "theripper93"; const repo = this.repositoryIndex[id] ?? id; const releases = await this.getAllReleasesFromGitHub(owner, repo, isPremium, module); if (!releases) return null; const latestRelease = Object.keys(releases).sort((a, b) => isNewerVersion(b, a))[0]; if(!latestRelease) return null; return { version: latestRelease, title: module.title, changelog: releases, latestChangeLog: this.converter.makeHtml(releases[latestRelease]), id: id, }; /*return await fetch(`https://forge-vtt.com/api/bazaar/package/${moduleId}`) .then((response) => response.json()) .then((data) => data);*/ } async getAllReleasesFromGitHub(owner, repo, isPremium, module) { if (isPremium) { const changelogURL = `https://raw.githubusercontent.com/theripper93/theripper-premium-hub/master/premium-changelogs/${repo}.md`; try { const response = await fetch(changelogURL); if (!response.ok) return null; const changelog = await response.text(); if (!changelog) return null; const releases = {}; //parse changelog const changelogLines = changelog.split(/\r?\n/); let currentVersion = ""; for (const line of changelogLines) { if (line.startsWith("##")) { const version = line.replace("## Version ", ""); releases[version] = ""; currentVersion = version; } else { const trimmed = line.trim(); if (trimmed != "") releases[currentVersion] += line + "\n"; } } return releases; } catch (error) { return null; } } else { const releasesPerPage = 10; // Maximum allowed by GitHub API const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=${releasesPerPage}`; try { let compiledReleases = {}; const response = await fetch(`${apiUrl}&page=1`); if (response.status === 404 && !module.download) { ui.notifications.error(`The module ${module.title} is still installed using the old manual installation, please switch to the new Foundry/Patreon integration. More Information`, { permanent: true }); } const releases = await response.json(); releases.forEach((release) => { compiledReleases[release.tag_name] = release.body; }); return compiledReleases; } catch (error) { return null; } } } async fetchAnnouncements() { return await fetch(`https://api.theripper93.com/moduleListing/latest`, { cache: "no-cache" }) .then((response) => response.json()) .then((data) => data); } _getdataforfile() { const mods = {}; game.modules.forEach((v, k) => { if (v.authors && v.authors.some((a) => a.name === "theripper93") && v.manifest.includes("foundryvtt")) { mods[k] = { title: v.title, version: v.version, downloadURL: this.moduleData[k]?.downloadURL ?? "", }; } }); return mods; } getDependencies(moduleId) { const rootModule = game.modules.get(moduleId); const dependencies = new Set(); const addDependecies = (module) => { const moduleDependencies = Array.from(module.relationships.requires).map((m) => game.modules.get(m.id)); moduleDependencies.forEach((m) => { dependencies.add(m); addDependecies(m); }); }; addDependecies(rootModule); return dependencies; } async troubleshoot(moduleId) { const confirm = await Dialog.confirm({ title: "TheRipper93 Premium HUB - Troubleshoot", content: `

Do you want to start the troubleshoot for ${game.modules.get(moduleId).title}?


This will disable all modules except the one you selected and its dependencies. You will be prompted to restore your modules after the troubleshoot.

`, yes: () => { return true; }, no: () => { return false; }, }); if (!confirm) return; const dependencies = this.getDependencies(moduleId); const dependenciesIds = [...Array.from(dependencies).map((m) => m.id), moduleId, "theripper-premium-hub"]; const modulesSetting = game.settings.get("core", ModuleManagement.CONFIG_SETTING); const currentlyEnabled = []; for (let [k, v] of Object.entries(modulesSetting)) { if (v) currentlyEnabled.push(k); if (dependenciesIds.includes(k)) continue; modulesSetting[k] = false; } await game.settings.set("theripper-premium-hub", "prevEnabledModules", currentlyEnabled); await game.settings.set("core", ModuleManagement.CONFIG_SETTING, modulesSetting); debouncedReload(); } }