下载力扣提交中时间分布图中的代码,下载的代码会按照[题目]-[语言].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
}
}