LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退)

LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退) + JavaScript 新版本 esnext 全支持

// ==UserScript==
// @name         LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退)
// @namespace    https://github.com/Q-Peppa/LeetCode-Code-Completion
// @version      2025-09-13
// @description  LeetCode Monaco JS 原生补全增强 (支持 iframe + 缓存回退) + JavaScript 新版本 esnext 全支持
// @author       Peppa
// @license      MIT
// @match        https://leetcode.cn/problems/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=leetcode.cn
// @grant        GM_log
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// ==/UserScript==

const LIBS = [
  "lib.dom.d.ts",
  "lib.es5.d.ts",
  "lib.es2015.core.d.ts",
  "lib.es2015.collection.d.ts",
  "lib.es2023.array.d.ts",
  "lib.es2024.object.d.ts",
  "lib.es2024.collection.d.ts",
  "lib.esnext.collection.d.ts",
];

const fallbacks = {
  "lib.dom.d.ts": `interface Document {
        getElementById(id: string): HTMLElement | null;
    }
    interface Window {
        document: Document;
    }`,
  "lib.es5.d.ts": `interface Array<T> {
        forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
        map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
        filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[];
    }`,
  "lib.es2015.core.d.ts": `interface Promise<T> {
        then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;
    }`,
  "lib.es2015.collection.d.ts": `interface Map<K, V> {
        get(key: K): V | undefined;
        set(key: K, value: V): this;
        has(key: K): boolean;
        delete(key: K): boolean;
    }`,
  "lib.esnext.iterator.d.ts": `interface Iterator<T> {
        next(value?: any): IteratorResult<T>;
    }`,
};
const options = {
  suggestOnTriggerCharacters: true,
  quickSuggestions: { strings: true, comments: false, other: true },
  acceptSuggestionOnCommitCharacter: true,
  acceptSuggestionOnEnter: "on",
  wordBasedSuggestions: "allDocuments",
  suggest: {
    showKeywords: true,
    showMethods: true,
    showFields: true,
    showFunctions: true,
    showModules: true,
    showVariables: true,
    filterGraceful: true,
    preview: true,
  },
  hover: {
    enabled: true,
    delay: 300,
    sticky: true,
  },
  renderErrors: true,
  scrollBeyondLastLine: false,
};
const customLibs = `
 declare class PrioriQueue<T>{
   private data: T[];
   constructor(compare?:(a: T, b: T) => number);
   enqueue(data: T): void;
   dequeue(): T | undefined;
   peek(): T | undefined;
   size(): number;
   isEmpty(): boolean;
 }
 declare class ListNode {
  val: number;
  next: ListNode | null;
  constructor(val?:number , next: ListNode | null) ;
 }
 interface LoDashStatic {
        map<T, U>(array: T[], callback: (value: T, index: number, array: T[]) => U): U[];
        filter<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean): T[];
        add(a: number, b: number): number;
        every<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean): boolean;
        some<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean): boolean;
        reduce<T, U>(array: T[], callback: (accumulator: U, value: T, index: number, array: T[]) => U, initialValue: U): U;
        forEach<T>(array: T[], callback: (value: T, index: number, array: T[]) => void): void;
        forEachRight<T>(array: T[], callback: (value: T, index: number, array: T[]) => void): void;
        invert<T>(object: { [index: string]: T }): { [index: string]: string };
        omit<T, K extends keyof T>(object: T, ...paths: K[]): { [index: string]: any };
        pick<T, K extends keyof T>(object: T, ...paths: K[]): { [index: string]: any };
        chunk<T>(array: T[], size?: number): T[][];
        compact<T>(array: T[]): T[];
        concat<T>(array: T[], ...values: Array<T | T[]>): T[];
        difference<T>(array: T[], ...values: T[][]): T[];
        differenceBy<T>(array: T[], values: T[], iteratee: (value: T) => any): T[];
        differenceWith<T>(array: T[], values: T[], comparator: (a: T, b: T) => boolean): T[];
        drop<T>(array: T[], n?: number): T[];
        dropRight<T>(array: T[], n?: number): T[];
        dropRightWhile<T>(array: T[], predicate: (value: T) => boolean): T[];
        dropWhile<T>(array: T[], predicate: (value: T) => boolean): T[];
        fill<T>(array: T[], value: any, start?: number, end?: number): T[];
        findIndex<T>(array: T[], predicate: (value: T) => boolean, fromIndex?: number): number;
        findLastIndex<T>(array: T[], predicate: (value: T) => boolean, fromIndex?: number): number;
        flatten<T>(array: Array<T | T[]>): T[];
        flattenDeep<T>(array: any[]): T[];
        flattenDepth(array: any[], depth?: number): any[];
        fromPairs<T>(pairs: Array<[string, T]>): { [index: string]: T };
        head<T>(array: T[]): T | undefined;
        indexOf<T>(array: T[], value: T, fromIndex?: number): number;
        initial<T>(array: T[]): T[];
        intersection<T>(...arrays: T[][]): T[];
        intersectionBy<T>(arrays: T[][], iteratee: (value: T) => any): T[];
        intersectionWith<T>(arrays: T[][], comparator: (a: T, b: T) => boolean): T[];
        join<T>(array: T[], separator?: string): string;
        last<T>(array: T[]): T | undefined;
        lastIndexOf<T>(array: T[], value: T, fromIndex?: number): number;
        nth<T>(array: T[], n?: number): T | undefined;
        pull<T>(array: T[], ...values: T[]): T[];
        pullAll<T>(array: T[], values: T[]): T[];
        pullAllBy<T>(array: T[], values: T[], iteratee: (value: T) => any): T[];
        pullAllWith<T>(array: T[], values: T[], comparator: (a: T, b: T) => boolean): T[];
        pullAt<T>(array: T[], ...indexes: number[]): T[];
        remove<T>(array: T[], predicate: (value: T) => boolean): T[];
        reverse<T>(array: T[]): T[];
        slice<T>(array: T[], start?: number, end?: number): T[];
        sortedIndex<T>(array: T[], value: T): number;
        sortedIndexBy<T>(array: T[], value: T, iteratee: (value: T) => any): number;
        sortedIndexOf<T>(array: T[], value: T): number;
        sortedLastIndex<T>(array: T[], value: T): number;
        sortedLastIndexBy<T>(array: T[], value: T, iteratee: (value: T) => any): number;
        sortedLastIndexOf<T>(array: T[], value: T): number;
        sortedUniq<T>(array: T[]): T[];
        sortedUniqBy<T>(array: T[], iteratee: (value: T) => any): T[];
        tail<T>(array: T[]): T[];
        take<T>(array: T[], n?: number): T[];
        takeRight<T>(array: T[], n?: number): T[];
        takeRightWhile<T>(array: T[], predicate: (value: T) => boolean): T[];
        takeWhile<T>(array: T[], predicate: (value: T) => boolean): T[];
        union<T>(...arrays: T[][]): T[];
        unionBy<T>(arrays: T[][], iteratee: (value: T) => any): T[];
        unionWith<T>(arrays: T[][], comparator: (a: T, b: T) => boolean): T[];
        uniq<T>(array: T[]): T[];
        uniqBy<T>(array: T[], iteratee: (value: T) => any): T[];
        uniqWith<T>(array: T[], comparator: (a: T, b: T) => boolean): T[];
        unzip<T>(array: T[][]): T[][];
        unzipWith<T, R>(array: T[][], iteratee: (...values: T[]) => R): R[];
        without<T>(array: T[], ...values: T[]): T[];
        xor<T>(...arrays: T[][]): T[];
        xorBy<T>(arrays: T[][], iteratee: (value: T) => any): T[];
        xorWith<T>(arrays: T[][], comparator: (a: T, b: T) => boolean): T[];
        zip<T>(...arrays: T[][]): T[][];
        zipObject(props: string[], values: any[]): { [index: string]: any };
        zipObjectDeep(props: string[], values: any[]): { [index: string]: any };
        zipWith<T, R>(arrays: T[][], iteratee: (...values: T[]) => R): R[];
 }
 declare const _: LoDashStatic;
`;

var globalMonaco = null;

(function () {
  "use strict";

  const TSC_VERSION = "5.9.2";
  const CACHE_KEY_PREFIX = "LeetCode_monaco_V2";
  const FETCH_TIMEOUT = 5000;
  const U = `https://cdn.jsdelivr.net/npm/typescript@${TSC_VERSION}/lib/`;

  async function getLibContent(libName = "") {
    // 先检查缓存
    const cache = GM_getValue(CACHE_KEY_PREFIX + libName);
    if (cache) {
      GM_log(`[Cache] 📦 命中缓存 ${libName}`);
      return cache;
    }

    // 使用 GM_download 替代 fetch
    return new Promise((resolve) => {
      GM_log(`[Download] 🔍 下载 ${libName}`);
      GM_xmlhttpRequest({
        method: "GET",
        url: U + libName,
        onload(data) {
          const text = data.responseText;
          try {
            GM_setValue(CACHE_KEY_PREFIX + libName, text);
            GM_log(`📤 缓存成功了 ${libName}`);
            resolve(text);
          } catch (e) {
            GM_log(`⚠️ 缓存失败了 ${libName}: ${e.message}`);
            resolve(text); // 即使缓存失败,也返回内容
          }
        },
        onerror(error) {
          GM_log(`❌ 下载失败 ${libName}: ${error.message}`);
          // 返回回退内容
          const fallback = fallbacks[libName] ?? "";
          GM_log(`[Fallback] 📥 使用本地定义的模型: ${libName}`);
          resolve(fallback);
        },
        timeout: FETCH_TIMEOUT,
        ontimeout: () => {
          GM_log(`❌ 获取 ${libName} 超时`);
          // 获取回退内容
          const fallback = fallbacks[libName] ?? "";
          GM_log(`[Fallback] 📥 使用本地定义的模型: ${libName}`);
          resolve(fallback);
        },
      });
    });
  }

  function waitForMonaco(target = unsafeWindow) {
    return new Promise((resolve) => {
      const check = () => {
        if (
          target.monaco &&
          target.monaco.languages?.typescript &&
          target.monaco.editor &&
          target.monaco.editor.createModel
        ) {
          resolve(target.monaco);
          return true;
        }
        return false;
      };

      // Check immediately
      if (check()) return;

      const interval = setInterval(() => {
        if (check()) {
          clearInterval(interval);
        }
      }, 200);

      // Extend timeout to 10 seconds
      setTimeout(() => {
        clearInterval(interval);
        resolve(null);
      }, 10000);
    });
  }

  async function enableJSCompletion(target = unsafeWindow) {
    try {
      GM_log("🔍 等待Monaco编辑器加载...");
      globalMonaco = await waitForMonaco(target);
      if (!globalMonaco) {
        GM_log("❌ 失败:未找到Monaco编辑器");
        return;
      }
    } catch (e) {
      GM_log("❌ 初始化出错:" + e.message);
      return;
    }

    GM_log("🔧 开始配置语言服务", globalMonaco);
    const jsDefaults = globalMonaco.languages.typescript.javascriptDefaults;

    // 添加自定义类型(这是基础类型,先加载)
    jsDefaults.addExtraLib(customLibs.trim(), "ts:custom-types.d.ts");

    jsDefaults.setDiagnosticsOptions({
      noSuggestionDiagnostics: false,
      noSyntaxValidation: false,
      noSemanticValidation: false,
    });

    jsDefaults.setCompilerOptions({
      allowJs: true,
      allowNonTsExtensions: true,
      target: 99,
      checkJs: true,
      strict: true,
      noImplicitAny: true,
      noEmit: true,
    });

    // 按顺序加载类型定义,确保依赖正确
    for (const libName of LIBS) {
      try {
        const content = await getLibContent(libName);
        if (content) {
          const uri = `ts:${libName}`;
          jsDefaults.addExtraLib(content, uri);
          GM_log(`✅ 加载完成: ${libName}`);
        } else {
          GM_log(`❌ 无法获取: ${libName}`);
        }
      } catch (e) {
        GM_log(`❌ 处理 ${libName} 时出错: ${e.message}`);
      }
    }

    // 更新现有编辑器选项
    globalMonaco.editor.getEditors?.().forEach((editor) => {
      editor.updateOptions({ ...options });
    });

    // 为新创建的编辑器设置选项
    globalMonaco.editor.onDidCreateEditor((editor) => {
      editor.updateOptions({ ...options });
    });

    // 最后启用模型同步
    jsDefaults.setEagerModelSync(true);

    // 强制刷新语言服务
    globalMonaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions(
      globalMonaco.languages.typescript.javascriptDefaults.getDiagnosticsOptions()
    );

    GM_log(
      "✅【LeetCode 补全代码】 全部的功能已经就绪、试试输入 const ans = [], ans. 有没有补全 ~"
    );
  }

  function detectMonacoInPage() {
    // 检查主页面
    if (document?.querySelector(".monaco-editor") && !globalMonaco) {
      GM_log("发现主页面含有 Monaco!");
      enableJSCompletion(unsafeWindow);
      return true;
    }

    // 检查所有iframe
    const iframes = document.querySelectorAll("iframe");
    for (const iframe of iframes) {
      try {
        const doc = iframe.contentDocument;
        if (doc && doc.querySelector(".monaco-editor")) {
          GM_log("发现 IFrame 页面含有 Monaco!");
          enableJSCompletion(iframe.contentWindow);
          return true;
        }
      } catch (e) {
        // 跨域访问会抛出异常,忽略
        GM_log("检查iframe时出错(可能是跨域):" + e.message);
      }
    }

    return false;
  }
  GM_log("🚀 【LeetCode 补全代码】 插件准备加载了~");

  const observer = new MutationObserver(() => {
    if (detectMonacoInPage()) {
      observer.disconnect();
    }
  });

  observer.observe(document, { childList: true, subtree: true });

  // 多次检查机制,提高成功率
  const checkTimes = [500, 1000, 2000, 3000];
  checkTimes.forEach((delay) => {
    setTimeout(detectMonacoInPage, delay);
  });
})();