Skip to content

Commit 14ce54a

Browse files
committed
feat: 新增91打卡至lc功能
1 parent 2a15b88 commit 14ce54a

File tree

5 files changed

+242
-2
lines changed

5 files changed

+242
-2
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
1+
/.idea
22
/node_modules
3-
/static/**/*.md
3+
/static/**/*.md

app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const lectures = require("./routes/lectures");
1616
const github = require("./routes/github");
1717
const fallback = require("./routes/redirect");
1818
const my = require("./routes/my");
19+
const lc = require("./routes/lc");
1920
const mockUserInfo = require("./middleware/mockUserInfo");
2021

2122
// error handler
@@ -79,6 +80,7 @@ app.use(lectures.routes(), lectures.allowedMethods());
7980
app.use(dailyProblem.routes(), dailyProblem.allowedMethods());
8081
app.use(my.routes(), my.allowedMethods());
8182
app.use(github.routes(), github.allowedMethods());
83+
app.use(lc.routes(), lc.allowedMethods());
8284
// error-handling
8385
app.on("error", (err, ctx) => {
8486
console.error("server error", err, ctx);

config/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,18 @@ const userList = [
395395
}))
396396
);
397397

398+
const leetcodeConfig = {
399+
baseUrl: 'https://leetcode-cn.com',
400+
submitUrl: 'https://leetcode-cn.com/problems/$slug/submit/',
401+
loginUrl: 'https://leetcode-cn.com/accounts/login/',
402+
_91UsernameCookieName: 'login', // 在91网站中存lc用户名的cookie的键名
403+
_91PwdCookieName: 'password', // 在91网站中存lc密码的cookie的键名
404+
lcSeesionCookieName: 'LEETCODE_SESSION', // lc存seesionid的 cookie键名
405+
lcCsrftokenCookieName: 'csrftoken' // lc存csrf的 cookie键名
406+
}
407+
398408
module.exports = {
409+
leetcodeConfig,
399410
owner: "leetcode-pp",
400411
repo: "91alg-4",
401412
startTime: startTime.getTime(),

routes/lc.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
2+
const router = require("koa-router")();
3+
const fetch = require("node-fetch");
4+
const {
5+
leetcodeConfig: {
6+
baseUrl,
7+
submitUrl,
8+
_91UsernameCookieName,
9+
_91PwdCookieName,
10+
lcSeesionCookieName,
11+
lcCsrftokenCookieName
12+
}
13+
} = require('../config/index')
14+
const { success, fail } = require('../utils/request')
15+
const { encrypt, decrypt } = require('../utils/crypto')
16+
const { set91Cookie, getLcRequestData } = require('./utils')
17+
18+
// 用户上传lc的账号名与密码
19+
router.post('/api/v1/lc/submitLcAccount', async (ctx) => {
20+
const { login, password } = ctx.request.body
21+
// 先提交给lc,账号密码是否正确
22+
let result = await getLcRequestData({login, password})
23+
if(result.success){
24+
// 密码正确时,对密码进行加密
25+
let encryptPwd = encrypt(password)
26+
// 将加密后的密文 以及 sessionId、 csrftoken 写入cookie中
27+
sessionId = result[lcSeesionCookieName]
28+
csrftoken = result[lcCsrftokenCookieName]
29+
set91Cookie({
30+
[_91UsernameCookieName]: login,
31+
[_91PwdCookieName]: encryptPwd,
32+
[lcSeesionCookieName]: sessionId,
33+
[lcCsrftokenCookieName]: csrftoken,
34+
}, ctx)
35+
ctx.body = success({isLogin: true})
36+
} else {
37+
ctx.body = fail({code: 302, message: '提交失败' })
38+
}
39+
});
40+
41+
// 用户提交题解
42+
router.post('/api/v1/lc/submitCode', async (ctx, next) => {
43+
// 先校验cookie中是否有账号密码,没有就让用户先输入再提交
44+
const userName = ctx.cookies.get(_91UsernameCookieName)
45+
const passwd = ctx.cookies.get(_91PwdCookieName)
46+
if(!userName || !passwd){
47+
return ctx.response.body = fail({
48+
code: 403,
49+
message: "请先提交账号名与密码后再提交"
50+
});
51+
}
52+
53+
// 如果这俩cookie有一个不存在就提示用户重新提交一遍lc的账号密码
54+
let sessionId = ctx.cookies.get(lcSeesionCookieName)
55+
let csrftoken = ctx.cookies.get(lcCsrftokenCookieName)
56+
let requestData = {
57+
[lcSeesionCookieName]: sessionId,
58+
[lcCsrftokenCookieName]: csrftoken
59+
}
60+
if(!sessionId || !csrftoken){
61+
return ctx.response.body = fail({
62+
code: 403,
63+
message: "提交失败,请重新输入账号名与密码后再提交!"
64+
});
65+
}
66+
67+
// 先试着用cookie中的旧csrftoken提交下看是否成功
68+
const problemData = formateSubmitData(ctx.request.body)
69+
let result = (await submitSolution(problemData, requestData)) || {}
70+
if(result.success){
71+
return ctx.body = success(result.data)
72+
}
73+
74+
// 如果403就用账号密码获取最新csrftoken,再提交一遍
75+
if(result.statusCode === 403){
76+
// 获取最新csrftoken
77+
let newRequestData = await getLatestLcRequestData(ctx)
78+
if(!newRequestData.success){
79+
return ctx.response.body = fail({
80+
code: 403,
81+
message: newRequestData.message || "提交失败,请重新输入账号名与密码后再提交!"
82+
});
83+
}
84+
// 更新下91的cookie
85+
set91Cookie(newRequestData, ctx)
86+
87+
// 再提交一遍
88+
let retryResult = await submitSolution(problemData, newRequestData)
89+
if(retryResult.success){
90+
return ctx.body = success(retryResult.data)
91+
}
92+
}
93+
94+
// 如果还是失败,就提示用户重新输入账号名与密码
95+
return ctx.response.body = fail({
96+
code: 403,
97+
message: "提交失败,请重新输入账号名与密码后再提交!"
98+
});
99+
});
100+
101+
// 获取最新的的向leetcode发送请求的必要参数
102+
async function getLatestLcRequestData(ctx){
103+
const userName = ctx.cookies.get(_91UsernameCookieName)
104+
const encryptPassword = ctx.cookies.get(_91PwdCookieName)
105+
const password = decrypt(encryptPassword)
106+
return await getLcRequestData({
107+
[_91UsernameCookieName]: userName,
108+
[_91PwdCookieName]: password
109+
})
110+
}
111+
112+
async function submitSolution(problem, requestData){
113+
let statusCode = 403
114+
const url = submitUrl.replace('$slug', problem.slug);
115+
const sessionId = requestData[lcSeesionCookieName]
116+
const csrftoken = requestData[lcCsrftokenCookieName]
117+
const cookie = `${lcSeesionCookieName}=${sessionId};${lcCsrftokenCookieName}=${csrftoken};`
118+
const opt = {
119+
method: 'POST',
120+
headers: {
121+
Origin: baseUrl,
122+
Referer: problem.link,
123+
Cookie: cookie,
124+
'X-csrftoken': csrftoken,
125+
'X-Requested-With': 'XMLHttpRequest',
126+
},
127+
json: true,
128+
_delay: 1,// in seconds
129+
body: JSON.stringify(problem || {})
130+
}
131+
console.log(opt)
132+
const result = await fetch(url, opt).then((res) => {
133+
statusCode = res.status
134+
return res.json()
135+
});
136+
return {
137+
success: !!result,
138+
statusCode: statusCode,
139+
data: result
140+
}
141+
}
142+
143+
// 整理提交题解时的数据格式
144+
function formateSubmitData(problem = {}){
145+
return Object.assign(problem, {
146+
judge_type: 'large',
147+
lang: problem.lang,
148+
question_id: parseInt(problem.id, 10),
149+
test_mode: false,
150+
typed_code: problem.code
151+
})
152+
}
153+
154+
module.exports = router;

routes/utils.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const fetch = require("node-fetch");
2+
const request = require('request');
3+
const {
4+
leetcodeConfig: {
5+
baseUrl,
6+
loginUrl,
7+
_91UsernameCookieName, // 在91网站中存lc用户名的cookie的键名
8+
_91PwdCookieName, // 在91网站中存lc密码的cookie的键名
9+
lcSeesionCookieName, // lc存seesionid的 cookie键名
10+
lcCsrftokenCookieName // lc存csrf的 cookie键名
11+
}
12+
} = require('../config/index')
13+
14+
// 设置91的cookie并且过期时间为一年
15+
function set91Cookie (data, ctx){
16+
for(let key in data){
17+
ctx.cookies.set(key, data[key], {
18+
maxAge: 365 * 24 * 60 * 60 * 1000,
19+
httpOnly: false
20+
})
21+
}
22+
}
23+
24+
// 从leetcode的请求中获取cookie值
25+
function getCookieFromLc(resp, key) {
26+
const cookies = resp.headers['set-cookie'];
27+
if (!cookies) return null;
28+
29+
for (let i = 0; i < cookies.length; ++i) {
30+
const sections = cookies[i].split(';');
31+
for (let j = 0; j < sections.length; ++j) {
32+
const kv = sections[j].trim().split('=');
33+
if (kv[0] === key) return kv[1];
34+
}
35+
}
36+
return null;
37+
};
38+
39+
// 从leetcode中获取 发送请求的必要参数 LEETCODE_SESSION、csrftoken
40+
async function getLcRequestData(options){
41+
const opt = {
42+
url: loginUrl,
43+
credentials: 'include',
44+
headers: {
45+
credentials: 'include',
46+
Origin: baseUrl,
47+
Referer: loginUrl,
48+
},
49+
form: {
50+
[_91UsernameCookieName]: options[_91UsernameCookieName],
51+
[_91PwdCookieName]: options[_91PwdCookieName]
52+
}
53+
}
54+
return await new Promise(resolve => {
55+
request.post(opt, function(e, resp, body) {
56+
if (resp.statusCode !== 302) {
57+
return resolve({success: false, message: 'pwd invaid'})
58+
}
59+
sessionId = getCookieFromLc(resp, lcSeesionCookieName);
60+
csrftoken = getCookieFromLc(resp, lcCsrftokenCookieName);
61+
resolve({
62+
success: true,
63+
[lcSeesionCookieName]: sessionId,
64+
[lcCsrftokenCookieName]:csrftoken
65+
})
66+
});
67+
})
68+
}
69+
70+
module.exports = {
71+
set91Cookie,
72+
getLcRequestData
73+
}

0 commit comments

Comments
 (0)