Greasy Fork

Duolingo: Type more, tap less

Replace tapping exercises with full-sentence typing

目前为 2025-02-09 提交的版本。查看 最新版本

// ==UserScript==
// @name         Duolingo: Type more, tap less
// @description  Replace tapping exercises with full-sentence typing
// @namespace    neobrain
// @author       neobrain
// @match        https://*.duolingo.com/*
// @grant        GM.XMLHTTPRequest
// @grant        GM.log
// @run-at       document-start
// @require      https://update.greasyfork.org/scripts/472943.js?version=1239323
// @license      MIT
// @version      0.4
// ==/UserScript==

middleMan.addHook("*/sessions", {
    requestHandler(request) {
      GM.log("Outgoing request: ", request);
    },
    async responseHandler(request, response, error) {
        GM.log("Incoming response: ", response, error);

        let reduxState;
        try {
          reduxState = JSON.parse(localStorage.getItem("duo.state")).state.redux
        } catch {
          // NOTE: This is now a base64-encoded gz archive

          let str = localStorage.getItem("duo.state");

          const str2 = atob(str.slice(1, -1)); // Remove wrapping quotes
          const ds = new DecompressionStream("gzip");
          let uint8Array = new Uint8Array(str2.split("").map(c => c.charCodeAt(0)));
          let readableStream = new ReadableStream({
              start(controller) {
                  controller.enqueue(uint8Array);
                  controller.close();
              }
          });

          let outstream = readableStream.pipeThrough(ds);

          let result = "";
          const reader = outstream.getReader();
          const decoder = new TextDecoder('utf-8');
          while (true) {
            let value = await reader.read();
            result += decoder.decode(value.value);
            if (value.done) {
              break;
            }
          }

          reduxState = JSON.parse(result).state.redux;
        }
        const learningLanguage = reduxState.user.learningLanguage;
        const fromLanguage = reduxState.user.fromLanguage;

        const data = await response?.json() ?? { errorMessage: error.message };
        for (let challenge of data.challenges) {
            // Translate exercises use tapping only when translating to the student language.
            // Trick the frontend into allowing free-form text by swapping the languages
            if (challenge.type === "translate" && challenge.grader.language == fromLanguage) {
                challenge.grader.language = learningLanguage;
                challenge.sourceLanguage = fromLanguage;
                // NOTE: The frontend uses challenge.targetLanguage for the "Write this in X" text, so we leave it unchanged
            }
        }
        return Response.json(data);
    }
});