All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
13 KiB

  1. class TheRipperPremiumHUB {
  2. constructor() {
  3. this.outdatedModules = {};
  4. this.announcementsHtml = "";
  5. this._debug = false;
  6. this.converter = new showdown.Converter();
  7. this.init();
  8. }
  9. get repositoryIndex() {
  10. return {
  11. megapack: "megapack",
  12. "vtt-desktop-client": "fvtt-player-client",
  13. levels: "Levels",
  14. enhancedcombathud: "enhancedcombathud",
  15. "automated-evocations": "automated-evocations",
  16. betterroofs: "Better-Roofs",
  17. "combat-tracker-dock": "combat-tracker-dock",
  18. bossbar: "Boss-Bar",
  19. combatbooster: "Combat-Booster",
  20. "fuzzy-foundry": "fuzzy-foundry",
  21. "hover-distance": "hover-distance",
  22. "hurry-up": "hurry-up",
  23. levelsautocover: "levelsautocover",
  24. levelsvolumetrictemplates: "levelsvolumetrictemplates",
  25. patrol: "Patrol",
  26. "damage-numbers": "damage-numbers",
  27. smarttarget: "Smart-Target",
  28. splatter: "splatter",
  29. "dnd-randomizer": "dnd-randomizer",
  30. "tile-sort": "tile-sort",
  31. "tile-scroll": "tile-scroll",
  32. "token-z": "token-z",
  33. "foundry-taskbar": "foundry-taskbar",
  34. quickdraw: "quickdraw",
  35. "config-presets": "config-presets",
  36. "light-switch": "light-switch",
  37. "filepicker-plus": "filepicker-plus",
  38. "progress-tracker": "progress-tracker",
  39. "situational-shortcuts": "situational-shortcuts",
  40. "quick-doors": "quick-doors",
  41. "image-context": "image-context",
  42. "inactive-tokens-lmao": "inactive-tokens-lmao",
  43. "levels-3d-preview": "levels-3d-preview",
  44. choices: "choices",
  45. mmm: "Maxwell-s-Manual-of-Malicious-Maladies",
  46. socketmacros: "socketmacros",
  47. tokenflip: "tokenflip",
  48. "token-notes": "token-notes",
  49. "wall-height": "wall-height",
  50. canvas3dcompendium: "canvas3dcompendium",
  51. canvas3dtokencompendium: "canvas3dtokencompendium",
  52. "theripper-premium-hub": "theripper-premium-hub",
  53. "levels-layer-effects": "levels-layer-effects",
  54. "three-actor-portrait": "three-actor-portrait",
  55. mastercrafted: "mastercrafted",
  56. gatherer: "gatherer",
  57. "camera-dock": "camera-dock",
  58. "macro-wheel": "macro-wheel",
  59. };
  60. }
  61. async init() {
  62. this.moduleData = await this.fetchData();
  63. this.announcements = this.moduleData.announcements;
  64. delete this.moduleData.announcements;
  65. this.getOutdatedModules();
  66. if (game.settings.get("theripper-premium-hub", "autoCheck")) {
  67. this.displayOutdated(false);
  68. }
  69. this.displayAnnouncements();
  70. }
  71. async getForgeData(moduleId) {
  72. return await fetch(`https://forge-vtt.com/api/bazaar/package/${moduleId}`)
  73. .then((response) => response.json())
  74. .then((data) => data)
  75. }
  76. getOutdatedModules() {
  77. const checkDisabled = game.settings.get("theripper-premium-hub", "checkDisabled");
  78. for (let [k, v] of Object.entries(this.moduleData)) {
  79. const installedModule = game.modules.get(k);
  80. if (!installedModule) continue;
  81. if (!checkDisabled && !installedModule?.active) continue;
  82. if (isNewerVersion(v.version, installedModule.version) || this._debug) {
  83. this.outdatedModules[k] = v;
  84. this.outdatedModules[k].title = installedModule.title;
  85. this.outdatedModules[k].currentVersion = installedModule.version;
  86. }
  87. }
  88. }
  89. async displayOutdated(notify = false) {
  90. if (Object.keys(this.outdatedModules).length === 0) return notify ? ui.notifications.info(`No outdated modules found.`) : null;
  91. let displayUpdated = {};
  92. if (!notify) {
  93. const viewedUpdates = game.settings.get("theripper-premium-hub", "viewedUpdates");
  94. for (let [k, v] of Object.entries(this.outdatedModules)) {
  95. if (viewedUpdates[k] && v.version === viewedUpdates[k]) continue;
  96. displayUpdated[k] = v;
  97. }
  98. } else {
  99. displayUpdated = this.outdatedModules;
  100. }
  101. if (Object.keys(displayUpdated).length === 0) return null;
  102. const html = await renderTemplate("modules/theripper-premium-hub/templates/modlist.hbs", displayUpdated);
  103. Dialog.prompt({
  104. title: "TheRipper93 Premium HUB - Updates Available!",
  105. content: html,
  106. rejectClose: false,
  107. callback: () => {
  108. const viewedUpdates = game.settings.get("theripper-premium-hub", "viewedUpdates");
  109. for (let [k, v] of Object.entries(this.outdatedModules)) {
  110. viewedUpdates[k] = v.version;
  111. }
  112. game.settings.set("theripper-premium-hub", "viewedUpdates", viewedUpdates);
  113. },
  114. close: () => {},
  115. });
  116. }
  117. async displayAnnouncements() {
  118. const announcements = this.announcements;
  119. if (!announcements) return;
  120. const ids = Object.keys(announcements);
  121. const viewedAnnouncements = game.settings.get("theripper-premium-hub", "viewedAnnouncements") ?? "";
  122. const allViewed = ids.every((id) => viewedAnnouncements.includes(id));
  123. const html = await renderTemplate("modules/theripper-premium-hub/templates/announcements.hbs", announcements);
  124. this.announcementsHtml = html;
  125. if (allViewed && !this._debug) return;
  126. game.settings.set("theripper-premium-hub", "viewedAnnouncements", ids.join(","));
  127. Dialog.prompt({
  128. title: "TheRipper93 Premium HUB - Announcement!",
  129. content:
  130. html +
  131. ` <p style="text-align: center;">Want to support me and get access to premium modules?</p><hr>
  132. <p style="text-align: center;"><a href="https://theripper93.com/" target="_blank" rel="nofollow" title="https://theripper93.com/">Check out my Website</a></p>`,
  133. rejectClose: false,
  134. callback: () => {},
  135. close: () => {},
  136. });
  137. }
  138. async fetchData() {
  139. const modules = game.modules.filter((v) => v.authors && v.authors.some((a) => a.name === "theripper93"));
  140. const announcements = await this.fetchAnnouncements();
  141. const promises = modules.map((m) => this.fetchModuleData(m));
  142. const obj = {};
  143. await Promise.all(promises).then((data) => {
  144. data.forEach((d) => {
  145. if (d) {
  146. obj[d.id] = d;
  147. }
  148. });
  149. });
  150. obj.announcements = announcements.announcements;
  151. return obj;
  152. }
  153. async fetchModuleData(module) {
  154. const checkDisabled = game.settings.get("theripper-premium-hub", "checkDisabled");
  155. if (!checkDisabled && !module.active) return null;
  156. const isPremium = module?.manifest?.includes("foundryvtt");
  157. const id = module.id;
  158. const owner = "theripper93";
  159. const repo = this.repositoryIndex[id] ?? id;
  160. const releases = await this.getAllReleasesFromGitHub(owner, repo, isPremium, module);
  161. if (!releases) return null;
  162. const latestRelease = Object.keys(releases).sort((a, b) => isNewerVersion(b, a))[0];
  163. if(!latestRelease) return null;
  164. return {
  165. version: latestRelease,
  166. title: module.title,
  167. changelog: releases,
  168. latestChangeLog: this.converter.makeHtml(releases[latestRelease]),
  169. id: id,
  170. };
  171. /*return await fetch(`https://forge-vtt.com/api/bazaar/package/${moduleId}`)
  172. .then((response) => response.json())
  173. .then((data) => data);*/
  174. }
  175. async getAllReleasesFromGitHub(owner, repo, isPremium, module) {
  176. if (isPremium) {
  177. const changelogURL = `https://raw.githubusercontent.com/theripper93/theripper-premium-hub/master/premium-changelogs/${repo}.md`;
  178. try {
  179. const response = await fetch(changelogURL);
  180. if (!response.ok) return null;
  181. const changelog = await response.text();
  182. if (!changelog) return null;
  183. const releases = {};
  184. //parse changelog
  185. const changelogLines = changelog.split(/\r?\n/);
  186. let currentVersion = "";
  187. for (const line of changelogLines) {
  188. if (line.startsWith("##")) {
  189. const version = line.replace("## Version ", "");
  190. releases[version] = "";
  191. currentVersion = version;
  192. } else {
  193. const trimmed = line.trim();
  194. if (trimmed != "") releases[currentVersion] += line + "\n";
  195. }
  196. }
  197. return releases;
  198. } catch (error) {
  199. return null;
  200. }
  201. } else {
  202. const releasesPerPage = 10; // Maximum allowed by GitHub API
  203. const apiUrl = `https://api.github.com/repos/${owner}/${repo}/releases?per_page=${releasesPerPage}`;
  204. try {
  205. let compiledReleases = {};
  206. const response = await fetch(`${apiUrl}&page=1`);
  207. if (response.status === 404 && !module.download) {
  208. ui.notifications.error(`The module <strong>${module.title}</strong> is still installed using the old manual installation, please switch to the new Foundry/Patreon integration. <a href="https://theripper93.com/info/installation" target="_blank" rel="nofollow">More Information</a>`, { permanent: true });
  209. }
  210. const releases = await response.json();
  211. releases.forEach((release) => {
  212. compiledReleases[release.tag_name] = release.body;
  213. });
  214. return compiledReleases;
  215. } catch (error) {
  216. return null;
  217. }
  218. }
  219. }
  220. async fetchAnnouncements() {
  221. return await fetch(`https://api.theripper93.com/moduleListing/latest`, { cache: "no-cache" })
  222. .then((response) => response.json())
  223. .then((data) => data);
  224. }
  225. _getdataforfile() {
  226. const mods = {};
  227. game.modules.forEach((v, k) => {
  228. if (v.authors && v.authors.some((a) => a.name === "theripper93") && v.manifest.includes("foundryvtt")) {
  229. mods[k] = {
  230. title: v.title,
  231. version: v.version,
  232. downloadURL: this.moduleData[k]?.downloadURL ?? "",
  233. };
  234. }
  235. });
  236. return mods;
  237. }
  238. getDependencies(moduleId) {
  239. const rootModule = game.modules.get(moduleId);
  240. const dependencies = new Set();
  241. const addDependecies = (module) => {
  242. const moduleDependencies = Array.from(module.relationships.requires).map((m) => game.modules.get(m.id));
  243. moduleDependencies.forEach((m) => {
  244. dependencies.add(m);
  245. addDependecies(m);
  246. });
  247. };
  248. addDependecies(rootModule);
  249. return dependencies;
  250. }
  251. async troubleshoot(moduleId) {
  252. const confirm = await Dialog.confirm({
  253. title: "TheRipper93 Premium HUB - Troubleshoot",
  254. content: `<p>Do you want to start the troubleshoot for ${game.modules.get(moduleId).title}?</p><br><p>This will disable all modules except the one you selected and its dependencies. You will be prompted to restore your modules after the troubleshoot.</p>`,
  255. yes: () => {
  256. return true;
  257. },
  258. no: () => {
  259. return false;
  260. },
  261. });
  262. if (!confirm) return;
  263. const dependencies = this.getDependencies(moduleId);
  264. const dependenciesIds = [...Array.from(dependencies).map((m) => m.id), moduleId, "theripper-premium-hub"];
  265. const modulesSetting = game.settings.get("core", ModuleManagement.CONFIG_SETTING);
  266. const currentlyEnabled = [];
  267. for (let [k, v] of Object.entries(modulesSetting)) {
  268. if (v) currentlyEnabled.push(k);
  269. if (dependenciesIds.includes(k)) continue;
  270. modulesSetting[k] = false;
  271. }
  272. await game.settings.set("theripper-premium-hub", "prevEnabledModules", currentlyEnabled);
  273. await game.settings.set("core", ModuleManagement.CONFIG_SETTING, modulesSetting);
  274. debouncedReload();
  275. }
  276. }