import { getIfUpdate, UpdatedObject } from "@xmcl/net"; import * as parser from "fast-html-parser"; import { ForgeInstaller } from "./index"; export namespace ForgeWebPage { export interface Download { md5: string; sha1: string; path: string; } /** * Parse the html string of forge webpage */ export function parse(content: string): ForgeWebPage { const dom = parser.parse(content); const selected = dom.querySelector(".elem-active"); const mcversion = selected.text; return { timestamp: "", mcversion, versions: dom.querySelector(".download-list").querySelector("tbody").querySelectorAll("tr") .map((e) => { const links = e.querySelector(".download-links").childNodes .filter((elem) => elem.tagName === "li") .map((elem) => { elem = elem.removeWhitespace(); /* *
* MD5: 31742b6c996f53af96f606b7a0c46e2a
* SHA1: 8d6a23554839d6f6014fbdb7991e3cd8af7eca80 *
*/ const tooltipInfo = elem.querySelector(".info-tooltip"); const url = tooltipInfo.querySelector("a") || elem.querySelector("a"); // href is like /maven/net/minecraftforge/forge/1.14.4-28.1.70/forge-1.14.4-28.1.70-changelog.txt const href = url.attributes.href.trim(); const matched = /forge-.+-.+-(\w+)\.\w+/.exec(href); let name = "", sha1 = "", md5 = ""; if (matched) { name = matched[1]; } if (!name) { throw new Error(`Cannot determine name for forge url "${href}". Maybe the forge webisite changed?`); } try { md5 = tooltipInfo.childNodes[1].text.trim(); sha1 = tooltipInfo.childNodes[4].text.trim(); } catch { console.warn(`Error during fetching the sha1 and md5 for the forge "${href}". The result might be wrong.`); } const isSha1 = /\b([a-f0-9]{40})\b/i const isMd5 = /\b[a-f0-9]{32}\b/i; if (!isMd5.test(md5.trim())) { console.warn(`Illegal Md5 for "${href}": ${md5}`) md5 = ""; } if (!isSha1.test(sha1.trim())) { console.warn(`Illegal Sha1 for "${href}": ${sha1}`) sha1 = ""; } return { name, md5, sha1, path: href, }; }); const downloadVersionElem = e.querySelector(".download-version"); let version; let type: ForgeWebPage.Version["type"] = "common"; const icon = downloadVersionElem.querySelector("i"); if (icon) { if (icon.classNames.indexOf("promo-recommended") !== -1) { type = "recommended"; } else if (icon.classNames.indexOf("promo-latest") !== -1) { type = "latest"; } else if (icon.classNames.indexOf("fa-bug") !== -1) { type = "buggy"; } version = downloadVersionElem.firstChild.text.trim(); } else { version = downloadVersionElem.text.trim(); } const installer = links.find((l) => l.name === "installer"); const universal = links.find((l) => l.name === "universal"); const changelog = links.find((l) => l.name === "changelog"); const installerWin = links.find((l) => l.name === "installer-win"); const source = links.find((l) => l.name === "source"); const launcher = links.find((l) => l.name === "launcher"); const mdk = links.find((l) => l.name === "mdk"); if (installer === undefined || universal === undefined) { throw new Error("Cannot parse forge web since it missing installer and universal jar info."); } const result = { version, "date": e.querySelector(".download-time").text.trim(), changelog, installer, mdk, universal, source, launcher, "installer-win": installerWin, "mcversion": mcversion, type, }; return result; }), }; } /** * Query the webpage content from files.minecraftforge.net. * * You can put the last query result to the fallback option. It will check if your old result is up-to-date. * It will request a new page only when the fallback option is outdated. * * @param option The option can control querying minecraft version, and page caching. */ export function getWebPage(): Promise; /** * Query the webpage content from files.minecraftforge.net. * * You can put the last query result to the fallback option. It will check if your old result is up-to-date. * It will request a new page only when the fallback option is outdated. * * @param option The option can control querying minecraft version, and page caching. */ export function getWebPage(option?: { mcversion?: string; }): Promise; /** * Query the webpage content from files.minecraftforge.net. * * You can put the last query result to the fallback option. It will check if your old result is up-to-date. * It will request a new page only when the fallback option is outdated. * * @param option The option can control querying minecraft version, and page caching. */ export function getWebPage(option?: { mcversion?: string; fallback?: ForgeWebPage; }): Promise; /** * Query the webpage content from files.minecraftforge.net. * * You can put the last query result to the fallback option. It will check if your old result is up-to-date. * It will request a new page only when the fallback option is outdated. * * @param option The option can control querying minecraft version, and page caching. */ export function getWebPage(option?: { mcversion?: string; fallback: ForgeWebPage; }): Promise; /** * Query the webpage content from files.minecraftforge.net. * * You can put the last query result to the fallback option. It will check if your old result is up-to-date. * It will request a new page only when the fallback option is outdated. * * @param option The option can control querying minecraft version, and page caching. */ export async function getWebPage(option: { mcversion?: string, fallback?: ForgeWebPage, } = {}): Promise { const mcversion = option.mcversion || ""; const url = mcversion === "" ? "http://files.minecraftforge.net/maven/net/minecraftforge/forge/index.html" : `http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_${mcversion}.html`; const page = await getIfUpdate(url, parse, option.fallback); return page; } /** * A richer version info than forge installer required */ export interface Version extends ForgeInstaller.VersionMeta { /** * The minecraft version */ mcversion: string; /** * The version of forge */ version: string; date: string; /** * The changelog info */ changelog?: ForgeWebPage.Download; installer: ForgeWebPage.Download; mdk?: ForgeWebPage.Download; universal: ForgeWebPage.Download; source?: ForgeWebPage.Download; launcher?: ForgeWebPage.Download; /** * The type of the forge release. The `common` means the normal release. */ type: "buggy" | "recommended" | "common" | "latest"; } export namespace Version { export function to(webPageVersion: ForgeWebPage.Version): ForgeInstaller.VersionMeta { return { universal: webPageVersion.universal, installer: webPageVersion.installer, mcversion: webPageVersion.mcversion, version: webPageVersion.version, }; } } } declare module "./index" { export namespace VersionMeta { export function from(webPageVersion: ForgeWebPage.Version): ForgeInstaller.VersionMeta; } } (ForgeInstaller as any).VersionMeta = (ForgeInstaller as any).VersionMeta || {}; (ForgeInstaller as any).VersionMeta.from = ForgeWebPage.Version.to; export interface ForgeWebPage extends UpdatedObject { versions: ForgeWebPage.Version[]; mcversion: string; }