Greasy Fork is available in English.

多领国阻止血量减少

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

// ==UserScript==
// @name            多领国阻止血量减少
// @name:en         DuoLingo Prevent Remove Heart
// @name:vi         DuoLingo Ngăn Chặn Mất Tim
// @name:fr         DuoLingo Empêcher la Perte de Cœurs
// @name:es         DuoLingo Evitar la Pérdida de Corazones
// @name:de         DuoLingo Verhindern des Herzverlusts
// @name:it         DuoLingo Impedire la Perdita di Cuori
// @name:ja         DuoLingo ハート減少防止
// @name:ko         DuoLingo 하트 감소 방지
// @name:ru         DuoLingo Предотвращение Потери Сердец
// @namespace       https://space.bilibili.com/1569275826
// @match           *://*.duolingo.com/*
// @match           *://*.duolingo.cn/*
// @version         0.0.6
// @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.
// @description:vi  Ngăn chặn trang web Duolingo giảm số lượng trái tim của người dùng trong quá trình học tập bằng cách chặn các yêu cầu "remove-heart" liên quan.
// @description:fr  Empêchez le site Web de Duolingo de réduire le nombre de cœurs des utilisateurs pendant le processus d'apprentissage en interceptant et en bloquant les requêtes "remove-heart" associées.
// @description:es  Evita que el sitio web de Duolingo reduzca la cantidad de corazones de los usuarios durante el proceso de aprendizaje al interceptar y bloquear las solicitudes "remove-heart" relacionadas.
// @description:de  Verhindern Sie, dass die Duolingo-Website die Anzahl der Herzen der Benutzer während des Lernprozesses verringert, indem Sie verwandte "remove-heart"-Anfragen abfangen und blockieren.
// @description:it  Impedisci al sito web di Duolingo di ridurre il numero di cuori degli utenti durante il processo di apprendimento intercettando e bloccando le richieste "remove-heart" correlate.
// @description:ja  Duolingoのウェブサイトが学習プロセス中にユーザーのハートを減らすのを防ぐため、"remove-heart"リクエストを傍受してブロックします。
// @description:ko  Duolingo 웹사이트가 학습 과정에서 사용자의 하트를 줄이는 것을 방지하기 위해 관련 "remove-heart" 요청을 가로채고 차단합니다.
// @description:ru  Предотвратите уменьшение количества сердец у пользователей на сайте Duolingo в процессе обучения, перехватывая и блокируя связанные запросы "remove-heart".
// @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] : undefined;
  }

  // 获取用户血量 (后端)
  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(
        navigator.language.toUpperCase() === "ZH-CN"
          ? "检测到血量不一致,是否刷新页面?"
          : "Detected inconsistent health, refresh the page?"
      )
    ) {
      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();
})();