From 3c84e43e7234b4d074b56e2818f035b793826d7a Mon Sep 17 00:00:00 2001 From: j <2019292338@qq.com> Date: Fri, 4 Jun 2021 23:46:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=2091=E6=89=93=E5=8D=A1=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 4 +- package.json | 3 +- routes/lc.js | 145 ++++++++++++++++++++++++++++++------------------ routes/utils.js | 73 ------------------------ 4 files changed, 95 insertions(+), 130 deletions(-) delete mode 100644 routes/utils.js diff --git a/app.js b/app.js index 777040d642..017247e7d0 100644 --- a/app.js +++ b/app.js @@ -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 @@ -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); diff --git a/package.json b/package.json index 1eeecc47a8..63cac0a787 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/routes/lc.js b/routes/lc.js index 19f3a598a0..d82ff56c9b 100644 --- a/routes/lc.js +++ b/routes/lc.js @@ -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, @@ -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); @@ -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() @@ -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; diff --git a/routes/utils.js b/routes/utils.js deleted file mode 100644 index 911ca8c073..0000000000 --- a/routes/utils.js +++ /dev/null @@ -1,73 +0,0 @@ -const fetch = require("node-fetch"); -const request = require("request"); -const { - leetcodeConfig: { - baseUrl, - loginUrl, - _91UsernameCookieName, // 在91网站中存lc用户名的cookie的键名 - _91PwdCookieName, // 在91网站中存lc密码的cookie的键名 - lcSeesionCookieName, // lc存seesionid的 cookie键名 - lcCsrftokenCookieName, // lc存csrf的 cookie键名 - }, -} = require("../config/index"); - -// 设置91的cookie并且过期时间为一年 -function set91Cookie(data, ctx) { - for (let key in data) { - ctx.cookies.set(key, data[key], { - maxAge: 365 * 24 * 60 * 60 * 1000, - httpOnly: false, - }); - } -} - -// 从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 await 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 = { - set91Cookie, - getLcRequestData, -};