// ==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); } });