Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const lectures = require("./routes/lectures");
const github = require("./routes/github");
const fallback = require("./routes/redirect");
const my = require("./routes/my");
// const lc = require("./routes/lc");
const lc = require("./routes/lc");
const mockUserInfo = require("./middleware/mockUserInfo");

// error handler
Expand Down Expand Up @@ -80,7 +80,7 @@ app.use(lectures.routes(), lectures.allowedMethods());
app.use(dailyProblem.routes(), dailyProblem.allowedMethods());
app.use(my.routes(), my.allowedMethods());
app.use(github.routes(), github.allowedMethods());
// app.use(lc.routes(), lc.allowedMethods());
app.use(lc.routes(), lc.allowedMethods());
// error-handling
app.on("error", (err, ctx) => {
console.error("server error", err, ctx);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"koa-views": "^6.2.0",
"mongodb": "^3.6.6",
"node-fetch": "^2.6.1",
"pug": "^2.0.3"
"pug": "^2.0.3",
"request": "^2.88.2"
},
"devDependencies": {
"nodemon": "^1.19.1"
Expand Down
145 changes: 91 additions & 54 deletions routes/lc.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

const router = require("koa-router")();
const fetch = require("node-fetch");
const request = require('request');
const {
leetcodeConfig: {
baseUrl,
loginUrl,
submitUrl,
_91UsernameCookieName,
_91PwdCookieName,
Expand All @@ -13,102 +14,93 @@ const {
} = require('../config/index')
const { success, fail } = require('../utils/request')
const { encrypt, decrypt } = require('../utils/crypto')
const { set91Cookie, getLcRequestData } = require('./utils')

// 用户上传lc的账号名与密码
router.post('/api/v1/lc/submitLcAccount', async (ctx) => {
const { login, password } = ctx.request.body
// 先提交给lc,账号密码是否正确
const { login, password } = ctx.request.body
let result = await getLcRequestData({login, password})
if(result.success){
// 密码正确时,对密码进行加密
let encryptPwd = encrypt(password)
// 将加密后的密文 以及 sessionId、 csrftoken 写入cookie中
sessionId = result[lcSeesionCookieName]
csrftoken = result[lcCsrftokenCookieName]
set91Cookie({
ctx.body = success({
isLogin: true,
[_91UsernameCookieName]: login,
[_91PwdCookieName]: encryptPwd,
[lcSeesionCookieName]: sessionId,
[lcCsrftokenCookieName]: csrftoken,
}, ctx)
ctx.body = success({isLogin: true})
[lcSeesionCookieName]: result[lcSeesionCookieName],
[lcCsrftokenCookieName]: result[lcCsrftokenCookieName],
})
} else {
ctx.body = fail({code: 302, message: '提交失败' })
ctx.body = fail({code: 302, message: result.message || '登录失败' })
}
});

// 用户提交题解
// 前置数据校验
router.post('/api/v1/lc/submitCode', async (ctx, next) => {
// 先校验cookie中是否有账号密码,没有就让用户先输入再提交
const userName = ctx.cookies.get(_91UsernameCookieName)
const passwd = ctx.cookies.get(_91PwdCookieName)
if(!userName || !passwd){
const {
login,
password,
[lcSeesionCookieName.toLowerCase()]: sessionId,
[lcCsrftokenCookieName.toLowerCase()]: csrftoken
} = ctx.request.headers
// 如果有一个不存在,就提示用户重新提交一遍lc的账号密码
if(!login || !password || !sessionId || !csrftoken){
return ctx.response.body = fail({
code: 403,
message: "请先提交账号名与密码后再提交"
message: "缺少字段或者cookie已过期,请重新提交账号名与密码后再提交"
});
}
await next()
})

// 题解提交逻辑
router.post('/api/v1/lc/submitCode', async (ctx) => {
const {
login,
password,
[lcSeesionCookieName.toLowerCase()]: sessionId,
[lcCsrftokenCookieName.toLowerCase()]: csrftoken
} = ctx.request.headers
const lcAccountData = {
login,
password,
}

// 如果这俩cookie有一个不存在就提示用户重新提交一遍lc的账号密码
let sessionId = ctx.cookies.get(lcSeesionCookieName)
let csrftoken = ctx.cookies.get(lcCsrftokenCookieName)
// 先试着用旧值提交下看是否成功
const problemData = formateSubmitData(ctx.request.body)
let requestData = {
[lcSeesionCookieName]: sessionId,
[lcCsrftokenCookieName]: csrftoken
}
if(!sessionId || !csrftoken){
return ctx.response.body = fail({
code: 403,
message: "提交失败,请重新输入账号名与密码后再提交!"
});
}

// 先试着用cookie中的旧csrftoken提交下看是否成功
const problemData = formateSubmitData(ctx.request.body)
let result = (await submitSolution(problemData, requestData)) || {}
if(result.success){
return ctx.body = success(result.data)
if(result.success && result.data.submission_id){
return ctx.response.body = success(Object.assign(requestData, result.data, lcAccountData))
}

// 如果403就用账号密码获取最新csrftoken,再提交一遍
if(result.statusCode === 403){
// 获取最新csrftoken
let newRequestData = await getLatestLcRequestData(ctx)
const decryPwd = decrypt(password)
let newRequestData = await getLcRequestData({
[_91UsernameCookieName]: login,
[_91PwdCookieName]: decryPwd
})
if(!newRequestData.success){
return ctx.response.body = fail({
code: 403,
message: newRequestData.message || "提交失败,请重新输入账号名与密码后再提交!"
});
}
// 更新下91的cookie
set91Cookie(newRequestData, ctx)

// 再提交一遍
let retryResult = await submitSolution(problemData, newRequestData)
if(retryResult.success){
return ctx.body = success(retryResult.data)
if(retryResult.success && retryResult.data.submission_id){
return ctx.body = success(Object.assign(newRequestData, retryResult.data, lcAccountData))
}
}

// 如果还是失败,就提示用户重新输入账号名与密码
return ctx.response.body = fail({
code: 403,
message: "提交失败,请重新输入账号名与密码后再提交!"
});
});

// 获取最新的的向leetcode发送请求的必要参数
async function getLatestLcRequestData(ctx){
const userName = ctx.cookies.get(_91UsernameCookieName)
const encryptPassword = ctx.cookies.get(_91PwdCookieName)
const password = decrypt(encryptPassword)
return await getLcRequestData({
[_91UsernameCookieName]: userName,
[_91PwdCookieName]: password
})
}

async function submitSolution(problem, requestData){
let statusCode = 403
const url = submitUrl.replace('$slug', problem.slug);
Expand All @@ -128,7 +120,6 @@ async function submitSolution(problem, requestData){
_delay: 1,// in seconds
body: JSON.stringify(problem || {})
}
console.log(opt)
const result = await fetch(url, opt).then((res) => {
statusCode = res.status
return res.json()
Expand All @@ -151,4 +142,50 @@ function formateSubmitData(problem = {}){
})
}

// 从leetcode的请求中获取cookie值
function getCookieFromLc(resp, key) {
const cookies = resp.headers['set-cookie'];
if (!cookies) return null;

for (let i = 0; i < cookies.length; ++i) {
const sections = cookies[i].split(';');
for (let j = 0; j < sections.length; ++j) {
const kv = sections[j].trim().split('=');
if (kv[0] === key) return kv[1];
}
}
return null;
};

// 从leetcode中获取 发送请求的必要参数 LEETCODE_SESSION、csrftoken
async function getLcRequestData(options){
const opt = {
url: loginUrl,
credentials: 'include',
headers: {
credentials: 'include',
Origin: baseUrl,
Referer: loginUrl,
},
form: {
[_91UsernameCookieName]: options[_91UsernameCookieName],
[_91PwdCookieName]: options[_91PwdCookieName]
}
}
return new Promise(resolve => {
request.post(opt, function(e, resp, body) {
if (resp.statusCode !== 302) {
return resolve({success: false, message: 'pwd invaid'})
}
sessionId = getCookieFromLc(resp, lcSeesionCookieName);
csrftoken = getCookieFromLc(resp, lcCsrftokenCookieName);
resolve({
success: true,
[lcSeesionCookieName]: sessionId,
[lcCsrftokenCookieName]:csrftoken
})
});
})
}

module.exports = router;
73 changes: 0 additions & 73 deletions routes/utils.js

This file was deleted.