Skip to content

Instantly share code, notes, and snippets.

@XYShaoKang
Last active December 23, 2023 03:48
Show Gist options
  • Save XYShaoKang/ac07ab216bb3e51c2549ce8733a00d42 to your computer and use it in GitHub Desktop.
Save XYShaoKang/ac07ab216bb3e51c2549ce8733a00d42 to your computer and use it in GitHub Desktop.
下载力扣提交中的代码

下载力扣提交中时间分布图中的代码,下载的代码会按照[题目]-[语言].md进行命名 可配置项:

  • langs: 需要下载的语言 slug,可以设置多个语言,具体 slug 的值请查看最后面的 getAllLangs 函数中定义的语言 slug
  • questionSlugs: 需要下载代码的题目列表,可以设置多个题目
  • autoCreateSubmission: 是否开启自动创建提交 必须要有对应的提交,才能获取到时间或者内存分布的数据,如果没有提交,则无法获取 如果需要获取的题目中没有对应语言的提交,可以通过代码自动创建一个空提交 当然这样会弄乱提交记录.所以最好是开一个新的进度,或者开一个小号来进行操作
{
  void (async function main() {
    // 需要下载的语言 slug,可以查看最后面的 getAllLangs 中定义的语言 slug
    const langs = ['typescript']
    // 需要下载代码的题目列表
    const questionSlugs = ['two-sum']
    // 是否开启自动创建提交
    // 必须要有对应的提交,才能获取到时间或者内存分布的数据
    // 如果需要获取的题目中没有对应语言的提交,可以通过代码自动创建一个空提交
    // 当然这样会弄乱提交记录.所以最好是开一个新的进度,或者开一个小号来进行操作
    const autoCreateSubmission = false

    const allLangs = getAllLangs()
    const unknowLangs = langs.filter(
      (lang) => !Object.prototype.hasOwnProperty.call(allLangs, lang),
    )
    if (unknowLangs.length) {
      console.log(`发现未知语言类型: ${unknowLangs.join(',')}`)
      return
    }

    const allQuestions = await getAllQuestions()

    for (const questionSlug of questionSlugs) {
      const question = allQuestions.find(
        ({ titleSlug }) => titleSlug === questionSlug,
      )
      const { submissions } = await getSubmissions(questionSlug)
      for (const lang of langs) {
        const { questionFrontendId, translatedTitle, questionId } = question

        let res = `# ${questionFrontendId}. ${translatedTitle} - ${lang}\n\n`
        const submission = submissions.find((s) => s.lang === lang)
        let submissionId
        if (!submission) {
          if (autoCreateSubmission) {
            const submit = await createSubmit(questionSlug, questionId, lang)
            submissionId = submit.submission_id
          } else {
            console.log(`未找到提交,不使用自动创建提交,跳过`)
            continue
          }
        } else {
          submissionId = submission.id
        }

        const runtimeData = await getRuntimeDistribution(submissionId)

        for (const [time] of runtimeData.distribution) {
          const code = await getSubmissionDetail(lang, questionId, time)

          res += `## ${time}ms\n\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`
        }
        download(res, `${questionSlug}-${lang}.md`)
      }
    }
  })()

  function sleep(time) {
    return new Promise(function (resolve, reject) {
      setTimeout(resolve, time)
    })
  }
  /**
   * 创建文件下载
   * @param str 需要下载的内容
   * @param filename 文件名
   */
  function download(str, filename = 'contest.md') {
    const blob = new Blob([str], { type: 'text/plain' })
    const url = URL.createObjectURL(blob)

    const a = document.createElement('a')
    a.href = url
    a.download = filename
    document.body.appendChild(a)
    a.click()

    document.body.removeChild(a)
  }
  function graphqlApi({ method, body }, retry = 1) {
    method = method || 'POST'
    return fetch(`https://leetcode-cn.com/graphql/`, {
      headers: {
        'content-type': 'application/json',
      },
      referrer: `https://leetcode-cn.com/`,
      referrerPolicy: 'strict-origin-when-cross-origin',
      body: JSON.stringify(body),
      method,
      mode: 'cors',
      credentials: 'include',
    }).then((res) => {
      if (res.status === 200) {
        return res.json()
      }

      if (res.status === 429) {
        console.log(`超出接口限制,休息一下,等待第${retry}次重试...`)
        if (retry > 5) {
          throw new Error(
            '已重试 5 次,仍然无法获取,可能力扣君生气了,晚点在试试吧...',
          )
        }
        // 触发限制之后,等一会儿在进行请求
        return sleep(3000).then(() => graphqlApi({ method, body }, retry + 1))
      }

      throw new Error(`未知状态: ${res.status}`)
    })
  }

  /**
   * 获取提交记录
   *
   * @param {string} questionSlug
   * @param {number} limit
   * @param {number} offset
   * @returns {Promise<{
   *    lastKey: string
   *    hasNext: boolean
   *    submissions: {
   *      id: string
   *      statusDisplay: string
   *      lang: string
   *      runtime: string
   *      timestamp: string
   *      url: string
   *      isPending: string
   *      memory: string
   *      submissionComment: {
   *        comment: string
   *        flagType: string
   *        __typename: string
   *      }
   *      __typename: string
   *    }[]
   *    __typename: string
   * }>}
   */
  function getSubmissions(questionSlug, limit = 40, offset = 0) {
    const body = {
      operationName: 'submissions',
      variables: {
        offset,
        limit,
        lastKey: null,
        questionSlug,
      },
      query: /* GraphQL */ `
        query submissions(
          $offset: Int!
          $limit: Int!
          $lastKey: String
          $questionSlug: String!
          $markedOnly: Boolean
          $lang: String
        ) {
          submissionList(
            offset: $offset
            limit: $limit
            lastKey: $lastKey
            questionSlug: $questionSlug
            markedOnly: $markedOnly
            lang: $lang
          ) {
            lastKey
            hasNext
            submissions {
              id
              statusDisplay
              lang
              runtime
              timestamp
              url
              isPending
              memory
              submissionComment {
                comment
                flagType
                __typename
              }
              __typename
            }
            __typename
          }
        }
      `,
    }

    return graphqlApi({ body }).then(({ data }) => data.submissionList)
  }

  /**
   * 获取所有题目信息
   *
   * @returns {Promise<{
   *    translatedTitle: string
   *    title: string
   *    questionFrontendId: string
   *    titleSlug: string
   *    __typename: string
   *    questionId: string
   *    categoryTitle: string
   *    isPaidOnly: boolean
   *    status: string
   *    difficulty: string
   * }[]>}
   */
  function getAllQuestions() {
    const body = {
      operationName: 'allQuestions',
      variables: {},
      query: /* GraphQL */ `
        query allQuestions {
          allQuestionsBeta {
            ...questionSummaryFields
            __typename
          }
        }

        fragment questionSummaryFields on QuestionNode {
          title
          titleSlug
          translatedTitle
          questionId
          questionFrontendId
          status
          difficulty
          isPaidOnly
          categoryTitle
          __typename
        }
      `,
    }

    return graphqlApi({ body }).then(({ data }) => data.allQuestionsBeta)
  }

  function isObject(obj) {
    return typeof obj === 'object' && obj !== null
  }

  function baseApi(url, method = 'GET', body = null, retry = 1) {
    method = method.toUpperCase()

    if (method === 'GET') {
      body = null
    } else {
      body = isObject(body) ? JSON.stringify(body) : body
    }

    return fetch(url, {
      headers: {
        accept: 'application/json, text/plain, */*',
      },
      referrer: 'https://leetcode-cn.com',
      referrerPolicy: 'strict-origin-when-cross-origin',
      body,
      method,
      mode: 'cors',
      credentials: 'include',
    }).then((res) => {
      if (res.status === 200) {
        return res.json()
      }

      if (res.status === 429) {
        console.log(`超出接口限制,休息一下,等待第${retry}次重试...`)
        if (retry > 10) {
          throw new Error(
            `已重试 10 次,仍然无法获取,可能力扣君生气了,晚点在试试吧...`,
          )
        }
        // 触发限制之后,等一会儿在进行请求
        return sleep(20000).then(() => baseApi(url, method, body, retry + 1))
      }

      throw new Error(`未知状态: ${res.status}`)
    })
  }

  /**
   * 获取时间分布数据
   *
   * @param {string} submissionId
   * @returns {Promise<{
   *    lang: string
   *    distribution: [string,number][]
   * }>}
   */
  function getRuntimeDistribution(submissionId) {
    const url = `https://leetcode-cn.com/submissions/api/runtime_distribution/${submissionId}/`
    return baseApi(url).then((data) =>
      JSON.parse(data.runtime_distribution_formatted),
    )
  }

  /**
   * 获取内存分布数据
   *
   * @param {string} submissionId
   * @returns {Promise<{
   *    lang: string
   *    distribution: [string,number][]
   * }>}
   */
  function getMemoryDistribution(submissionId) {
    const url = `https://leetcode-cn.com/submissions/api/memory_distribution/${submissionId}/`
    return baseApi(url)
  }

  /**
   * 获取详细代码
   *
   * @param {string} lang 语言类型
   * @param {string} questionId 题目的 ID
   * @param {string} time 时间
   * @returns
   */
  function getSubmissionDetail(lang, questionId, time) {
    const url = `https://leetcode-cn.com/submissions/api/detail/${questionId}/${lang}/${time}/`

    return baseApi(url).then((data) => data.code)
  }

  /**
   * 创建提交
   *
   * @param {string} questionSlug
   * @param {string} questionId
   * @param {string} lang
   * @returns {Promise<{submission_id: string}>}
   */
  function createSubmit(questionSlug, questionId, lang) {
    const body = {
      question_id: questionId,
      lang: lang,
      typed_code: ``,
      test_mode: false,
      test_judger: '',
      questionSlug,
    }
    return baseApi(
      `https://leetcode-cn.com/problems/${questionSlug}/submit/`,
      'POST',
      body,
    )
  }

  function getAllLangs(params) {
    const LANG_TYPES = [
      {
        slug: 'cpp',
        lang: 'C++',
      },
      {
        slug: 'java',
        lang: 'Java',
      },
      {
        slug: 'python',
        lang: 'Python',
      },
      {
        slug: 'mysql',
        lang: 'MySQL',
      },
      {
        slug: 'c',
        lang: 'C',
      },
      {
        slug: 'csharp',
        lang: 'C#',
      },
      {
        slug: 'javascript',
        lang: 'JavaScript',
      },
      {
        slug: 'ruby',
        lang: 'Ruby',
      },
      {
        slug: 'bash',
        lang: 'Bash',
      },
      {
        slug: 'swift',
        lang: 'Swift',
      },
      {
        slug: 'golang',
        lang: 'Go',
      },
      {
        slug: 'python3',
        lang: 'Python3',
      },
      {
        slug: 'scala',
        lang: 'Scala',
      },
      {
        slug: 'kotlin',
        lang: 'Kotlin',
      },
      {
        slug: 'rust',
        lang: 'Rust',
      },
      {
        slug: 'php',
        lang: 'PHP',
      },
      {
        slug: 'typescript',
        lang: 'TypeScript',
      },
      {
        slug: 'racket',
        lang: 'Racket',
      },
      {
        slug: 'erlang',
        lang: 'Erlang',
      },
      {
        slug: 'elixir',
        lang: 'Elixir',
      },
    ]
    let res = {}
    for (const { slug, lang } of LANG_TYPES) {
      res[slug] = lang
    }
    return res
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment