Curseforge 直接下载

跳过下载时的 5 秒等待,适用于新旧 Curseforge。

// ==UserScript==
// @name        Curseforge 直接下载
// @namespace   su226
// @include     https://www.curseforge.com/*
// @include     https://legacy.curseforge.com/*
// @grant       none
// @version     1.1
// @author      su226
// @description 跳过下载时的 5 秒等待,适用于新旧 Curseforge。
// @license     MIT
// ==/UserScript==

function observeSelector(selector, callback) {
  for (const el of document.querySelectorAll(selector)) {
    callback(el);
  }
  new MutationObserver((mutations, observer) => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        if (node.nodeType != Node.ELEMENT_NODE) {
          continue;
        }
        if (node.matches(selector)) {
          callback(node);
        }
        for (const el of node.querySelectorAll(selector)) {
          callback(el);
        }
      }
    }
  }).observe(document, { childList: true, subtree: true });
}

async function downloadOld(href) {
  if (href.match(/\/\d+$/)) {
    location.href = href + "/file";
    return;
  }
  try {
    const response = await fetch(href);
    const content = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(content, "text/html");
    const downloadLink = doc.querySelector(".alink").href;
    location.href = downloadLink;
  } catch (e) {
    // 下载一段时间内下载过的文件会直接跳转 edge.forgecdn.net,导致触发 CORS 拦截
    console.exception(e);
    location.href = href;
  }
}

async function downloadNew(href) {
  try {
    const response = await fetch(href);
    const content = await response.text();
    const projectId = content.match(/\\"project\\":{\\"id\\":(\d+)/)[1];
    const fileId = href.match(/\d+$/);
    location.href = `https://www.curseforge.com/api/v1/mods/${projectId}/files/${fileId}/download`;
  } catch (e) {
    // 这个纯粹是兜底
    console.exception(e);
    location.href = href;
  }
}

if (location.host.indexOf("legacy") !== -1) {
  observeSelector("a", el => {
    if (!el.href.match(/\/download($|\/)/) || el.href.indexOf("?client=y") !== -1) {
      return;
    }
    console.log(el);
    el.addEventListener("click", e => {
      e.preventDefault();
      downloadOld(el.href);
    });
  });
} else {
  observeSelector(".download-cta", el => {
    console.log(el);
    el.addEventListener("click", e => {
      e.preventDefault();
      downloadNew(el.href);
    })
  });
  observeSelector(".kebab-menu a", el => {
    if (el.href.indexOf("/download/") === -1) {
      return;
    }
    console.log(el);
    el.addEventListener("click", e => {
      e.preventDefault();
      downloadNew(el.href);
    })
  });
  observeSelector(".project-download-modal .download-btn", el => {
    console.log(el);
    el.addEventListener("click", e => {
      e.preventDefault();
      downloadNew(el.href);
    });
  });
  observeSelector(".download-button", el => {
    console.log(el);
    el.addEventListener("click", e => {
      e.preventDefault();
      downloadNew(el.href);
    });
  });
}