getmdfromleetcode 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/index.js +101 -13
- package/lib/markdownFormatter.js +535 -112
- package/lib/problemDataFetcher.js +44 -12
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- 支持多种编程语言的代码展示
|
|
14
14
|
- 数学公式自动转换为 LaTeX 格式
|
|
15
15
|
- 支持将生成内容复制到系统剪贴板
|
|
16
|
+
- 支持只输出题目内容或只输出题解内容
|
|
16
17
|
|
|
17
18
|
## 安装
|
|
18
19
|
|
|
@@ -62,6 +63,8 @@ getmdfromleetcode -u <leetcode-problem-url>
|
|
|
62
63
|
- `-e, --english`: 切换到英文内容显示
|
|
63
64
|
- `-r, --raw`: 输出原始 HTML 格式内容
|
|
64
65
|
- `-c, --clipboard`: 将输出内容复制到系统剪贴板
|
|
66
|
+
- `-s, --solution-only`: 只输出题解内容
|
|
67
|
+
- `-p, --problem-only`: 只输出题目内容
|
|
65
68
|
|
|
66
69
|
示例:
|
|
67
70
|
```bash
|
|
@@ -77,6 +80,12 @@ getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -r
|
|
|
77
80
|
# 获取题目内容并复制到剪贴板
|
|
78
81
|
getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -c
|
|
79
82
|
|
|
83
|
+
# 只获取题解内容
|
|
84
|
+
getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -s
|
|
85
|
+
|
|
86
|
+
# 只获取题目内容
|
|
87
|
+
getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -p
|
|
88
|
+
|
|
80
89
|
# 组合使用多个参数
|
|
81
90
|
getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -e -c
|
|
82
91
|
```
|
package/index.js
CHANGED
|
@@ -11,7 +11,12 @@ import { hideBin } from 'yargs/helpers';
|
|
|
11
11
|
import { fetchProblemDataViaGraphQL, fetchProblemDataFromPage } from './lib/problemDataFetcher.js';
|
|
12
12
|
import { formatAsMarkdown } from './lib/markdownFormatter.js';
|
|
13
13
|
import { execSync } from 'child_process';
|
|
14
|
-
import { writeFileSync } from 'fs';
|
|
14
|
+
import { writeFileSync, readFileSync } from 'fs';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { dirname, join } from 'path';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
15
20
|
|
|
16
21
|
// 解析命令行参数
|
|
17
22
|
const argv = yargs(hideBin(process.argv))
|
|
@@ -39,13 +44,42 @@ const argv = yargs(hideBin(process.argv))
|
|
|
39
44
|
description: 'Copy output to clipboard',
|
|
40
45
|
default: false
|
|
41
46
|
})
|
|
47
|
+
.option('username', {
|
|
48
|
+
alias: 'n',
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Username for specific solution author',
|
|
51
|
+
default: 'endlesscheng'
|
|
52
|
+
})
|
|
53
|
+
.option('solutionOnly', {
|
|
54
|
+
alias: 's',
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description: 'Output solution only',
|
|
57
|
+
default: false
|
|
58
|
+
})
|
|
59
|
+
.option('problemOnly', {
|
|
60
|
+
alias: 'p',
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
description: 'Output problem content only',
|
|
63
|
+
default: false
|
|
64
|
+
})
|
|
65
|
+
.option('flag', {
|
|
66
|
+
alias: 'f',
|
|
67
|
+
type: 'boolean',
|
|
68
|
+
description: 'Output tags and difficulty only',
|
|
69
|
+
default: false
|
|
70
|
+
})
|
|
71
|
+
.option('output', {
|
|
72
|
+
alias: 'o',
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'Output file path'
|
|
75
|
+
})
|
|
42
76
|
.help()
|
|
43
77
|
.alias('help', 'h')
|
|
44
78
|
.argv;
|
|
45
79
|
|
|
46
80
|
// 主函数
|
|
47
81
|
async function main() {
|
|
48
|
-
const { url, english, raw, clipboard } = argv;
|
|
82
|
+
const { url, english, raw, clipboard, username, solutionOnly, problemOnly, flag, output } = argv;
|
|
49
83
|
const language = english ? 'english' : 'chinese';
|
|
50
84
|
const rawOutput = raw;
|
|
51
85
|
|
|
@@ -54,24 +88,78 @@ async function main() {
|
|
|
54
88
|
|
|
55
89
|
// 首先尝试通过GraphQL API获取数据
|
|
56
90
|
try {
|
|
57
|
-
questionData = await fetchProblemDataViaGraphQL(url, language);
|
|
91
|
+
questionData = await fetchProblemDataViaGraphQL(url, language, username);
|
|
58
92
|
} catch (apiError) {
|
|
59
93
|
console.error('Failed to fetch data via GraphQL API, trying to fetch from page...', apiError.message);
|
|
60
|
-
|
|
61
|
-
questionData = await fetchProblemDataFromPage(url, language);
|
|
94
|
+
questionData = await fetchProblemDataFromPage(url, language, username);
|
|
62
95
|
}
|
|
63
96
|
|
|
64
97
|
// 添加语言信息到 questionData
|
|
65
98
|
questionData.isEnglish = english;
|
|
66
99
|
|
|
67
|
-
//
|
|
68
|
-
|
|
100
|
+
// 根据solutionOnly或problemOnly参数决定输出内容
|
|
101
|
+
let outputContent;
|
|
102
|
+
if (solutionOnly && problemOnly) {
|
|
103
|
+
console.log('Cannot use both solutionOnly and problemOnly at the same time.');
|
|
104
|
+
return;
|
|
105
|
+
} else if (solutionOnly) {
|
|
106
|
+
// 只输出题解
|
|
107
|
+
if (questionData.solution) {
|
|
108
|
+
outputContent = formatAsMarkdown(questionData, rawOutput, true);
|
|
109
|
+
} else {
|
|
110
|
+
console.log('No solution found for the given problem.');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
} else if (problemOnly) {
|
|
114
|
+
// 只输出题目内容
|
|
115
|
+
outputContent = formatAsMarkdown(questionData, rawOutput, false, true);
|
|
116
|
+
} else if (flag) {
|
|
117
|
+
// 只输出标签和难度
|
|
118
|
+
let result = '';
|
|
119
|
+
|
|
120
|
+
// 添加难度
|
|
121
|
+
if (questionData.difficulty) {
|
|
122
|
+
const difficultyLabel = questionData.isEnglish ? 'Difficulty: ' : '难度: ';
|
|
123
|
+
result += `${difficultyLabel}${questionData.difficulty}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 添加标签
|
|
127
|
+
if (questionData.topicTags && questionData.topicTags.length > 0) {
|
|
128
|
+
if (result) result += '\n'; // 如果已经有内容了,添加换行
|
|
129
|
+
const tagsLabel = questionData.isEnglish ? 'Tags: ' : '标签: ';
|
|
130
|
+
const tags = questionData.topicTags.map(tag =>
|
|
131
|
+
questionData.isEnglish ? tag.name : (tag.translatedName || tag.name)
|
|
132
|
+
).join(', ');
|
|
133
|
+
result += `${tagsLabel}${tags}`;
|
|
134
|
+
} else {
|
|
135
|
+
if (result) result += '\n'; // 如果已经有内容了,添加换行
|
|
136
|
+
const noTagsMessage = questionData.isEnglish ? 'No tags found.' : '未找到标签.';
|
|
137
|
+
result += noTagsMessage;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
outputContent = result;
|
|
141
|
+
} else {
|
|
142
|
+
// 正常输出完整内容
|
|
143
|
+
outputContent = formatAsMarkdown(questionData, rawOutput);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 如果用户指定了输出文件,则写入文件
|
|
147
|
+
if (output) {
|
|
148
|
+
try {
|
|
149
|
+
writeFileSync(output, outputContent, 'utf8');
|
|
150
|
+
console.log(`Content successfully written to: ${output}`);
|
|
151
|
+
} catch (fileError) {
|
|
152
|
+
console.error('Failed to write to file:', fileError.message);
|
|
153
|
+
// 即使写入文件失败,仍然输出内容
|
|
154
|
+
console.log(outputContent);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
69
157
|
|
|
70
158
|
// 如果用户指定了复制到剪贴板的选项,则复制内容
|
|
71
159
|
if (clipboard) {
|
|
72
160
|
try {
|
|
73
161
|
// 将内容写入临时文件
|
|
74
|
-
writeFileSync('/tmp/leetcode_content.txt',
|
|
162
|
+
writeFileSync('/tmp/leetcode_content.txt', outputContent);
|
|
75
163
|
|
|
76
164
|
// 根据操作系统使用不同的命令复制到剪贴板
|
|
77
165
|
if (process.platform === 'darwin') {
|
|
@@ -88,12 +176,12 @@ async function main() {
|
|
|
88
176
|
console.log('Content copied to clipboard successfully!');
|
|
89
177
|
} catch (clipboardError) {
|
|
90
178
|
console.error('Failed to copy content to clipboard:', clipboardError.message);
|
|
91
|
-
// 即使复制到剪贴板失败,仍然输出内容
|
|
92
|
-
console.log(markdownContent);
|
|
93
179
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 如果没有指定输出文件或剪贴板,则输出到控制台
|
|
183
|
+
if (!output && !clipboard) {
|
|
184
|
+
console.log(outputContent);
|
|
97
185
|
}
|
|
98
186
|
} catch (error) {
|
|
99
187
|
console.error('Error fetching or processing problem data:', error.message);
|
package/lib/markdownFormatter.js
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
import * as cheerio from 'cheerio';
|
|
6
6
|
|
|
7
7
|
// 将题目内容格式化为Markdown
|
|
8
|
-
function formatAsMarkdown(question, rawOutput = false) {
|
|
8
|
+
function formatAsMarkdown(question, rawOutput = false, solutionOnly = false, problemOnly = false) {
|
|
9
9
|
const title = question.displayTitle;
|
|
10
10
|
const difficulty = question.difficulty;
|
|
11
11
|
const content = question.displayContent;
|
|
12
12
|
const topicTags = question.topicTags || [];
|
|
13
13
|
const solution = question.solution;
|
|
14
14
|
const isEnglish = question.isEnglish || false;
|
|
15
|
-
const url = question.url || '';
|
|
16
|
-
|
|
15
|
+
const url = question.url || '';
|
|
16
|
+
|
|
17
17
|
// 如果需要原始输出,则返回包含标题和难度的原始HTML内容
|
|
18
18
|
if (rawOutput) {
|
|
19
19
|
let rawResult = '';
|
|
@@ -25,108 +25,71 @@ function formatAsMarkdown(question, rawOutput = false) {
|
|
|
25
25
|
}
|
|
26
26
|
rawResult += content;
|
|
27
27
|
|
|
28
|
-
//
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
// 如果不是只输出题目内容,才添加题解
|
|
29
|
+
if (!problemOnly) {
|
|
30
|
+
// 添加题解内容(如果存在)
|
|
31
|
+
if (solution && solution.content) {
|
|
32
|
+
rawResult += `\n\n<h2>题解</h2>\n`;
|
|
33
|
+
rawResult += solution.content || '';
|
|
34
|
+
} else {
|
|
35
|
+
rawResult += `\n\n<h2>题解</h2>\n`;
|
|
36
|
+
rawResult += `<p>没有找到题解</p>\n`;
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
return rawResult;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
//
|
|
41
|
-
|
|
43
|
+
// 如果只输出题解
|
|
44
|
+
if (solutionOnly) {
|
|
45
|
+
if (solution && solution.content) {
|
|
46
|
+
// 检查 content 是否已经是 Markdown 格式
|
|
47
|
+
// 如果包含 Markdown 标记(如 ##, ```),则直接返回
|
|
48
|
+
if (isMarkdownContent(solution.content)) {
|
|
49
|
+
let result = '';
|
|
50
|
+
if (solution.title) {
|
|
51
|
+
result += `### ${solution.title}\n\n`;
|
|
52
|
+
}
|
|
53
|
+
result += solution.content;
|
|
54
|
+
return result;
|
|
55
|
+
} else {
|
|
56
|
+
// 使用 cheerio 处理题解的 HTML 内容
|
|
57
|
+
const $ = cheerio.load(solution.content, { decodeEntities: false });
|
|
58
|
+
return convertHtmlToMarkdown($, solution.title || '题解');
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
return '没有找到题解';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
42
64
|
|
|
43
|
-
//
|
|
44
|
-
|
|
65
|
+
// 如果只输出题目内容
|
|
66
|
+
if (problemOnly) {
|
|
67
|
+
// 使用cheerio处理HTML格式的描述
|
|
68
|
+
const $ = cheerio.load(content, { decodeEntities: false });
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const $elem = $(elem);
|
|
70
|
+
// 提取文本内容并格式化
|
|
71
|
+
let description = '';
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
text += child.data;
|
|
57
|
-
} else if (child.tagName === 'strong') {
|
|
58
|
-
text += ` **${$child.text()}** `;
|
|
59
|
-
} else if (child.tagName === 'em') {
|
|
60
|
-
text += ` *${$child.text()}* `;
|
|
61
|
-
} else if (child.tagName === 'code') {
|
|
62
|
-
text += `\`${$child.text()}\``;
|
|
63
|
-
} else if (child.tagName === 'sup') {
|
|
64
|
-
// 处理上标(数学公式中的幂)
|
|
65
|
-
text += `^{${$child.text()}}`;
|
|
66
|
-
} else if (child.tagName === 'sub') {
|
|
67
|
-
// 处理下标
|
|
68
|
-
text += `_${$child.text()}`;
|
|
69
|
-
} else {
|
|
70
|
-
// 其他标签直接获取文本
|
|
71
|
-
text += $child.text();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
text = text.trim();
|
|
75
|
-
if (text) {
|
|
76
|
-
// 特殊处理示例标题
|
|
77
|
-
if (text.startsWith('**示例') || text.startsWith('**Example')) {
|
|
78
|
-
// 去掉前后的**
|
|
79
|
-
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
80
|
-
description += `\n\n## ${text}\n\n`;
|
|
81
|
-
} else if (text.startsWith('**提示') || text.startsWith('**Hint')) {
|
|
82
|
-
// 去掉前后的**
|
|
83
|
-
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
84
|
-
description += `\n\n## ${text}\n`;
|
|
85
|
-
} else if (text.startsWith('**进阶**')) {
|
|
86
|
-
// 去掉前后的**
|
|
87
|
-
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
88
|
-
description += `\n\n## ${text}\n`;
|
|
89
|
-
} else {
|
|
90
|
-
description += text + '\n\n';
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} else if (elem.tagName === 'pre') {
|
|
94
|
-
const codeText = $elem.text();
|
|
95
|
-
if (codeText) {
|
|
96
|
-
description += '```\n' + codeText + '```\n\n';
|
|
97
|
-
}
|
|
98
|
-
} else if (elem.tagName === 'ul' || elem.tagName === 'ol') {
|
|
99
|
-
$elem.children('li').each((j, li) => {
|
|
73
|
+
// 按照HTML中的顺序处理所有元素
|
|
74
|
+
$('body').children().each((i, elem) => {
|
|
75
|
+
const $elem = $(elem);
|
|
76
|
+
|
|
77
|
+
if (elem.tagName === 'p') {
|
|
78
|
+
// 处理段落中的内联元素
|
|
100
79
|
let text = '';
|
|
101
|
-
|
|
102
|
-
$(li).contents().each((k, child) => {
|
|
80
|
+
$elem.contents().each((j, child) => {
|
|
103
81
|
const $child = $(child);
|
|
104
82
|
if (child.type === 'text') {
|
|
105
83
|
text += child.data;
|
|
106
84
|
} else if (child.tagName === 'strong') {
|
|
107
85
|
text += ` **${$child.text()}** `;
|
|
86
|
+
} else if (child.tagName === 'em') {
|
|
87
|
+
text += ` *${$child.text()}* `;
|
|
108
88
|
} else if (child.tagName === 'code') {
|
|
109
|
-
|
|
110
|
-
let codeText = '';
|
|
111
|
-
$child.contents().each((l, codeChild) => {
|
|
112
|
-
if (codeChild.type === 'text') {
|
|
113
|
-
codeText += codeChild.data;
|
|
114
|
-
} else if (codeChild.tagName === 'sup') {
|
|
115
|
-
// 处理上标(数学公式中的幂)
|
|
116
|
-
console.log('处理code中的sup标签'); // 调试信息
|
|
117
|
-
codeText += `^${$(codeChild).text()}`;
|
|
118
|
-
} else if (codeChild.tagName === 'sub') {
|
|
119
|
-
// 处理下标
|
|
120
|
-
console.log('处理code中的sub标签'); // 调试信息
|
|
121
|
-
codeText += `_${$(codeChild).text()}`;
|
|
122
|
-
} else {
|
|
123
|
-
codeText += $(codeChild).text();
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
text += `\`${codeText}\``;
|
|
89
|
+
text += `\`${$child.text()}\``;
|
|
127
90
|
} else if (child.tagName === 'sup') {
|
|
128
91
|
// 处理上标(数学公式中的幂)
|
|
129
|
-
text +=
|
|
92
|
+
text += `^{${$child.text()}}`;
|
|
130
93
|
} else if (child.tagName === 'sub') {
|
|
131
94
|
// 处理下标
|
|
132
95
|
text += `_${$child.text()}`;
|
|
@@ -135,16 +98,120 @@ function formatAsMarkdown(question, rawOutput = false) {
|
|
|
135
98
|
text += $child.text();
|
|
136
99
|
}
|
|
137
100
|
});
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
101
|
+
text = text.trim();
|
|
102
|
+
if (text) {
|
|
103
|
+
// 特殊处理示例标题
|
|
104
|
+
if (text.startsWith('**示例') || text.startsWith('**Example')) {
|
|
105
|
+
// 去掉前后的**
|
|
106
|
+
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
107
|
+
description += `\n\n## ${text}\n\n`;
|
|
108
|
+
} else if (text.startsWith('**提示') || text.startsWith('**Hint')) {
|
|
109
|
+
// 去掉前后的**
|
|
110
|
+
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
111
|
+
description += `\n\n## ${text}\n`;
|
|
112
|
+
} else if (text.startsWith('**进阶**')) {
|
|
113
|
+
// 去掉前后的**
|
|
114
|
+
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
115
|
+
description += `\n\n## ${text}\n`;
|
|
116
|
+
} else {
|
|
117
|
+
description += text + '\n\n';
|
|
118
|
+
}
|
|
141
119
|
}
|
|
142
|
-
})
|
|
143
|
-
|
|
120
|
+
} else if (elem.tagName === 'pre') {
|
|
121
|
+
const codeText = $elem.text();
|
|
122
|
+
if (codeText) {
|
|
123
|
+
description += '```\n' + codeText + '```\n\n';
|
|
124
|
+
}
|
|
125
|
+
} else if (elem.tagName === 'ul' || elem.tagName === 'ol') {
|
|
126
|
+
$elem.children('li').each((j, li) => {
|
|
127
|
+
let text = '';
|
|
128
|
+
// 保留li元素中的所有内容,包括strong标签
|
|
129
|
+
$(li).contents().each((k, child) => {
|
|
130
|
+
const $child = $(child);
|
|
131
|
+
if (child.type === 'text') {
|
|
132
|
+
text += child.data;
|
|
133
|
+
} else if (child.tagName === 'strong') {
|
|
134
|
+
text += ` **${$child.text()}** `;
|
|
135
|
+
} else if (child.tagName === 'code') {
|
|
136
|
+
// 特殊处理code标签中的sup和sub标签
|
|
137
|
+
let codeText = '';
|
|
138
|
+
$child.contents().each((l, codeChild) => {
|
|
139
|
+
if (codeChild.type === 'text') {
|
|
140
|
+
codeText += codeChild.data;
|
|
141
|
+
} else if (codeChild.tagName === 'sup') {
|
|
142
|
+
codeText += `^${$(codeChild).text()}`;
|
|
143
|
+
} else if (codeChild.tagName === 'sub') {
|
|
144
|
+
codeText += `_${$(codeChild).text()}`;
|
|
145
|
+
} else {
|
|
146
|
+
codeText += $(codeChild).text();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
text += `\`${codeText}\``;
|
|
150
|
+
} else if (child.tagName === 'sup') {
|
|
151
|
+
text += `^${$child.text()}`;
|
|
152
|
+
} else if (child.tagName === 'sub') {
|
|
153
|
+
text += `_${$child.text()}`;
|
|
154
|
+
} else {
|
|
155
|
+
text += $child.text();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
if (text.trim()) {
|
|
159
|
+
description += `- ${text.trim()}\n`;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
description += '\n';
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// 构建Markdown输出,只包含题目相关信息
|
|
167
|
+
let markdown = '';
|
|
168
|
+
if (title) {
|
|
169
|
+
markdown += `# ${title}\n\n`;
|
|
144
170
|
}
|
|
145
|
-
});
|
|
146
171
|
|
|
147
|
-
|
|
172
|
+
if (difficulty) {
|
|
173
|
+
markdown += `**Difficulty:** ${difficulty}\n\n`;
|
|
174
|
+
} else {
|
|
175
|
+
markdown += `**Difficulty:** 未找到\n\n`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 添加题目标签
|
|
179
|
+
if (topicTags.length > 0) {
|
|
180
|
+
const tags = topicTags.map(tag =>
|
|
181
|
+
tag.translatedName || tag.name
|
|
182
|
+
).join(', ');
|
|
183
|
+
markdown += `**Tags:** ${tags}\n\n`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (description) {
|
|
187
|
+
// 格式化描述内容
|
|
188
|
+
let formattedDescription = description;
|
|
189
|
+
|
|
190
|
+
// 仅保留解释部分的处理
|
|
191
|
+
formattedDescription = formattedDescription.replace(/解释:/g, '\n**解释:** ');
|
|
192
|
+
formattedDescription = formattedDescription.replace(/Explanation:/g, '\n**Explanation:** ');
|
|
193
|
+
|
|
194
|
+
// 清理多余的空白字符
|
|
195
|
+
formattedDescription = formattedDescription.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
196
|
+
formattedDescription = formattedDescription.replace(/^ +/gm, '');
|
|
197
|
+
|
|
198
|
+
// 根据语言显示不同的标题
|
|
199
|
+
const descriptionTitle = isEnglish ? "Description" : "题目描述";
|
|
200
|
+
markdown += `## ${descriptionTitle}\n\n${formattedDescription}\n`;
|
|
201
|
+
} else {
|
|
202
|
+
const descriptionTitle = isEnglish ? "Description" : "题目描述";
|
|
203
|
+
markdown += `## ${descriptionTitle}\n\n未能提取到题目描述\n\n`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 添加题目来源
|
|
207
|
+
if (url) {
|
|
208
|
+
markdown += `\n\n## SOURCE\n\n[${title}](${url})\n\n`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return markdown;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 构建完整Markdown输出(包含题解)
|
|
148
215
|
let markdown = '';
|
|
149
216
|
if (title) {
|
|
150
217
|
markdown += `# ${title}\n\n`;
|
|
@@ -164,12 +231,110 @@ function formatAsMarkdown(question, rawOutput = false) {
|
|
|
164
231
|
markdown += `**Tags:** ${tags}\n\n`;
|
|
165
232
|
}
|
|
166
233
|
|
|
167
|
-
if (
|
|
234
|
+
if (content) {
|
|
235
|
+
// 使用cheerio处理HTML格式的描述
|
|
236
|
+
const $ = cheerio.load(content, { decodeEntities: false });
|
|
237
|
+
|
|
238
|
+
// 提取文本内容并格式化
|
|
239
|
+
let description = '';
|
|
240
|
+
|
|
241
|
+
// 按照HTML中的顺序处理所有元素
|
|
242
|
+
$('body').children().each((i, elem) => {
|
|
243
|
+
const $elem = $(elem);
|
|
244
|
+
|
|
245
|
+
if (elem.tagName === 'p') {
|
|
246
|
+
// 处理段落中的内联元素
|
|
247
|
+
let text = '';
|
|
248
|
+
$elem.contents().each((j, child) => {
|
|
249
|
+
const $child = $(child);
|
|
250
|
+
if (child.type === 'text') {
|
|
251
|
+
text += child.data;
|
|
252
|
+
} else if (child.tagName === 'strong') {
|
|
253
|
+
text += ` **${$child.text()}** `;
|
|
254
|
+
} else if (child.tagName === 'em') {
|
|
255
|
+
text += ` *${$child.text()}* `;
|
|
256
|
+
} else if (child.tagName === 'code') {
|
|
257
|
+
text += `\`${$child.text()}\``;
|
|
258
|
+
} else if (child.tagName === 'sup') {
|
|
259
|
+
// 处理上标(数学公式中的幂)
|
|
260
|
+
text += `^{${$child.text()}}`;
|
|
261
|
+
} else if (child.tagName === 'sub') {
|
|
262
|
+
// 处理下标
|
|
263
|
+
text += `_${$child.text()}`;
|
|
264
|
+
} else {
|
|
265
|
+
// 其他标签直接获取文本
|
|
266
|
+
text += $child.text();
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
text = text.trim();
|
|
270
|
+
if (text) {
|
|
271
|
+
// 特殊处理示例标题
|
|
272
|
+
if (text.startsWith('**示例') || text.startsWith('**Example')) {
|
|
273
|
+
// 去掉前后的**
|
|
274
|
+
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
275
|
+
description += `\n\n## ${text}\n\n`;
|
|
276
|
+
} else if (text.startsWith('**提示') || text.startsWith('**Hint')) {
|
|
277
|
+
// 去掉前后的**
|
|
278
|
+
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
279
|
+
description += `\n\n## ${text}\n`;
|
|
280
|
+
} else if (text.startsWith('**进阶**')) {
|
|
281
|
+
// 去掉前后的**
|
|
282
|
+
text = text.replace(/^\*\*(.*)\*\*$/, '$1');
|
|
283
|
+
description += `\n\n## ${text}\n`;
|
|
284
|
+
} else {
|
|
285
|
+
description += text + '\n\n';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} else if (elem.tagName === 'pre') {
|
|
289
|
+
const codeText = $elem.text();
|
|
290
|
+
if (codeText) {
|
|
291
|
+
description += '```\n' + codeText + '```\n\n';
|
|
292
|
+
}
|
|
293
|
+
} else if (elem.tagName === 'ul' || elem.tagName === 'ol') {
|
|
294
|
+
$elem.children('li').each((j, li) => {
|
|
295
|
+
let text = '';
|
|
296
|
+
// 保留li元素中的所有内容,包括strong标签
|
|
297
|
+
$(li).contents().each((k, child) => {
|
|
298
|
+
const $child = $(child);
|
|
299
|
+
if (child.type === 'text') {
|
|
300
|
+
text += child.data;
|
|
301
|
+
} else if (child.tagName === 'strong') {
|
|
302
|
+
text += ` **${$child.text()}** `;
|
|
303
|
+
} else if (child.tagName === 'code') {
|
|
304
|
+
// 特殊处理code标签中的sup和sub标签
|
|
305
|
+
let codeText = '';
|
|
306
|
+
$child.contents().each((l, codeChild) => {
|
|
307
|
+
if (codeChild.type === 'text') {
|
|
308
|
+
codeText += codeChild.data;
|
|
309
|
+
} else if (codeChild.tagName === 'sup') {
|
|
310
|
+
codeText += `^${$(codeChild).text()}`;
|
|
311
|
+
} else if (codeChild.tagName === 'sub') {
|
|
312
|
+
codeText += `_${$(codeChild).text()}`;
|
|
313
|
+
} else {
|
|
314
|
+
codeText += $(codeChild).text();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
text += `\`${codeText}\``;
|
|
318
|
+
} else if (child.tagName === 'sup') {
|
|
319
|
+
text += `^${$child.text()}`;
|
|
320
|
+
} else if (child.tagName === 'sub') {
|
|
321
|
+
text += `_${$child.text()}`;
|
|
322
|
+
} else {
|
|
323
|
+
text += $child.text();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
if (text.trim()) {
|
|
327
|
+
description += `- ${text.trim()}\n`;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
description += '\n';
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
168
334
|
// 格式化描述内容
|
|
169
335
|
let formattedDescription = description;
|
|
170
336
|
|
|
171
337
|
// 仅保留解释部分的处理
|
|
172
|
-
// 处理解释部分
|
|
173
338
|
formattedDescription = formattedDescription.replace(/解释:/g, '\n**解释:** ');
|
|
174
339
|
formattedDescription = formattedDescription.replace(/Explanation:/g, '\n**Explanation:** ');
|
|
175
340
|
|
|
@@ -181,32 +346,290 @@ function formatAsMarkdown(question, rawOutput = false) {
|
|
|
181
346
|
const descriptionTitle = isEnglish ? "Description" : "题目描述";
|
|
182
347
|
markdown += `## ${descriptionTitle}\n\n${formattedDescription}\n`;
|
|
183
348
|
} else {
|
|
184
|
-
// 根据语言显示不同的标题
|
|
185
349
|
const descriptionTitle = isEnglish ? "Description" : "题目描述";
|
|
186
350
|
markdown += `## ${descriptionTitle}\n\n未能提取到题目描述\n\n`;
|
|
187
351
|
}
|
|
352
|
+
|
|
188
353
|
// 添加题目来源
|
|
189
354
|
if (url) {
|
|
190
355
|
markdown += `\n\n## SOURCE\n\n[${title}](${url})\n\n`;
|
|
191
356
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
357
|
+
|
|
358
|
+
// 添加题解内容(如果存在且不是只输出题目内容)
|
|
359
|
+
if (!problemOnly) {
|
|
360
|
+
if (solution && solution.content) {
|
|
361
|
+
markdown += '\n## 题解\n\n';
|
|
362
|
+
// 检查 content 是否已经是 Markdown 格式
|
|
363
|
+
if (isMarkdownContent(solution.content)) {
|
|
364
|
+
if (solution.title) {
|
|
365
|
+
markdown += `### ${solution.title}\n\n`;
|
|
366
|
+
}
|
|
367
|
+
markdown += solution.content + '\n';
|
|
368
|
+
} else {
|
|
369
|
+
const $ = cheerio.load(solution.content, { decodeEntities: false });
|
|
370
|
+
markdown += convertHtmlToMarkdown($, solution.title || '题解') + '\n';
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
markdown += '\n## 题解\n\n';
|
|
374
|
+
markdown += '没有找到题解\n\n';
|
|
375
|
+
}
|
|
376
|
+
}
|
|
199
377
|
|
|
200
|
-
|
|
201
|
-
|
|
378
|
+
return markdown;
|
|
379
|
+
}
|
|
202
380
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
381
|
+
// 检查内容是否已经是 Markdown 格式
|
|
382
|
+
function isMarkdownContent(content) {
|
|
383
|
+
// 检查是否包含常见的 Markdown 标记
|
|
384
|
+
const markdownPatterns = [
|
|
385
|
+
/^#{1,6}\s+/m, // 标题
|
|
386
|
+
/\`\`\`/, // 代码块
|
|
387
|
+
/\[.+\]\(.+\)/, // 链接
|
|
388
|
+
/\*\*.+\*\*/, // 粗体
|
|
389
|
+
/\*.+\*/, // 斜体
|
|
390
|
+
/^-\s+/m, // 无序列表
|
|
391
|
+
/^\d+\.\s+/m, // 有序列表
|
|
392
|
+
/^\> /m, // 引用
|
|
393
|
+
/\$\$?.+\$\$?/ // 数学公式
|
|
394
|
+
];
|
|
395
|
+
|
|
396
|
+
for (const pattern of markdownPatterns) {
|
|
397
|
+
if (pattern.test(content)) {
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
207
400
|
}
|
|
401
|
+
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
208
404
|
|
|
405
|
+
// 将 HTML 内容转换为 Markdown
|
|
406
|
+
function convertHtmlToMarkdown($, title = '') {
|
|
407
|
+
let markdown = '';
|
|
408
|
+
|
|
409
|
+
// 如果有标题,添加标题
|
|
410
|
+
if (title) {
|
|
411
|
+
markdown += `### ${title}\n\n`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 递归处理每个元素
|
|
415
|
+
function processElement(elem) {
|
|
416
|
+
const $elem = $(elem);
|
|
417
|
+
const tagName = elem.tagName?.toLowerCase() || '';
|
|
418
|
+
|
|
419
|
+
// 跳过注释和脚本
|
|
420
|
+
if (elem.type === 'comment' || tagName === 'script' || tagName === 'style') {
|
|
421
|
+
return '';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
let result = '';
|
|
425
|
+
|
|
426
|
+
switch (tagName) {
|
|
427
|
+
case 'h1':
|
|
428
|
+
case 'h2':
|
|
429
|
+
case 'h3':
|
|
430
|
+
case 'h4':
|
|
431
|
+
case 'h5':
|
|
432
|
+
case 'h6':
|
|
433
|
+
const level = parseInt(tagName.charAt(1));
|
|
434
|
+
result = '\n' + '#'.repeat(level) + ' ' + processInlineElements($elem).trim() + '\n\n';
|
|
435
|
+
break;
|
|
436
|
+
|
|
437
|
+
case 'p':
|
|
438
|
+
result = processInlineElements($elem) + '\n\n';
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
case 'strong':
|
|
442
|
+
case 'b':
|
|
443
|
+
result = '**' + processInlineElements($elem) + '**';
|
|
444
|
+
break;
|
|
445
|
+
|
|
446
|
+
case 'em':
|
|
447
|
+
case 'i':
|
|
448
|
+
result = '*' + processInlineElements($elem) + '*';
|
|
449
|
+
break;
|
|
450
|
+
|
|
451
|
+
case 'code':
|
|
452
|
+
const codeText = $elem.text();
|
|
453
|
+
// 检查是否是多行代码块
|
|
454
|
+
if (codeText.includes('\n')) {
|
|
455
|
+
result = '```\n' + codeText + '\n```\n\n';
|
|
456
|
+
} else {
|
|
457
|
+
result = '`' + codeText + '`';
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
460
|
+
|
|
461
|
+
case 'pre':
|
|
462
|
+
// 处理代码块
|
|
463
|
+
const $code = $elem.find('code').first();
|
|
464
|
+
const langClass = $code.attr('class') || '';
|
|
465
|
+
const langMatch = langClass.match(/language-(\w+)/);
|
|
466
|
+
const lang = langMatch ? langMatch[1] : '';
|
|
467
|
+
const preContent = $code.length > 0 ? $code.text() : $elem.text();
|
|
468
|
+
result = `\`\`\`${lang}\n${preContent}\n\`\`\`\n\n`;
|
|
469
|
+
break;
|
|
470
|
+
|
|
471
|
+
case 'ul':
|
|
472
|
+
$elem.children('li').each((i, li) => {
|
|
473
|
+
result += '- ' + processInlineElements($(li)) + '\n';
|
|
474
|
+
});
|
|
475
|
+
result += '\n';
|
|
476
|
+
break;
|
|
477
|
+
|
|
478
|
+
case 'ol':
|
|
479
|
+
$elem.children('li').each((i, li) => {
|
|
480
|
+
result += `${i + 1}. ` + processInlineElements($(li)) + '\n';
|
|
481
|
+
});
|
|
482
|
+
result += '\n';
|
|
483
|
+
break;
|
|
484
|
+
|
|
485
|
+
case 'li':
|
|
486
|
+
result = processInlineElements($elem) + '\n';
|
|
487
|
+
break;
|
|
488
|
+
|
|
489
|
+
case 'a':
|
|
490
|
+
const href = $elem.attr('href') || '';
|
|
491
|
+
const linkText = processInlineElements($elem);
|
|
492
|
+
result = `[${linkText}](${href})`;
|
|
493
|
+
break;
|
|
494
|
+
|
|
495
|
+
case 'blockquote':
|
|
496
|
+
result = '> ' + processInlineElements($elem).replace(/\n/g, '\n> ') + '\n\n';
|
|
497
|
+
break;
|
|
498
|
+
|
|
499
|
+
case 'br':
|
|
500
|
+
result = '\n';
|
|
501
|
+
break;
|
|
502
|
+
|
|
503
|
+
case 'hr':
|
|
504
|
+
result = '\n---\n\n';
|
|
505
|
+
break;
|
|
506
|
+
|
|
507
|
+
case 'table':
|
|
508
|
+
result = '\n' + processTable($elem) + '\n';
|
|
509
|
+
break;
|
|
510
|
+
|
|
511
|
+
case 'img':
|
|
512
|
+
const src = $elem.attr('src') || '';
|
|
513
|
+
const alt = $elem.attr('alt') || '';
|
|
514
|
+
result = `\n\n`;
|
|
515
|
+
break;
|
|
516
|
+
|
|
517
|
+
case 'div':
|
|
518
|
+
case 'section':
|
|
519
|
+
case 'article':
|
|
520
|
+
case 'span':
|
|
521
|
+
$elem.contents().each((i, child) => {
|
|
522
|
+
if (child.type === 'text') {
|
|
523
|
+
const text = child.data.trim();
|
|
524
|
+
if (text) {
|
|
525
|
+
result += text + ' ';
|
|
526
|
+
}
|
|
527
|
+
} else if (child.tagName) {
|
|
528
|
+
result += processElement(child);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
if (result.trim()) {
|
|
532
|
+
result += '\n\n';
|
|
533
|
+
}
|
|
534
|
+
break;
|
|
535
|
+
|
|
536
|
+
default:
|
|
537
|
+
// 处理其他标签,递归处理其子元素
|
|
538
|
+
$elem.contents().each((i, child) => {
|
|
539
|
+
if (child.type === 'text') {
|
|
540
|
+
result += child.data;
|
|
541
|
+
} else if (child.tagName) {
|
|
542
|
+
result += processElement(child);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return result;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// 处理内联元素
|
|
552
|
+
function processInlineElements($elem) {
|
|
553
|
+
let result = '';
|
|
554
|
+
$elem.contents().each((i, child) => {
|
|
555
|
+
if (child.type === 'text') {
|
|
556
|
+
result += child.data;
|
|
557
|
+
} else if (child.tagName) {
|
|
558
|
+
const tagName = child.tagName?.toLowerCase() || '';
|
|
559
|
+
if (['sup', 'sub'].includes(tagName)) {
|
|
560
|
+
const content = processInlineElements($(child));
|
|
561
|
+
result += tagName === 'sup' ? `^${content}` : `_${content}`;
|
|
562
|
+
} else {
|
|
563
|
+
result += processElement(child);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// 处理表格
|
|
571
|
+
function processTable($table) {
|
|
572
|
+
let tableMarkdown = '';
|
|
573
|
+
|
|
574
|
+
// 收集所有行
|
|
575
|
+
const rows = [];
|
|
576
|
+
|
|
577
|
+
// 处理表头
|
|
578
|
+
const $thead = $table.find('thead').first();
|
|
579
|
+
if ($thead.length) {
|
|
580
|
+
$thead.find('tr').each((i, tr) => {
|
|
581
|
+
const $tr = $(tr);
|
|
582
|
+
const $cells = $tr.find('th');
|
|
583
|
+
if ($cells.length) {
|
|
584
|
+
rows.push({
|
|
585
|
+
cells: $cells.map((j, cell) => processInlineElements($(cell))).get(),
|
|
586
|
+
isHeader: true
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// 处理表体
|
|
593
|
+
const $tbody = $table.find('tbody').first();
|
|
594
|
+
const $rows = $tbody.length ? $tbody.find('tr') : $table.find('tr');
|
|
595
|
+
$rows.each((i, tr) => {
|
|
596
|
+
const $tr = $(tr);
|
|
597
|
+
const $cells = $tr.find('td, th');
|
|
598
|
+
if ($cells.length) {
|
|
599
|
+
rows.push({
|
|
600
|
+
cells: $cells.map((j, cell) => processInlineElements($(cell))).get(),
|
|
601
|
+
isHeader: false
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
if (rows.length > 0) {
|
|
607
|
+
// 添加表头行
|
|
608
|
+
const headerRow = rows[0];
|
|
609
|
+
tableMarkdown += '| ' + headerRow.cells.join(' | ') + ' |\n';
|
|
610
|
+
|
|
611
|
+
// 添加分隔行
|
|
612
|
+
tableMarkdown += '| ' + headerRow.cells.map(() => '---').join(' | ') + ' |\n';
|
|
613
|
+
|
|
614
|
+
// 添加数据行
|
|
615
|
+
for (let i = headerRow.isHeader ? 1 : 0; i < rows.length; i++) {
|
|
616
|
+
const row = rows[i];
|
|
617
|
+
tableMarkdown += '| ' + row.cells.join(' | ') + ' |\n';
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return tableMarkdown;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// 处理 body 中的所有内容
|
|
625
|
+
$('body').contents().each((i, elem) => {
|
|
626
|
+
markdown += processElement(elem);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// 清理多余的空行
|
|
630
|
+
markdown = markdown.replace(/\n{3,}/g, '\n\n');
|
|
631
|
+
|
|
209
632
|
return markdown;
|
|
210
633
|
}
|
|
211
634
|
|
|
212
|
-
export { formatAsMarkdown };
|
|
635
|
+
export { formatAsMarkdown };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
// 使用GraphQL API获取LeetCode题目数据
|
|
6
|
-
async function fetchProblemDataViaGraphQL(url, language = 'chinese') {
|
|
6
|
+
async function fetchProblemDataViaGraphQL(url, language = 'chinese', username = null) {
|
|
7
7
|
// 从URL中提取题目slug
|
|
8
8
|
const match = url.match(/\/problems\/([^/]+)/);
|
|
9
9
|
const slug = match ? match[1] : null;
|
|
@@ -35,8 +35,8 @@ async function fetchProblemDataViaGraphQL(url, language = 'chinese') {
|
|
|
35
35
|
`;
|
|
36
36
|
|
|
37
37
|
const solutionQuery = `
|
|
38
|
-
query solutionData($slug: String
|
|
39
|
-
questionSolutionArticles(first:
|
|
38
|
+
query solutionData($slug: String!, $first: Int) {
|
|
39
|
+
questionSolutionArticles(first: $first, questionSlug: $slug) {
|
|
40
40
|
edges {
|
|
41
41
|
node {
|
|
42
42
|
title
|
|
@@ -85,7 +85,7 @@ async function fetchProblemDataViaGraphQL(url, language = 'chinese') {
|
|
|
85
85
|
},
|
|
86
86
|
body: JSON.stringify({
|
|
87
87
|
query: solutionQuery,
|
|
88
|
-
variables: { slug: slug }
|
|
88
|
+
variables: { slug: slug, first: 20 } // 增加数量以查找特定用户
|
|
89
89
|
})
|
|
90
90
|
});
|
|
91
91
|
|
|
@@ -114,7 +114,23 @@ async function fetchProblemDataViaGraphQL(url, language = 'chinese') {
|
|
|
114
114
|
if (solutionData && solutionData.data && solutionData.data.questionSolutionArticles &&
|
|
115
115
|
solutionData.data.questionSolutionArticles.edges &&
|
|
116
116
|
solutionData.data.questionSolutionArticles.edges.length > 0) {
|
|
117
|
-
|
|
117
|
+
|
|
118
|
+
if (username) {
|
|
119
|
+
// 尝试查找指定用户名的题解
|
|
120
|
+
const userSolution = solutionData.data.questionSolutionArticles.edges.find(edge =>
|
|
121
|
+
edge.node.author && edge.node.author.username === username
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// 如果找到了指定用户的题解,则使用它;否则使用第一个题解
|
|
125
|
+
if (userSolution) {
|
|
126
|
+
question.solution = userSolution.node;
|
|
127
|
+
} else if (solutionData.data.questionSolutionArticles.edges.length > 0) {
|
|
128
|
+
question.solution = solutionData.data.questionSolutionArticles.edges[0].node;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
// 如果没有指定用户名,则使用第一个题解
|
|
132
|
+
question.solution = solutionData.data.questionSolutionArticles.edges[0].node;
|
|
133
|
+
}
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
// 添加URL到question对象
|
|
@@ -124,7 +140,7 @@ async function fetchProblemDataViaGraphQL(url, language = 'chinese') {
|
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
// 从页面的__NEXT_DATA__中提取题目数据
|
|
127
|
-
async function fetchProblemDataFromPage(url, language = 'chinese') {
|
|
143
|
+
async function fetchProblemDataFromPage(url, language = 'chinese', username = null) {
|
|
128
144
|
// 从URL中提取题目slug
|
|
129
145
|
const match = url.match(/\/problems\/([^/]+)/);
|
|
130
146
|
const slug = match ? match[1] : null;
|
|
@@ -183,7 +199,7 @@ async function fetchProblemDataFromPage(url, language = 'chinese') {
|
|
|
183
199
|
|
|
184
200
|
// 单独获取题解数据,因为页面中可能不包含题解数据
|
|
185
201
|
try {
|
|
186
|
-
const solutionData = await fetchSolutionData(slug);
|
|
202
|
+
const solutionData = await fetchSolutionData(slug, username);
|
|
187
203
|
if (solutionData) {
|
|
188
204
|
question.solution = solutionData;
|
|
189
205
|
}
|
|
@@ -201,12 +217,12 @@ async function fetchProblemDataFromPage(url, language = 'chinese') {
|
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
// 单独获取题解数据
|
|
204
|
-
async function fetchSolutionData(slug) {
|
|
220
|
+
async function fetchSolutionData(slug, username = null) {
|
|
205
221
|
const graphqlUrl = 'https://leetcode.cn/graphql';
|
|
206
222
|
|
|
207
223
|
const solutionQuery = `
|
|
208
|
-
query solutionData($slug: String
|
|
209
|
-
questionSolutionArticles(first:
|
|
224
|
+
query solutionData($slug: String!, $first: Int) {
|
|
225
|
+
questionSolutionArticles(first: $first, questionSlug: $slug) {
|
|
210
226
|
edges {
|
|
211
227
|
node {
|
|
212
228
|
title
|
|
@@ -230,7 +246,7 @@ async function fetchSolutionData(slug) {
|
|
|
230
246
|
},
|
|
231
247
|
body: JSON.stringify({
|
|
232
248
|
query: solutionQuery,
|
|
233
|
-
variables: { slug: slug }
|
|
249
|
+
variables: { slug: slug, first: 20 } // 增加数量以查找特定用户
|
|
234
250
|
})
|
|
235
251
|
});
|
|
236
252
|
|
|
@@ -247,7 +263,23 @@ async function fetchSolutionData(slug) {
|
|
|
247
263
|
if (solutionData.data && solutionData.data.questionSolutionArticles &&
|
|
248
264
|
solutionData.data.questionSolutionArticles.edges &&
|
|
249
265
|
solutionData.data.questionSolutionArticles.edges.length > 0) {
|
|
250
|
-
|
|
266
|
+
|
|
267
|
+
if (username) {
|
|
268
|
+
// 尝试查找指定用户名的题解
|
|
269
|
+
const userSolution = solutionData.data.questionSolutionArticles.edges.find(edge =>
|
|
270
|
+
edge.node.author && edge.node.author.username === username
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// 如果找到了指定用户的题解,则使用它;否则使用第一个题解
|
|
274
|
+
if (userSolution) {
|
|
275
|
+
return userSolution.node;
|
|
276
|
+
} else {
|
|
277
|
+
return solutionData.data.questionSolutionArticles.edges[0].node;
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
// 如果没有指定用户名,则返回第一个题解
|
|
281
|
+
return solutionData.data.questionSolutionArticles.edges[0].node;
|
|
282
|
+
}
|
|
251
283
|
}
|
|
252
284
|
|
|
253
285
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getmdfromleetcode",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "A command line tool to fetch LeetCode problem content and convert it to Markdown",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node index.js"
|
|
12
12
|
},
|
|
13
|
-
"keywords": [
|
|
13
|
+
"keywords": [
|
|
14
|
+
"leetcode",
|
|
15
|
+
"markdown",
|
|
16
|
+
"cli",
|
|
17
|
+
"solution",
|
|
18
|
+
"algorithm"
|
|
19
|
+
],
|
|
14
20
|
"author": "User",
|
|
15
21
|
"license": "MIT",
|
|
16
22
|
"dependencies": {
|
|
@@ -28,4 +34,4 @@
|
|
|
28
34
|
"engines": {
|
|
29
35
|
"node": ">=14.0.0"
|
|
30
36
|
}
|
|
31
|
-
}
|
|
37
|
+
}
|