-
Notifications
You must be signed in to change notification settings - Fork 37
feat: 新增直接提交代码至lc功能 #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
|
||
./idea | ||
/node_modules | ||
/static/**/*.md | ||
/static/**/*.md |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// 用户上传账号名与密码 | ||
const router = require("koa-router")(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议不叫 account,目的和我的 user 区分。可以叫 LCAccount |
||
|
||
const { | ||
set91Cookie, | ||
getLeetcodeCookie, | ||
success, | ||
fail | ||
} = require('./utils') | ||
|
||
// 设置响应头 | ||
router.options('/submitLcAccount', async (ctx, next) => { | ||
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不需要自己设置跨域头,中间件设置好了 |
||
ctx.set("Access-Control-Allow-Credentials", "true"); | ||
ctx.set("Access-Control-Allow-Methods", "*"); | ||
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token"); | ||
ctx.set("Access-Control-Expose-Headers", "*"); | ||
ctx.body = 200; | ||
ctx.status = 200; | ||
}); | ||
|
||
// 设置响应头 | ||
router.post('/submitLcAccount', async (ctx, next) => { | ||
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); | ||
ctx.set("Access-Control-Allow-Credentials", "true"); | ||
ctx.set("Access-Control-Allow-Methods", "*"); | ||
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token"); | ||
ctx.set("Access-Control-Expose-Headers", "*"); | ||
await next(); | ||
}); | ||
|
||
// 先提交给lc,账号密码是否正确 | ||
router.post('/submitLcAccount', async (ctx, next) => { | ||
const {login, password} = ctx.request.body | ||
let result = await getLeetcodeCookie({login, password}) | ||
if(result.success){ | ||
// todo密码正确时,对密码进行加密 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 加密方法我写好了,直接调用 utils 下的即可 |
||
|
||
// 将加密后的密文 以及 sessionId、 csrftoken写入cookie中 | ||
LEETCODE_SESSION = result.LEETCODE_SESSION | ||
csrftoken = result.csrftoken | ||
set91Cookie({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 使用 koa 内置的 setCookie 参考我的user 接口 |
||
login, | ||
password, | ||
csrftoken, | ||
LEETCODE_SESSION | ||
}, ctx) | ||
ctx.body = success({isLogin: true}) | ||
} else { | ||
ctx.body = fail() | ||
} | ||
}); | ||
|
||
module.exports = router; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// 提交题解 | ||
const router = require("koa-router")(); | ||
const request = require('request'); | ||
|
||
const { | ||
set91Cookie, | ||
getLeetcodeCookie, | ||
success, | ||
fail | ||
} = require('./utils') | ||
const { submitUrl, baseUrl } = require('../config/index') | ||
|
||
// 设置响应头 | ||
router.options('/submitCode', async (ctx, next) => { | ||
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); | ||
ctx.set("Access-Control-Allow-Credentials", "true"); | ||
ctx.set("Access-Control-Allow-Methods", "*"); | ||
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token"); | ||
ctx.set("Access-Control-Expose-Headers", "*"); | ||
ctx.body = 200; | ||
ctx.status = 200; | ||
}); | ||
|
||
// 设置响应头 | ||
router.post('/submitCode', async (ctx, next) => { | ||
ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); | ||
ctx.set("Access-Control-Allow-Credentials", "true"); | ||
ctx.set("Access-Control-Allow-Methods", "*"); | ||
ctx.set("Access-Control-Allow-Headers", "Content-Type,Access-Token"); | ||
ctx.set("Access-Control-Expose-Headers", "*"); | ||
await next(); | ||
}); | ||
|
||
|
||
// 用户提交题解 | ||
// 先校验cookie中是否有账号密码,没有就让用户先输入再提交 | ||
router.post('/submitCode', async (ctx, next) => { | ||
const userName = ctx.cookies.get('login') | ||
const passwd = ctx.cookies.get('password') | ||
if(!userName || !passwd){ | ||
return ctx.response.body = fail({ | ||
message: "请先提交账号名与密码后再提交" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fail 加 code |
||
}); | ||
} | ||
await next(); | ||
}); | ||
|
||
// 先试着用cookie中的csrftoken提交下看是否成功, | ||
router.post('/submitCode', async (ctx, next) => { | ||
const login = ctx.cookies.get('login') | ||
const password = ctx.cookies.get('password') | ||
let LEETCODE_SESSION = ctx.cookies.get('LEETCODE_SESSION') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cookie.get() 建议提出常量 |
||
let csrftoken = ctx.cookies.get('csrftoken') | ||
const problemData = formateSubmitData(ctx.request.body) | ||
|
||
// 如果这俩cookie有一个不存在就拿账号密码再去lc请求一个新的 | ||
if(!LEETCODE_SESSION || !csrftoken){ | ||
const newCookie = await getLeetcodeCookie({login, password}) | ||
LEETCODE_SESSION = newCookie.LEETCODE_SESSION | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 变量不要全大写 |
||
csrftoken = newCookie.csrftoken | ||
set91Cookie({ | ||
csrftoken, | ||
LEETCODE_SESSION | ||
}, ctx) | ||
} | ||
|
||
// 先试着用cookie中的csrftoken提交下看是否成功, | ||
let result = await submitSolution(problemData, ctx) | ||
if(result.success){ | ||
set91Cookie({ | ||
csrftoken, | ||
LEETCODE_SESSION | ||
}, ctx) | ||
return ctx.body = success(result) | ||
} | ||
|
||
// 如果403就用账号密码获取最新csrftoken,再提交一遍 | ||
let { statusCode } = result | ||
if(statusCode === 403){ | ||
const newCookie = await getLeetcodeCookie({login, password}) | ||
LEETCODE_SESSION = newCookie.LEETCODE_SESSION | ||
csrftoken = newCookie.csrftoken | ||
|
||
let retryResult = await submitSolution(problemData, ctx) | ||
if(retryResult.success){ | ||
set91Cookie({ | ||
csrftoken, | ||
LEETCODE_SESSION | ||
}, ctx) | ||
return ctx.body = success(retryResult) | ||
} | ||
} | ||
|
||
// 如果还是失败,就提示用户重新输入账号名与密码 | ||
return ctx.response.body = fail({ | ||
message: "提交失败,请重新输入账号名与密码后再提交!" | ||
}); | ||
}); | ||
|
||
function submitSolution(problem, ctx){ | ||
const LEETCODE_SESSION = ctx.cookies.get('LEETCODE_SESSION') | ||
const csrftoken = ctx.cookies.get('csrftoken') | ||
const opts = {} | ||
opts.method = 'POST'; | ||
opts.url = submitUrl.replace('$slug', problem.slug); | ||
opts.headers = {}; | ||
opts.headers.Origin = baseUrl; | ||
opts.headers.Referer = problem.link; | ||
opts.headers.Cookie = `LEETCODE_SESSION=${LEETCODE_SESSION};csrftoken=${csrftoken};`; | ||
opts.headers['X-csrftoken'] = csrftoken; | ||
opts.headers['X-Requested-With'] = 'XMLHttpRequest'; | ||
opts.json = true; | ||
opts._delay = 1; // in seconds | ||
opts.body = problem || {}; | ||
|
||
return new Promise(res => { | ||
request(opts, function(e, resp, body) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议用 fetch。这样一致一点,参考我的 middleware 代码 |
||
if(e){ | ||
return res({success: false, statusCode: resp.statusCode}) | ||
} | ||
body.success = true | ||
body.statusCode = resp.statusCode | ||
return res(body) | ||
}); | ||
}) | ||
} | ||
|
||
// 整理提交题解时的数据格式 | ||
function formateSubmitData(problem = {}){ | ||
return Object.assign(problem, { | ||
judge_type: 'large', | ||
lang: problem.lang, | ||
question_id: parseInt(problem.id, 10), | ||
test_mode: false, | ||
typed_code: problem.code | ||
}) | ||
} | ||
|
||
module.exports = router; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
const request = require('request'); | ||
const { baseUrl, loginUrl } = require('../config/index') | ||
|
||
// 设置cookie | ||
function set91Cookie (data, ctx){ | ||
for(let key in data){ | ||
ctx.cookies.set(key, data[key], { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 为啥不直接 set 而是 for? |
||
maxAge: 365 * 24 * 60 * 60 * 1000, | ||
httpOnly: true | ||
}) | ||
} | ||
} | ||
|
||
// 从leetcode的请求中获取cookie值 | ||
function getDataFromLcResponse(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两个cookie | ||
function getLeetcodeCookie({login, password}){ | ||
return new Promise((resolve, reject) => { | ||
// 先发前置请求获取csrftoken | ||
request(loginUrl, function(e, resp, body) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fetch |
||
let csrftoken = getDataFromLcResponse(resp, 'csrftoken'); | ||
let LEETCODE_SESSION | ||
const opts = { | ||
url: loginUrl, | ||
headers: { | ||
Origin: baseUrl, | ||
Referer: loginUrl, | ||
Cookie: `csrftoken=${csrftoken};` | ||
}, | ||
form: { | ||
csrfmiddlewaretoken: csrftoken, | ||
login, | ||
password, | ||
} | ||
}; | ||
|
||
// 再发一次请求获取csrftoken、LEETCODE_SESSION | ||
request.post(opts, function(e, resp, body) { | ||
if (resp.statusCode !== 302) { | ||
return resolve({success: false, message: 'pwd invaid'}) | ||
} | ||
LEETCODE_SESSION = getDataFromLcResponse(resp, 'LEETCODE_SESSION'); | ||
csrftoken = getDataFromLcResponse(resp, 'csrftoken'); | ||
resolve({ | ||
success: true, | ||
LEETCODE_SESSION, | ||
csrftoken | ||
}) | ||
}); | ||
}); | ||
}) | ||
} | ||
|
||
function success(data) { | ||
return { | ||
success: true, | ||
data, | ||
}; | ||
} | ||
|
||
function fail({ message, code = 10001 }) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. success 和 fail 我已经写了,你咋又写一遍 |
||
return { | ||
success: false, | ||
data: null, | ||
message, | ||
code, | ||
}; | ||
} | ||
module.exports = { | ||
set91Cookie, | ||
getDataFromLcResponse, | ||
getLeetcodeCookie, | ||
success, | ||
fail | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.