中国移动APP签到 BETA
// ==UserScript==
// @name 中国移动APP签到 BETA
// @description 仅供学习交流
// @namespace cxxjackie
// @author cxxjackie
// @version 0.2.0
// @crontab * * once * *
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_getValue
// @grant GM_setValue
// @grant CAT_userConfig
// @connect wx.10086.cn
// @connect client.app.coc.10086.cn
// @require https://scriptcat.org/lib/532/1.0.2/ajax.js
// @require https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js
// @require https://cdn.bootcdn.net/ajax/libs/crypto-js/4.0.0/crypto-js.min.js
// ==/UserScript==
/* ==UserConfig==
设置:
CellNum:
title: 手机号
type: number
default: 99999999999
VerifyCode:
title: 登录验证码
type: text
default: 0
Notice:
title: 通知窗口
default: 总是
values: [总是, 仅失败时, 关闭]
Device:
title: 设备信息(不知道是什么就别改)
type: text
default: 00000000000000001|#$0000000000000|#$0000000000000000|#$00:00:00:00:00:00|#$null
==/UserConfig== */
/* globals ajax, JSEncrypt, CryptoJS */
/* eslint-disable no-async-promise-executor */
const storage = {
cellNum: GM_getValue('设置.CellNum', '99999999999').toString(),
verifyCode: GM_getValue('设置.VerifyCode', '0').toString(),
notice: GM_getValue('设置.Notice', '总是'),
device: GM_getValue('设置.Device') || '00000000000000001|#$0000000000000|#$0000000000000000|#$00:00:00:00:00:00|#$null'
};
addDescriptor('xk', 'null');
addDescriptor('oldDevice', storage.device);
addDescriptor('shouldLogin', false);
addDescriptor('curMonth', 0);
addDescriptor('markedTimes', 0);
ajax('default', {
headers: {
'User-Agent': 'Mozilla/5.0 (Linux; Android 12; PFJM10 Build/RKQ1.211119.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/6.2 TBS/046141 Mobile Safari/537.36 leadeon/8.0.5/CMCCIT',
'X-Requested-With': 'com.greenpoint.android.mc10086.activity',
},
_status: [200, 201],
_nocatch: true
});
const curTime = new Date();
// getter/setter实现GM_getValue/GM_setValue
function addDescriptor(key, defaultValue) {
const _key = '_' + key;
storage[_key] = GM_getValue(key, defaultValue);
Object.defineProperty(storage, key, {
configurable: true,
enumerable: true,
get: () => storage[_key],
set: val => {
storage[_key] = val;
GM_setValue(key, val);
}
});
}
const AES_KEY_REQUEST = CryptoJS.enc.Hex.parse('62414967767741754134746244723964');
const AES_KEY_RESPONSE = CryptoJS.enc.Hex.parse('47533756656C6B4A6C35495431757751');
const AES_IV = CryptoJS.enc.Utf8.parse('9791027341711819');
// aes加密
function aesEncrypt(str, key, iv) {
const encrypt = CryptoJS.AES.encrypt(str, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypt.toString();
}
// aes解密
function aesDecrypt(str, key, iv) {
const decrypt = CryptoJS.AES.decrypt(str, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return CryptoJS.enc.Utf8.stringify(decrypt);
}
const DES_KEY = CryptoJS.enc.Hex.parse('40786927616E256C766469616E237869746F6E6762757E26');
const DES_IV = CryptoJS.enc.Utf8.parse('01234567');
// des加密
function desEncrypt(str, key, iv) {
const encrypt = CryptoJS.TripleDES.encrypt(str, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypt.toString();
}
const RSA_PUBLIC_KEY_s1 = {
n: 'A3123079DD30EAF54C8CDC1677576ABD8BEE25938116F64F22F0D5B5D70F0FD6FF40895D6CF554A63F7707B6A5A6EA5C7B114E2D611E2F675726961D0E5CF289B70331E3DDCAFC096E888E686E252E52DBE01D3F3970F4D7F7C44AF1AF01931925798CEEB1CC89239BA8885DF3ED5C79DEF4D71BC578959DCACC70539A2C230D',
e: '10001'
};
const RSA_PUBLIC_KEY_s = {
n: '968E9ACF9E59B4E2E1BB24266EE39043788A45F788ECDC5C971F4964C3267CF20AFD3B7F029B68A9A9B65D77D0306B8319DF0C174F3DD82EF3894A6C38E23634F6095A81901AD7E6650911C0910F12C7DE50A6FCEE3AE3563CC5985C46A965DB2AF49E94F69B62F67FF3D5C0F79782572375E5F8B44AA43C0CA6D48E8A969BEB',
e: '10001'
};
// rsa加密
function rsaEncrypt(str, publicKey) {
const encrypt = new JSEncrypt();
encrypt.setKey();
encrypt.key.setPublic(publicKey.n, publicKey.e);
return encrypt.encrypt(str);
}
const POST_BODY = {
ak: 'F4AA34B89513F0D087CA0EF11A3277469DC74905',
xk: storage.xk,
xc: 'A2160',
packageName: 'com.greenpoint.android.mc10086.activity',
appKey: '00000159',
cv: '8.0.5',
tel: '99999999999',
imei: '00000000000000000',
prov: '100',
city: '010',
sn: '0',
sv: '12',
cid: '0',
ctid: '0',
en: '0',
nt: '3',
st: '1'
};
// 加密post请求,解密响应数据
function postByAes(url, reqBody) {
const data = JSON.stringify({...POST_BODY, reqBody});
const urlObj = new URL(url);
const time = curTime.getTime();
const nonce = parseInt(1e7 * (Math.random() * 9 + 1));
const xs = CryptoJS.MD5(`${url}_${data}_Leadeon/SecurityOrganization`).toString();
const token = aesEncrypt(`${POST_BODY.xk}_${urlObj.pathname}_${time}_${nonce}`, AES_KEY_REQUEST, AES_IV);
const sign = CryptoJS.MD5(`${token}_${time}_${nonce}_null`).toString();
return new Promise(resolve => {
ajax(url, {
method: 'post',
data: aesEncrypt(data, AES_KEY_REQUEST, AES_IV),
anonymous: true,
headers: {
'content-type': 'application/json; charset=utf-8',
'x-qen': '2',
'x-time': time,
'x-nonce': nonce,
'xs': xs,
'x-token': token,
'x-sign': sign
},
_detail: true
}).then(res => {
if (res.response) {
res.response = JSON.parse(aesDecrypt(res.response, AES_KEY_RESPONSE, AES_IV));
}
resolve(res);
});
});
}
// 更新xk
async function updateXk() {
const res = await postByAes('https://client.app.coc.10086.cn/biz-orange/DN/init/startInit', {
ssk: desEncrypt(storage.device, DES_KEY, DES_IV)
});
storage.xk = POST_BODY.xk = res.response.rspBody.ssv;
}
// 获取UTC时间
function getUTCTime() {
return curTime.toISOString().replace(/[-T:]|\..*$/g, '');
}
// 发送验证码
async function sendMsgLogin() {
if (typeof window.CAT_userConfig === 'function') window.CAT_userConfig();
const res = await postByAes('https://client.app.coc.10086.cn/biz-orange/LN/uamrandcode/sendMsgLogin', {
cellNum: storage.cellNum
});
if (res.response.retCode !== '000000') {
throw '验证码发送失败,请修改设备信息。'
}
}
// 登录
async function login(tryTimes = 0) {
const res = await postByAes('https://client.app.coc.10086.cn/biz-orange/LN/uamthreenetworklogin/login', {
cellNum: rsaEncrypt('leadeon' + storage.cellNum + getUTCTime(), RSA_PUBLIC_KEY_s),
imei: POST_BODY.imei,
sendSmsFlag: '0',
verifyCode: storage.verifyCode
});
switch (res.status) {
case 200:
break;
case 403:
if (tryTimes > 0) throw '登录失败403';
await updateXk();
return await login(tryTimes + 1);
default:
throw '登录失败' + res.status;
}
switch (res.response.retCode) {
case '000000':
storage.shouldLogin = false;
return res;
case '213008':
case '223028':
await sendMsgLogin();
throw '验证码错误,请重新输入。';
case '213009':
await sendMsgLogin();
throw '验证码失效,请重新输入。';
default:
throw res.response.retCode + ': ' + res.response.retDesc;
}
}
// 自动登录
async function autoLogin(tryTimes = 0) {
const res = await postByAes('https://client.app.coc.10086.cn/biz-orange/LN/uamrandcodelogin/autoLogin', {
cellNum: rsaEncrypt('leadeon' + storage.cellNum + getUTCTime(), RSA_PUBLIC_KEY_s),
imei: POST_BODY.imei,
sendSmsFlag: '0',
sysTime: curTime.getTime()
});
switch (res.status) {
case 200:
break;
case 403:
if (tryTimes > 0) throw '自动登录失败403';
await updateXk();
return await autoLogin(tryTimes + 1);
default:
throw '自动登录失败' + res.status;
}
switch (res.response.retCode) {
case '000000':
return res;
default:
await sendMsgLogin();
storage.shouldLogin = true;
throw '登录已失效,请在设置中填入手机验证码后重新签到。';
}
}
// 获取UID
async function getUID() {
const loginRes = storage.shouldLogin ? await login() : await autoLogin();
if ('set-cookie' in loginRes.responseHeaders) {
const cookie = {};
for (const item of loginRes.responseHeaders['set-cookie'].split(';')) {
const parts = item.split('=');
if (parts.length === 2) {
cookie[parts[0].trim()] = parts[1].trim();
}
}
return cookie.UID;
} else {
throw '获取登录cookie时发生错误,请尝试升级你的脚本猫。';
}
}
// 登录cookie
async function appLogin() {
const mark = await ajax('https://wx.10086.cn/qwhdhub/qwhdmark/1021122301', {_detail: true});
if (mark.status !== 201) {
const uid = await getUID();
const res = await ajax('https://wx.10086.cn/qwhdsso/chinaMobileApp', {
method: 'post',
data: {
openid: mark.response.match(/pwd = '([^']+)';/)[1],
mobile: encodeURIComponent(rsaEncrypt('leadeon' + uid + getUTCTime(), RSA_PUBLIC_KEY_s1)),
isMobilePhone: true
},
headers: {
'content-type': 'application/json;charset=UTF-8',
'referer': mark.finalUrl,
},
responseType: 'json'
});
if (res.data) await ajax(res.data);
}
}
// 获取签到信息
async function getMarkInfo() {
const res = await ajax('https://wx.10086.cn/qwhdhub/api/mark/info/prizeInfo', {
method: 'post',
responseType: 'json'
});
return res.data;
}
// 签到
async function doMark() {
const res = await ajax('https://wx.10086.cn/qwhdhub/api/mark/do/mark', {
method: 'post',
responseType: 'json',
headers: {referer: 'https://wx.10086.cn/qwhdhub/qwhdmark/1021122301'}
});
if (res.success) {
storage.markedTimes += 1;
return '签到成功!奖励:' + res.data.prizeName;
} else {
throw '签到失败!原因:' + res.msg;
}
}
return new Promise(async (resolve, reject) => {
const _resolve = message => {
if (storage.notice === '总是') {
GM_notification({
title: '中国移动APP签到',
text: message
});
}
resolve(message);
};
const _reject = reason => {
if (storage.notice !== '关闭') {
GM_notification({
title: '中国移动APP签到',
text: reason
});
}
reject(reason);
};
try {
if (storage.cellNum === '99999999999') {
if (typeof window.CAT_userConfig === 'function') window.CAT_userConfig();
return _reject('请在设置中填入正确的手机号!');
}
if (storage.curMonth !== curTime.getMonth() + 1) {
storage.curMonth = curTime.getMonth() + 1;
storage.markedTimes = 0;
}
if (storage.markedTimes >= 7) return resolve('本月已签到7次。');
if (storage.xk === 'null' || storage.device !== storage.oldDevice) {
await updateXk();
}
storage.oldDevice = storage.device;
await appLogin();
const markInfo = await getMarkInfo();
storage.markedTimes = markInfo.markedTimes;
if (markInfo.markedTimes >= 7) {
resolve('本月已签到7次。');
} else if (!markInfo.todayMarked) {
_resolve(await doMark());
} else {
resolve('今日已签到。');
}
} catch (e) {
_reject(e);
}
});