Skip to content

Commit d0d0b99

Browse files
committed
feat: 新增直接提交代码至lc功能
1 parent 2a15b88 commit d0d0b99

File tree

6 files changed

+290
-2
lines changed

6 files changed

+290
-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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ 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 account = require("./routes/account");
20+
const submit = require("./routes/submit");
1921
const mockUserInfo = require("./middleware/mockUserInfo");
2022

2123
// error handler
@@ -79,6 +81,8 @@ app.use(lectures.routes(), lectures.allowedMethods());
7981
app.use(dailyProblem.routes(), dailyProblem.allowedMethods());
8082
app.use(my.routes(), my.allowedMethods());
8183
app.use(github.routes(), github.allowedMethods());
84+
app.use(account.routes(), my.allowedMethods());
85+
app.use(submit.routes(), github.allowedMethods());
8286
// error-handling
8387
app.on("error", (err, ctx) => {
8488
console.error("server error", err, ctx);

config/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,9 @@ const userList = [
396396
);
397397

398398
module.exports = {
399+
baseUrl: 'https://leetcode-cn.com',
400+
submitUrl: 'https://leetcode-cn.com/problems/$slug/submit/',
401+
loginUrl: 'https://leetcode-cn.com/accounts/login/',
399402
owner: "leetcode-pp",
400403
repo: "91alg-4",
401404
startTime: startTime.getTime(),

routes/account.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// 用户上传账号名与密码
2+
const router = require("koa-router")();
3+
4+
const {
5+
set91Cookie,
6+
getLeetcodeCookie,
7+
success,
8+
fail
9+
} = require('./utils')
10+
11+
// 设置响应头
12+
router.options('/submitLcAccount', async (ctx, next) => {
13+
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
14+
ctx.set("Access-Control-Allow-Credentials", "true");
15+
ctx.set("Access-Control-Allow-Methods", "*");
16+
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token");
17+
ctx.set("Access-Control-Expose-Headers", "*");
18+
ctx.body = 200;
19+
ctx.status = 200;
20+
});
21+
22+
// 设置响应头
23+
router.post('/submitLcAccount', async (ctx, next) => {
24+
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
25+
ctx.set("Access-Control-Allow-Credentials", "true");
26+
ctx.set("Access-Control-Allow-Methods", "*");
27+
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token");
28+
ctx.set("Access-Control-Expose-Headers", "*");
29+
await next();
30+
});
31+
32+
// 先提交给lc,账号密码是否正确
33+
router.post('/submitLcAccount', async (ctx, next) => {
34+
const {login, password} = ctx.request.body
35+
let result = await getLeetcodeCookie({login, password})
36+
if(result.success){
37+
// todo密码正确时,对密码进行加密
38+
39+
// 将加密后的密文 以及 sessionId、 csrftoken写入cookie中
40+
LEETCODE_SESSION = result.LEETCODE_SESSION
41+
csrftoken = result.csrftoken
42+
set91Cookie({
43+
login,
44+
password,
45+
csrftoken,
46+
LEETCODE_SESSION
47+
}, ctx)
48+
ctx.body = success({isLogin: true})
49+
} else {
50+
ctx.body = fail()
51+
}
52+
});
53+
54+
module.exports = router;

routes/submit.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// 提交题解
2+
const router = require("koa-router")();
3+
const request = require('request');
4+
5+
const {
6+
set91Cookie,
7+
getLeetcodeCookie,
8+
success,
9+
fail
10+
} = require('./utils')
11+
const { submitUrl, baseUrl } = require('../config/index')
12+
13+
// 设置响应头
14+
router.options('/submitCode', async (ctx, next) => {
15+
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
16+
ctx.set("Access-Control-Allow-Credentials", "true");
17+
ctx.set("Access-Control-Allow-Methods", "*");
18+
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token");
19+
ctx.set("Access-Control-Expose-Headers", "*");
20+
ctx.body = 200;
21+
ctx.status = 200;
22+
});
23+
24+
// 设置响应头
25+
router.post('/submitCode', async (ctx, next) => {
26+
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
27+
ctx.set("Access-Control-Allow-Credentials", "true");
28+
ctx.set("Access-Control-Allow-Methods", "*");
29+
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token");
30+
ctx.set("Access-Control-Expose-Headers", "*");
31+
await next();
32+
});
33+
34+
35+
// 用户提交题解
36+
// 先校验cookie中是否有账号密码,没有就让用户先输入再提交
37+
router.post('/submitCode', async (ctx, next) => {
38+
const userName = ctx.cookies.get('login')
39+
const passwd = ctx.cookies.get('password')
40+
if(!userName || !passwd){
41+
return ctx.response.body = fail({
42+
message: "请先提交账号名与密码后再提交"
43+
});
44+
}
45+
await next();
46+
});
47+
48+
// 先试着用cookie中的csrftoken提交下看是否成功,
49+
router.post('/submitCode', async (ctx, next) => {
50+
const login = ctx.cookies.get('login')
51+
const password = ctx.cookies.get('password')
52+
let LEETCODE_SESSION = ctx.cookies.get('LEETCODE_SESSION')
53+
let csrftoken = ctx.cookies.get('csrftoken')
54+
const problemData = formateSubmitData(ctx.request.body)
55+
56+
// 如果这俩cookie有一个不存在就拿账号密码再去lc请求一个新的
57+
if(!LEETCODE_SESSION || !csrftoken){
58+
const newCookie = await getLeetcodeCookie({login, password})
59+
LEETCODE_SESSION = newCookie.LEETCODE_SESSION
60+
csrftoken = newCookie.csrftoken
61+
set91Cookie({
62+
csrftoken,
63+
LEETCODE_SESSION
64+
}, ctx)
65+
}
66+
67+
// 先试着用cookie中的csrftoken提交下看是否成功,
68+
let result = await submitSolution(problemData, ctx)
69+
if(result.success){
70+
set91Cookie({
71+
csrftoken,
72+
LEETCODE_SESSION
73+
}, ctx)
74+
return ctx.body = success(result)
75+
}
76+
77+
// 如果403就用账号密码获取最新csrftoken,再提交一遍
78+
let { statusCode } = result
79+
if(statusCode === 403){
80+
const newCookie = await getLeetcodeCookie({login, password})
81+
LEETCODE_SESSION = newCookie.LEETCODE_SESSION
82+
csrftoken = newCookie.csrftoken
83+
84+
let retryResult = await submitSolution(problemData, ctx)
85+
if(retryResult.success){
86+
set91Cookie({
87+
csrftoken,
88+
LEETCODE_SESSION
89+
}, ctx)
90+
return ctx.body = success(retryResult)
91+
}
92+
}
93+
94+
// 如果还是失败,就提示用户重新输入账号名与密码
95+
return ctx.response.body = fail({
96+
message: "提交失败,请重新输入账号名与密码后再提交!"
97+
});
98+
});
99+
100+
function submitSolution(problem, ctx){
101+
const LEETCODE_SESSION = ctx.cookies.get('LEETCODE_SESSION')
102+
const csrftoken = ctx.cookies.get('csrftoken')
103+
const opts = {}
104+
opts.method = 'POST';
105+
opts.url = submitUrl.replace('$slug', problem.slug);
106+
opts.headers = {};
107+
opts.headers.Origin = baseUrl;
108+
opts.headers.Referer = problem.link;
109+
opts.headers.Cookie = `LEETCODE_SESSION=${LEETCODE_SESSION};csrftoken=${csrftoken};`;
110+
opts.headers['X-csrftoken'] = csrftoken;
111+
opts.headers['X-Requested-With'] = 'XMLHttpRequest';
112+
opts.json = true;
113+
opts._delay = 1; // in seconds
114+
opts.body = problem || {};
115+
116+
return new Promise(res => {
117+
request(opts, function(e, resp, body) {
118+
if(e){
119+
return res({success: false, statusCode: resp.statusCode})
120+
}
121+
body.success = true
122+
body.statusCode = resp.statusCode
123+
return res(body)
124+
});
125+
})
126+
}
127+
128+
// 整理提交题解时的数据格式
129+
function formateSubmitData(problem = {}){
130+
return Object.assign(problem, {
131+
judge_type: 'large',
132+
lang: problem.lang,
133+
question_id: parseInt(problem.id, 10),
134+
test_mode: false,
135+
typed_code: problem.code
136+
})
137+
}
138+
139+
module.exports = router;

routes/utils.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const request = require('request');
2+
const { baseUrl, loginUrl } = require('../config/index')
3+
4+
// 设置cookie
5+
function set91Cookie (data, ctx){
6+
for(let key in data){
7+
ctx.cookies.set(key, data[key], {
8+
maxAge: 365 * 24 * 60 * 60 * 1000,
9+
httpOnly: true
10+
})
11+
}
12+
}
13+
14+
// 从leetcode的请求中获取cookie值
15+
function getDataFromLcResponse(resp, key) {
16+
const cookies = resp.headers['set-cookie'];
17+
if (!cookies) return null;
18+
19+
for (let i = 0; i < cookies.length; ++i) {
20+
const sections = cookies[i].split(';');
21+
for (let j = 0; j < sections.length; ++j) {
22+
const kv = sections[j].trim().split('=');
23+
if (kv[0] === key) return kv[1];
24+
}
25+
}
26+
return null;
27+
};
28+
29+
// 发送一个登录请求,获取leetcode中 LEETCODE_SESSION、csrftoken两个cookie
30+
function getLeetcodeCookie({login, password}){
31+
return new Promise((resolve, reject) => {
32+
// 先发前置请求获取csrftoken
33+
request(loginUrl, function(e, resp, body) {
34+
let csrftoken = getDataFromLcResponse(resp, 'csrftoken');
35+
let LEETCODE_SESSION
36+
const opts = {
37+
url: loginUrl,
38+
headers: {
39+
Origin: baseUrl,
40+
Referer: loginUrl,
41+
Cookie: `csrftoken=${csrftoken};`
42+
},
43+
form: {
44+
csrfmiddlewaretoken: csrftoken,
45+
login,
46+
password,
47+
}
48+
};
49+
50+
// 再发一次请求获取csrftoken、LEETCODE_SESSION
51+
request.post(opts, function(e, resp, body) {
52+
if (resp.statusCode !== 302) {
53+
return resolve({success: false, message: 'pwd invaid'})
54+
}
55+
LEETCODE_SESSION = getDataFromLcResponse(resp, 'LEETCODE_SESSION');
56+
csrftoken = getDataFromLcResponse(resp, 'csrftoken');
57+
resolve({
58+
success: true,
59+
LEETCODE_SESSION,
60+
csrftoken
61+
})
62+
});
63+
});
64+
})
65+
}
66+
67+
function success(data) {
68+
return {
69+
success: true,
70+
data,
71+
};
72+
}
73+
74+
function fail({ message, code = 10001 }) {
75+
return {
76+
success: false,
77+
data: null,
78+
message,
79+
code,
80+
};
81+
}
82+
module.exports = {
83+
set91Cookie,
84+
getDataFromLcResponse,
85+
getLeetcodeCookie,
86+
success,
87+
fail
88+
}

0 commit comments

Comments
 (0)