Greasy Fork

Greasy Fork is available in English.

Online Judge 标题复制器

这个脚本可以复制各大 OJ 的题目标题,方便整理题号信息并收藏到本地

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                       Online Judge 标题复制器
// @name:zh-CN                 Online Judge 标题复制器
// @name:en                    Online Judge Problem Title Copier
// @namespace                  https://tampermonkey.net
// @version                    0.3.2
// @author                     Jerry Karlbaey
// @description                这个脚本可以复制各大 OJ 的题目标题,方便整理题号信息并收藏到本地
// @description:zh-CN          这个脚本可以复制各大 OJ 的题目标题,方便整理题号信息并收藏到本地
// @description:en             This script can copy the problem titles from major OJs, making it easy to organize problem numbers and save them locally.
// @homepage                   https://github.com/Karlbaey/Karlgo
// @supportURL                 https://github.com/Karlbaey/Karlgo/issues
// @grant                      GM_setClipboard
// @license                    AGPL-3.0-only
// @match                      *://onlinejudge.org/index.php*option=com_onlinejudge*page=show_problem*
// @match                      *://codeforces.com/problemset/problem/*
// @match                      *://codeforces.com/contest/*/problem/*
// @match                      *://leetcode.com/problems/*
// @match                      *://leetcode.cn/problems/*
// @match                      *://www.luogu.com.cn/problem/*
// @match                      *://www.lintcode.com/problem/*
// @match                      *://acm.hdu.edu.cn/*
// ==/UserScript==

(function () {
  "use strict";

  // --- Site Configurations ---
  const siteConfigs = {
    "onlinejudge.org": {
      prefix: "UVa",
      selector: "#col3_content_wrapper h3",
    },
    "codeforces.com": {
      prefix: "CF",
      selector: ".problem-statement .header .title",
      process: (text) => {
        // 1. Extract the match ID and problem code from the URL
        const Path = window.location.pathname;
        const match = Path.match(/\/problem\/(\d+)\/([A-Z]\d?)/);
        if (!match) {
          return text.replace(/\./g, "");
        }
        // match[1] is the competition/problem set ID, such as "2175"
        const contestId = match[1];
        // match[2] is the problem code, such as "B"
        const problemCode = match[2];
        const problemName = text.substring(text.indexOf(".") + 1).trim();
        return `${contestId}${problemCode}_${problemName}`;
      },
    },
    "leetcode.com": {
      prefix: "LC",
      selector: ".text-title-large",
      // Remove dot '.'
      process: (text) => text.replace(/\./g, ""),
    },
    "leetcode.cn": {
      prefix: "LC", // Sync with English site
      selector: ".text-title-large",
      // Remove dot '.'
      process: (text) => text.replace(/\./g, ""),
    },
    "www.luogu.com.cn": {
      prefix: "LG",
      selector: ".lfe-h1",
    },
    "www.lintcode.com": {
      prefix: "Lint",
      selector: ".title-TkVpy",
    },
    "acm.hdu.edu.cn": {
      prefix: "HDU",
      // FIX: Use a more specific selector to target the problem title's h1,
      // which has a unique inline style. This avoids selecting the site's main banner h1.
      selector: "h1",
      process: (text) => {
        const params = new URLSearchParams(window.location.search);
        const problemId = params.get("pid");
        return problemId ? `${problemId}_${text}` : text;
      },
    },
  };

  // --- Core Logic ---

  /**
   * Create and attach a copy button next to the specified element
   * @param {HTMLElement} titleElement - The DOM element where the title is located
   * @param {object} config - Current site configuration
   */
  function create(titleElement, config) {
    if (
      titleElement.nextElementSibling &&
      titleElement.nextElementSibling.classList.contains(
        "oj-title-copier-wrapper"
      )
    ) {
      return;
    }

    const wrapper = document.createElement("div");
    wrapper.className = "oj-title-copier-wrapper";

    Object.assign(wrapper.style, {
      display: "inline-block",
      verticalAlign: "middle",
    });

    const copybutton = document.createElement("button");
    copybutton.textContent = "复制标题";
    copybutton.className = "oj-title-copier-btn";

    Object.assign(copybutton.style, {
      marginLeft: "15px",
      padding: "4px 10px",
      cursor: "pointer",
      border: "1px solid #ccc",
      borderRadius: "4px",
      backgroundColor: "#f0f0f0",
      color: "#333",
      fontSize: "14px",
      fontWeight: "normal",
    });

    copybutton.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();

      const Raw = titleElement.textContent.trim();
      const Clean = config.process ? config.process(Raw) : Raw;
      const Formatted = `${config.prefix}_${Clean.replace(/\s+/g, "_")}`;
      GM_setClipboard(Formatted);

      const originalText = copybutton.textContent;
      copybutton.textContent = "已复制!";
      copybutton.style.backgroundColor = "#d4edda";
      copybutton.style.borderColor = "#c3e6cb";
      setTimeout(() => {
        copybutton.textContent = originalText;
        copybutton.style.backgroundColor = "#f0f0f0";
        copybutton.style.borderColor = "#ccc";
      }, 2000);
    });

    wrapper.appendChild(copybutton);

    titleElement.insertAdjacentElement("afterend", wrapper);
  }

  // --- Script Entry ---
  // Get the configuration of the current site
  const Current = siteConfigs[window.location.hostname];
  if (Current) {
    // Since modern website content may load dynamically, we use setInterval to poll for the title element
    const Find = setInterval(() => {
      const title = document.querySelector(Current.selector);

      if (title) {
        // After finding the element, clear the timer and perform the operation
        clearInterval(Find);
        create(title, Current);
      }
    }, 500); // Check every 500 ms

    // Set a timeout to prevent the element from being permanently unfindable due to changes in the page structure.
    setTimeout(() => {
      clearInterval(Find);
    }, 15000); // Stop trying after 15 seconds
  }
})();