Greasy Fork is available in English.

Leetcode 助手

禁英文站跳中文站,增加中英站互跳按钮,删除中英站一些广告,复制题解与描述

// ==UserScript==
// @name         Leetcode 助手
// @namespace    http://tampermonkey.net/
// @homepageURL   https://github.com/h-hg/leetcode-optimization
// @supportURL    https://github.com/h-hg/leetcode-optimization/issues
// @version      0.2.0
// @description  禁英文站跳中文站,增加中英站互跳按钮,删除中英站一些广告,复制题解与描述
// @author       Hunter Hwang
// @license      MIT
// @match        https://leetcode.com/*
// @match        https://leetcode.com/problems/*
// @match        https://leetcode.cn/problems/*
// @icon         https://assets.leetcode.com/static_assets/public/icons/favicon-192x192.png
// @run-at       document-idle
// @grant        GM_addStyle
// @grant        GM_webRequest
// ==/UserScript==

(function () {
  'use strict';
  // function handleCopy(e) {
  //   e.stopPropagation();
  //   const copytext = window.getSelection();
  //   const clipdata = e.clipboardData || window.clipboardData;
  //   if (clipdata) {
  //     clipdata.setData("Text", copytext);
  //   }
  // }
  /**
   * @link https://stackoverflow.com/questions/22125865/wait-until-flag-true
   */
  function waitFor(condition, callback) {
    if (!condition()) {
      window.setTimeout(waitFor.bind(null, condition, callback), 1000);
    } else {
      callback();
    }
  }
  function isLoadFinish() {
    var tag = isCNSite() ? 'nav' : 'img';
    return document.querySelector(tag) != null;
  }
  function isCNSite() {
    return location.hostname === 'leetcode.cn'
  }
  function getProblemName() {
    var tmp = location.href.match(/problems\/([^\/]+)/);
    return (tmp != null && tmp[1] != 'all') ? tmp[1] : null;
  }
  function copyCnSolution(callback) {
    // example: https://leetcode.cn/problems/number-of-matching-subsequences/solutions/1973995/pi-pei-zi-xu-lie-de-dan-ci-shu-by-leetco-vki7/
    var match = location.href.match(/problems\/[^\/]+\/solutions\/[^\/]+\/([^\/]+)/);
    if (match == null)
      return;
    const data = JSON.stringify({
      "query": `query discussTopic($slug: String) {
      solutionArticle(slug: $slug, orderBy: DEFAULT) {
        ...solutionArticle
        content
        next {
          slug
          title
        }
        prev {
          slug
          title
        }
      }
    }

    fragment solutionArticle on SolutionArticleNode {
      ipRegion
      rewardEnabled
      canEditReward
      uuid
      title
      slug
      sunk
      chargeType
      status
      identifier
      canEdit
      canSee
      reactionType
      reactionsV2 {
        count
        reactionType
        }
      tags {
        name
        nameTranslated
        slug
        tagType
      }
      createdAt
      thumbnail
      author {
        username
        isDiscussAdmin
        isDiscussStaff
        profile {
          userAvatar
          userSlug
          realName
          reputation
        }
      }
      summary
      topic {
        id
        subscribed
        commentCount
        viewCount
        post {
          id
          status
          voteStatus
          isOwnPost
        }
      }
      byLeetcode
      isMyFavorite
      isMostPopular
      favoriteCount
      isEditorsPick
      hitCount
      videosInfo {
        videoId
        coverUrl
        duration
      }
    }`,
      "variables": {
        "slug": match[1],
      }
    });
    const response = fetch(
      'https://leetcode.cn/graphql/',
      {
        method: 'post',
        body: data,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': data.length,
        },
      }
    );
    response.then(res => res.json()).then(d => {
      navigator.clipboard.writeText(d.data.solutionArticle.content)
    })
  }
  function copyEnsolution() {
    // example: https://leetcode.com/problems/median-of-two-sorted-arrays/discuss/2799909/Python-or-Easy-Solution
    var match = location.href.match(/problems\/[^\/]+\/discuss\/([^\/]+)\/[^\/]+/);
    if (match == null)
      return;
    const data = JSON.stringify({
    "operationName":"DiscussTopic",
    "variables":{
      "topicId": parseInt(match[1]),
    },
    "query":`query DiscussTopic($topicId: Int!) {
      topic(id: $topicId) {
        id
        viewCount
        topLevelCommentCount
        subscribed
        title
        pinned
        tags
        hideFromTrending
        post {
          ...DiscussPost
          __typename
        }
        __typename
      }
    }

    fragment DiscussPost on PostNode {
      id
      voteCount
      voteStatus
      content
      updationDate
      creationDate
      status
      isHidden
      coinRewards {
        ...CoinReward
        __typename
      }
      author {
        isDiscussAdmin
        isDiscussStaff
        username
        nameColor
        activeBadge {
          displayName
          icon
          __typename
        }
        profile {
          userAvatar
          reputation
          __typename
        }
        isActive
        __typename
      }
      authorIsModerator
      isOwnPost
      __typename
    }

    fragment CoinReward on ScoreNode {
      id
      score
      description
      date
      __typename
    }`});
    const response = fetch(
      'https://leetcode.com/graphql/',
      {
        method: 'post',
        body: data,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': data.length,
        },
      }
    );
    response.then(res => res.json()).then(d => {
      navigator.clipboard.writeText(d.data.topic.post.content.replaceAll('\\n', '\n'))
    })
  }
  function copySolution() {
    if (isCNSite())
      copyCnSolution();
    else
      copyEnsolution();
  }
  function copyCnDescription() {
    // example: https://leetcode.cn/problems/number-of-matching-subsequences/description/
    var match = location.href.match(/problems\/([^\/]+)\//);
    if (match == null)
      return;
    const data = JSON.stringify({
      "query": `query questionTranslations($titleSlug: String!) {
      question(titleSlug: $titleSlug) {
        translatedTitle
        translatedContent
      }
    }`,
      "variables": {
        "titleSlug": "number-of-matching-subsequences"
      }
    });
    const response = fetch(
      'https://leetcode.cn/graphql/',
      {
        method: 'post',
        body: data,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': data.length,
        },
      }
    );
    response.then(res => res.json()).then(d => {
      navigator.clipboard.writeText(d.data.question.translatedContent)
    })
  }
  function copyEnDescription() {
    var match = location.href.match(/problems\/([^\/]+)\//);
    if (match == null)
      return;
    const data = JSON.stringify({
      "operationName": "questionData",
      "variables": {
        "titleSlug": match[1],
      },
      "query": `query questionData($titleSlug: String!) {
        question(titleSlug: $titleSlug) {
          questionId
          questionFrontendId
          boundTopicId
          title
          titleSlug
          content
          translatedTitle
          translatedContent
          isPaidOnly
          canSeeQuestion
          difficulty
          likes
          dislikes
          isLiked
          similarQuestions
          exampleTestcases
          categoryTitle
          contributors {
            username
            profileUrl
            avatarUrl
            __typename
          }
          topicTags {
            name
            slug
            translatedName
            __typename
          }
          companyTagStats
          codeSnippets {
            lang
            langSlug
            code
            __typename
          }
          stats
          hints
          solution {
            id
            canSeeDetail
            paidOnly
            hasVideoSolution
            paidOnlyVideo
            __typename
          }
          status
          sampleTestCase
          metaData
          judgerAvailable
          judgeType
          mysqlSchemas
          enableRunCode
          enableTestMode
          enableDebugger
          envInfo
          libraryUrl
          adminUrl
          challengeQuestion {
            id
            date
            incompleteChallengeCount
            streakCount
            type
            __typename
          }
          __typename
      }
      }`
    });
    const response = fetch(
      'https://leetcode.com/graphql/',
      {
        method: 'post',
        body: data,
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': data.length,
        },
      }
    );
    response.then(res => res.json()).then(d => {
      navigator.clipboard.writeText(d.data.question.content);
    })
  }
  function copyDescription() {
    if(isCNSite())
      copyCnDescription();
    else
      copyEnDescription();
  }
  function getOtherLangUrl() {
    var matchRes = location.href.match(/problems\/([^\/]+)\/?([a-z]+)?/);
    if (matchRes == null || matchRes[1] == 'all')
      return null;
    var problemName = matchRes[1], tab = matchRes[2];

    if (tab == 'discuss') {
      tab = 'comments';
    } else if (tab == 'comments') {
      tab = 'discuss';
    } else if (tab == 'submissions' || tab == 'solution') {
    } else {
      tab = '';
    }
    return `https://leetcode${isCNSite() ? '.com' : '.cn'}/problems/${problemName}/${tab}`
  }
  function banAutoJump2Cn() {
    GM_webRequest([
      { selector: 'https://assets.leetcode.cn/*', action: 'cancel' },
    ], function (info, message, details) {
      console.log(info, message, details);
    });
  }
  function html2elem(html) {
    let template = document.createElement('template');
    html = html.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = html;
    return template.content.firstChild;
  }
  function createBall() {
    // add css
    GM_addStyle(`
      .leetcode-wrapper {
        position: fixed;
        top: 30%;
        left: 10px;
        z-index: 1;
      }
      .leetcode-wrapper .btn {
        cursor: move;
        width: 48px;
        height: 48px;
        border-radius: 50%;
        border: 2px solid black;
        opacity: 0.1;
      }
      .leetcode-wrapper:hover .btn {
        background:url(https://leetcode.com/favicon-96x96.png) no-repeat;
        background-size:cover;
        opacity: 0.8;
      }
      .leetcode-wrapper .menu {
        display: none;
        position: absolute;
        background-color: #f9f9f9;
        min-width: 100px;
        box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
      }
      .leetcode-wrapper:hover .menu {
        display: block;
      }
      .leetcode-wrapper .menu a,
      .leetcode-wrapper .menu button {
        color: black;
        padding: 12px 16px;
        text-decoration: none;
        display: block;
        border: none;
        text-align: center;
        background-color: transparent;
        cursor: pointer;
        width: 100%;
      }
      .leetcode-wrapper .menu a:hover,
      .leetcode-wrapper .menu button:hover {
        background-color: #f1f1f1
      }
    `);

    // add html
    var wrapper = html2elem(`
      <div class="leetcode-wrapper">
      <button id="leetcode-btn" class="btn"></button>
        <div class="menu">
          <!-- <a href="", target="_blank"></a> -->
        </div>
      </div>
    `);
    var menu = wrapper.querySelector('.menu');
    waitFor(isLoadFinish, () => {
      // switch to other language
      var link = document.createElement('a');
      link.target = '_blank';
      link.appendChild(document.createTextNode(isCNSite() ? 'English' : '中文'));
      link.href = getOtherLangUrl();
      menu.appendChild(link);
      // copy solution
      var b1 = document.createElement('button')
      b1.textContent = isCNSite() ? '复制题解' : 'Copy discuss';
      b1.onclick = copySolution;
      menu.appendChild(b1)
      // copy problem description
      var b2 = document.createElement('button')
      b2.textContent = isCNSite() ? '复制描述' : 'Copy description';
      b2.onclick = copyDescription;
      menu.appendChild(b2)

      document.body.appendChild(wrapper);
      var btn = document.getElementById('leetcode-btn');
      btn.addEventListener('mouseenter', function (e) {
        menu.firstElementChild.href = getOtherLangUrl();
        // TODO
      })
    })
  }
  // prevent auto jump to leetcode.cn
  if (!isCNSite()) {
    banAutoJump2Cn();
  }

  // AD
  if (isCNSite()) {

  } else {
    GM_addStyle(`
      /* 顶部中文横幅 */
      #cn-banner {
        display: none!important;
      }';
      #region_switcher{
        display: none!important;
      }
      /* 顶部 LeetCode is hiring! Apply Now! */
      .feedback-anchor {
        display: none!important;
      };
    `);
  }

  // problems navigator
  let problemName = getProblemName();
  if (problemName != null) {
    createBall();
  }
  // document.addEventListener("copy", handleCopy, true);
})();