'use strict';
const got = require('got');
require('dotenv').config();
const QRCode = require('qrcode');
const { addEnv, delEnv, getEnvs, getEnvsCount, updateEnv } = require('./ql');
const path = require('path');
const qlDir = process.env.QL_DIR || '/ql';
const notifyFile = path.join(qlDir, 'shell/notify.sh');
const { exec } = require('child_process');
const { GET_RANDOM_TIME_UA } = require('./utils/USER_AGENT');
const api = got.extend({
retry: { limit: 0 },
responseType: 'json',
});
module.exports = class User {
ua;
pt_key;
pt_pin;
cookie;
eid;
timestamp;
nickName;
token;
okl_token;
cookies;
QRCode;
remark;
#s_token;
constructor({ token, okl_token, cookies, pt_key, pt_pin, cookie, eid, remarks, remark, ua }) {
this.token = token;
this.okl_token = okl_token;
this.cookies = cookies;
this.pt_key = pt_key;
this.pt_pin = pt_pin;
this.cookie = cookie;
this.eid = eid;
this.remark = remark;
this.ua = ua;
if (pt_key && pt_pin) {
this.cookie = 'pt_key=' + this.pt_key + ';pt_pin=' + this.pt_pin + ';';
}
if (cookie) {
this.pt_pin = cookie.match(/pt_pin=(.*?);/)[1];
this.pt_key = cookie.match(/pt_key=(.*?);/)[1];
}
if (remarks) {
this.remark = remarks.match(/remark=(.*?);/) && remarks.match(/remark=(.*?);/)[1];
}
}
async getQRConfig() {
this.ua = this.ua || process.env.NINJA_UA || GET_RANDOM_TIME_UA();
const taskUrl = `https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance?lang=chs&appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=${Date.now()}&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport`;
const response = await api({
url: taskUrl,
headers: {
Connection: 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'zh-cn',
Referer: taskUrl,
'User-Agent': this.ua,
Host: 'plogin.m.jd.com',
},
});
const headers = response.headers;
const data = response.body;
await this.#formatSetCookies(headers, data);
if (!this.#s_token) {
throw new Error('二维码创建失败!');
}
const nowTime = Date.now();
const taskPostUrl = `https://plogin.m.jd.com/cgi-bin/m/tmauthreflogurl?s_token=${
this.#s_token
}&v=${nowTime}&remember=true`;
const configRes = await api({
method: 'post',
url: taskPostUrl,
body: `lang=chs&appid=300&source=wq_passport&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=${nowTime}&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action`,
headers: {
Connection: 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'zh-cn',
Referer: taskUrl,
'User-Agent': this.ua,
Host: 'plogin.m.jd.com',
Cookie: this.cookies,
},
});
const configHeaders = configRes.headers;
const configData = configRes.body;
this.token = configData.token;
if (this.token)
this.QRCode = await QRCode.toDataURL(
`https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=${this.token}`
);
const cookies = configHeaders['set-cookie'][0];
this.okl_token = cookies.substring(cookies.indexOf('=') + 1, cookies.indexOf(';'));
}
async checkQRLogin() {
if (!this.token || !this.okl_token || !this.cookies) {
throw new Error('初始化登录请求失败!');
}
const nowTime = Date.now();
const loginUrl = `https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=${nowTime}&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport`;
const getUserCookieUrl = `https://plogin.m.jd.com/cgi-bin/m/tmauthchecktoken?&token=${this.token}&ou_state=0&okl_token=${this.okl_token}`;
const response = await api({
method: 'POST',
url: getUserCookieUrl,
body: `lang=chs&appid=300&source=wq_passport&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=${nowTime}&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action`,
headers: {
Connection: 'Keep-Alive',
'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8',
Accept: 'application/json, text/plain, */*',
'Accept-Language': 'zh-cn',
Referer: loginUrl,
'User-Agent': this.ua,
Cookie: this.cookies,
},
});
const data = response.body;
const headers = response.headers;
if (data.errcode === 0) {
const pt_key = headers['set-cookie'][1];
this.pt_key = pt_key.substring(pt_key.indexOf('=') + 1, pt_key.indexOf(';'));
const pt_pin = headers['set-cookie'][2];
this.pt_pin = pt_pin.substring(pt_pin.indexOf('=') + 1, pt_pin.indexOf(';'));
this.cookie = 'pt_key=' + this.pt_key + ';pt_pin=' + this.pt_pin + ';';
const result = await this.CKLogin();
result.errcode = 0;
return result;
}
return {
errcode: data.errcode,
message: data.message,
};
}
async CKLogin() {
let message;
await this.#getNickname();
const envs = await getEnvs();
const poolInfo = await User.getPoolInfo();
const env = await envs.find((item) => item.value.match(/pt_pin=(.*?);/)[1] === this.pt_pin);
if (!env) {
if (!poolInfo.allowAdd) {
throw new UserError('管理员已关闭注册,去其他地方看看吧', 210, 200);
} else if (poolInfo.marginCount === 0) {
throw new UserError('本站已到达注册上限,你来晚啦', 211, 200);
} else {
const remarks = `remark=${this.nickName};`;
const body = await addEnv(this.cookie, remarks);
if (body.code !== 200) {
throw new UserError(body.message || '添加账户错误,请重试', 220, body.code || 200);
}
this.eid = body.data[0]._id;
this.timestamp = body.data[0].timestamp;
message = `注册成功,${this.nickName}`;
this.#sendNotify('Ninja 运行通知', `用户 ${this.nickName}(${decodeURIComponent(this.pt_pin)}) 已上线`);
}
} else {
this.eid = env._id;
const body = await updateEnv(this.cookie, this.eid);
if (body.code !== 200) {
throw new UserError(body.message || '更新账户错误,请重试', 221, body.code || 200);
}
this.timestamp = body.data.timestamp;
message = `欢迎回来,${this.nickName}`;
this.#sendNotify('Ninja 运行通知', `用户 ${this.nickName}(${decodeURIComponent(this.pt_pin)}) 已更新 CK`);
}
return {
nickName: this.nickName,
eid: this.eid,
timestamp: this.timestamp,
message,
};
}
async getUserInfoByEid() {
const envs = await getEnvs();
const env = await envs.find((item) => item._id === this.eid);
if (!env) {
throw new UserError('没有找到这个账户,重新登录试试看哦', 230, 200);
}
this.cookie = env.value;
this.timestamp = env.timestamp;
const remarks = env.remarks;
if (remarks) {
this.remark = remarks.match(/remark=(.*?);/) && remarks.match(/remark=(.*?);/)[1];
}
await this.#getNickname();
return {
nickName: this.nickName,
eid: this.eid,
timestamp: this.timestamp,
remark: this.remark,
};
}
async updateRemark() {
if (!this.eid || !this.remark || this.remark.replace(/(^\s*)|(\s*$)/g, '') === '') {
throw new UserError('参数错误', 240, 200);
}
const envs = await getEnvs();
const env = await envs.find((item) => item._id === this.eid);
if (!env) {
throw new UserError('没有找到这个账户,重新登录试试看哦', 230, 200);
}
this.cookie = env.value;
const remarks = `remark=${this.remark};`;
const updateEnvBody = await updateEnv(this.cookie, this.eid, remarks);
if (updateEnvBody.code !== 200) {
throw new UserError('更新/上传备注出错,请重试', 241, 200);
}
return {
message: '更新/上传备注成功',
};
}
async delUserByEid() {
await this.getUserInfoByEid();
const body = await delEnv(this.eid);
if (body.code !== 200) {
throw new UserError(body.message || '删除账户错误,请重试', 240, body.code || 200);
}
this.#sendNotify('Ninja 运行通知', `用户 ${this.nickName}(${decodeURIComponent(this.pt_pin)}) 删号跑路了`);
return {
message: '账户已移除',
};
}
static async getPoolInfo() {
const count = await getEnvsCount();
const allowCount = (process.env.ALLOW_NUM || 40) - count;
return {
marginCount: allowCount >= 0 ? allowCount : 0,
allowAdd: Boolean(process.env.ALLOW_ADD) || true,
};
}
static async getUsers() {
const envs = await getEnvs();
const result = envs.map(async (env) => {
const user = new User({ cookie: env.value, remarks: env.remarks });
await user.#getNickname(true);
return {
pt_pin: user.pt_pin,
nickName: user.nickName,
remark: user.remark || user.nickName,
};
});
return Promise.all(result);
}
async #getNickname(nocheck) {
const body = await api({
url: `https://me-api.jd.com/user_new/info/GetJDUserInfoUnion?orgFlag=JD_PinGou_New&callSource=mainorder&channel=4&isHomewhite=0&sceneval=2&_=${Date.now()}&sceneval=2&g_login_type=1&g_ty=ls`,
headers: {
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-cn',
Connection: 'keep-alive',
Cookie: this.cookie,
Referer: 'https://home.m.jd.com/myJd/newhome.action',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
Host: 'me-api.jd.com',
},
}).json();
if (!body.data?.userInfo && !nocheck) {
throw new UserError('获取用户信息失败,请检查您的 cookie !', 201, 200);
}
this.nickName = body.data?.userInfo.baseInfo.nickname || decodeURIComponent(this.pt_pin);
}
#formatSetCookies(headers, body) {
return new Promise((resolve) => {
let guid, lsid, ls_token;
this.#s_token = body.s_token;
guid = headers['set-cookie'][0];
guid = guid.substring(guid.indexOf('=') + 1, guid.indexOf(';'));
lsid = headers['set-cookie'][2];
lsid = lsid.substring(lsid.indexOf('=') + 1, lsid.indexOf(';'));
ls_token = headers['set-cookie'][3];
ls_token = ls_token.substring(ls_token.indexOf('=') + 1, ls_token.indexOf(';'));
this.cookies = `guid=${guid};lang=chs;lsid=${lsid};ls_token=${ls_token};`;
resolve();
});
}
#sendNotify(title, content) {
const notify = process.env.NINJA_NOTIFY || true;
if (!notify) {
console.log('Ninja 通知已关闭\n' + title + '\n' + content + '\n' + '已跳过发送');
return;
}
exec(`${notifyFile} "${title}" "${content}"`, (error, stdout, stderr) => {
if (error) {
console.log(stderr);
} else {
console.log(stdout);
}
});
}
};
class UserError extends Error {
constructor(message, status, statusCode) {
super(message);
this.name = 'UserError';
this.status = status;
this.statusCode = statusCode || 200;
}
}