Greasy Fork

多领国阻止血量减少

阻止 多领国 网站在学习过程中因错误减少用户的血量,通过拦截并阻止相关的“remove-heart”请求。

当前为 2025-02-01 提交的版本,查看 最新版本

// ==UserScript==
// @name            多领国阻止血量减少
// @name:en         DuoLingo Prevent Remove Heart
// @namespace       https://space.bilibili.com/1569275826
// @match           *://*.duolingo.com/*
// @match           *://*.duolingo.cn/*
// @version         0.0.5
// @description     阻止 多领国 网站在学习过程中因错误减少用户的血量,通过拦截并阻止相关的“remove-heart”请求。
// @description:en  Prevent the Duolingo website from reducing the number of hearts for users during the learning process by intercepting and blocking related "remove-heart" request.
// @author          深海云霁
// @license         MIT
// @icon            data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant           none
// ==/UserScript==

(function () {
  "use strict";

  // 获取用户 UUID
  function getUUID() {
    const match = document.cookie.match(
      new RegExp("(^| )logged_out_uuid=([^;]+)")
    );
    return match ? match[2] : null;
  }

  // 获取用户血量 (后端)
  async function getUserHeartFromBackend() {
    const uuid = getUUID();
    const url = `/2017-06-30/users/${uuid}?fields=health&_=${Date.now()}`;
    const response = await fetch(url, {
      method: "GET",
      credentials: "include",
    });
    return response.json();
  }

  // 获取用户血量 (前端)
  function getUserHeartFromFrontend() {
    const spanElement =
      document.querySelector(".xb7v_") || document.querySelector("._1ceMn");
    return spanElement?.textContent ? +spanElement.textContent : undefined;
  }

  // 检查血量是否一致
  async function checkHeartConsistency() {
    const backendHearts = await getUserHeartFromBackend();
    const frontendHearts = getUserHeartFromFrontend();

    if (
      frontendHearts !== undefined &&
      backendHearts?.health?.hearts !== frontendHearts &&
      window.confirm("检测到血量不一致,是否刷新页面?")
    ) {
      window.location.reload();
    }
  }

  // 阻止 remove-heart 请求
  function blockRemoveHeart() {
    const originalFetch = window.fetch;

    window.fetch = function (...args) {
      const [url] = args;
      if (typeof url === "string" && url.includes("remove-heart")) {
        return Promise.resolve(new Response(null, { status: 200 }));
      }
      return originalFetch.apply(this, args);
    };
  }

  // 监听 pathname 变化
  function watchPathname() {
    const originalPushState = history.pushState;
    let isLearning = false;

    history.pushState = function (state, title, url) {
      originalPushState.apply(history, arguments);

      if (url.startsWith("/lesson")) {
        isLearning = true;
      } else if (isLearning) {
        isLearning = false;
        checkHeartConsistency();
      }
    };
  }

  // 初始化
  function init() {
    blockRemoveHeart();
    watchPathname();
  }

  init();
})();