Skip to content

Commit

Permalink
重写监听逻辑,直接使用环信sdk;提升稳定性;
Browse files Browse the repository at this point in the history
  • Loading branch information
cxOrz committed Sep 9, 2022
1 parent 8a98488 commit f6e2200
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 129 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chaoxing-sign-cli",
"version": "3.6.2",
"version": "3.8.9",
"description": "超星学习通签到命令行工具",
"scripts": {
"build": "tsc",
Expand All @@ -18,6 +18,7 @@
"@koa/router": "^12.0.0",
"crypto-js": "^4.1.1",
"form-data": "^4.0.0",
"jsdom": "^20.0.0",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"kolorist": "^1.5.1",
Expand All @@ -30,6 +31,7 @@
},
"devDependencies": {
"@types/crypto-js": "^4.1.1",
"@types/jsdom": "^20.0.0",
"@types/koa": "^2.13.5",
"@types/koa-bodyparser": "^4.3.7",
"@types/koa__router": "^8.0.11",
Expand Down
5 changes: 3 additions & 2 deletions src/functions/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export interface Activity {
courseId: string,
classId: string,
otherId: number,
ifphoto?: number
ifphoto?: number,
chatID?: string
}

/**
Expand Down Expand Up @@ -139,7 +140,7 @@ export const preSign = async (uf: string, _d: string, vc3: string, activeId: str
})
}

export const preSign2 = (uf: string, _d: string, vc3: string, activeId: string | number, chatId: string, uid: string, tuid: string) => {
export const preSign2 = (uf: string, _d: string, vc3: string, activeId: string | number, chatId: string | undefined, uid: string, tuid: string) => {
let data = ''
return new Promise<string>((resolve) => {
https.get(CHAT_GROUP.PRESTUSIGN.URL + `?activeId=${activeId}&code=&uid=${uid}&courseId=null&classId=0&general=0&chatId=${chatId}&appType=0&tid=${tuid}&atype=null&sys=0`, {
Expand Down
205 changes: 79 additions & 126 deletions src/monitor.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import { extendGlobalThis } from './utils/helper';
extendGlobalThis(globalThis)
import prompts from 'prompts';
import WebSocket from 'ws';
import fs from 'fs';
import path from 'path';
import { blue, red } from 'kolorist';
import jsdom from 'jsdom';
import WebSocket from 'ws';
const JSDOM = new jsdom.JSDOM('', { url: 'https://im.chaoxing.com/webim/me' });
(globalThis.window as any) = JSDOM.window;
(globalThis.WebSocket as any) = WebSocket;
globalThis.navigator = JSDOM.window.navigator;
globalThis.location = JSDOM.window.location;
const webIM = require('./utils/websdk3.1.4.js').default;
import { extendGlobalThis } from './utils/helper';
extendGlobalThis(globalThis);
import { Activity, getPPTActiveInfo, preSign, preSign2, speculateType } from './functions/activity';
import { GeneralSign, GeneralSign_2 } from "./functions/general";
import { LocationSign, LocationSign_2 } from "./functions/location";
import { PhotoSign, getObjectIdFromcxPan, PhotoSign_2 } from "./functions/photo";
import { getJsonObject, storeUser } from './utils/file';
import { getIMParams, getLocalUsers, IMParamsType, userLogin } from './functions/user';
import { blue, red } from 'kolorist';
import { getIMParams, getLocalUsers, userLogin } from './functions/user';
import { sendEmail } from './utils/mailer';
const convert = (from: any, to: any) => (str: string) => Buffer.from(str, from).toString(to);
const utf8ToHex = convert('utf8', 'hex')
const hexToUtf8 = convert('hex', 'utf8');
const hexToBase64 = convert('hex', 'base64');
const base64toHex = convert('base64', 'hex');

const PromptsOptions = {
onCancel: () => {
Expand All @@ -25,67 +27,37 @@ const PromptsOptions = {
}
}

class Monitor {
static WebSocketURL = 'wss://im-api-vip6-v2.easecdn.com/ws/468/c4lxie22/websocket';
static COMING_SIGN_PREFIX = '080040024a2b0a2912';
static CONFIRM = '["CABAAVgA"]';
static ChatIDHex = '';
static UNKNOWN_PREFIX_0 = '0800123c0a0e63782d64657623637873747564791208';
static UNKNOWN_PREFIX_1 = '0800123d0a0e63782d64657623637873747564791209';

// 生成登录请求数据包
generateLoginHex(IM_Params: IMParamsType, mode: string | number) {
const timestampHex = utf8ToHex(new Date().getTime().toString());

// 兼容三种账号类型
switch (mode) {
case 0: mode = Monitor.UNKNOWN_PREFIX_0; break;
default: mode = Monitor.UNKNOWN_PREFIX_1;
}

return (mode + utf8ToHex(IM_Params.myTuid) + "1a0b656173656d6f622e636f6d2213776562696d5f" +
timestampHex + "1a8501247424" + utf8ToHex(IM_Params.myToken) +
"40034ac00108101205332e302e30280030004a0d" + timestampHex +
"6205776562696d6a13776562696d5f" + timestampHex + "728501247424" +
utf8ToHex(IM_Params.myToken) + "50005800");
}

// 生成活动请求数据包
generateGetActivityHex() {
return '080040004a2b1a29120f' + Monitor.ChatIDHex + '1a16636f6e666572656e63652e656173656d6f622e636f6d5800';
}

// 生成请求保持连接数据包
generateKeepAliveHex() {
return '080040004a3510d09580acd5a2d2a90e1a29120f' + Monitor.ChatIDHex + '1a16636f6e666572656e63652e656173656d6f622e636f6d5800';
}

}
const WebIMConfig = {
xmppURL: "https://im-api-vip6-v2.easecdn.com/ws",
apiURL: "https://a1-vip6.easecdn.com",
appkey: 'cx-dev#cxstudy',
Host: "easemob.com",
https: true,
isHttpDNS: false,
isMultiLoginSessions: true,
isAutoLogin: true,
isWindowSDK: false,
isSandBox: false,
isDebug: false,
autoReconnectNumMax: 2,
autoReconnectInterval: 2,
isWebRTC: false,
heartBeatWait: 4500,
delivery: false,
};

// 提取活动数据的JSON部分
function parseCourseInfo(hexStr: string) {
const textStr = hexToUtf8(hexStr);
const position = textStr.lastIndexOf('/preSign?') + 9;
let data = [];
let data_start = textStr.indexOf('courseId=', position) + 9;
let data_end = textStr.indexOf('&', data_start);
data.push(textStr.substring(data_start, data_end));
data_start = textStr.indexOf('classId=', position) + 8;
data_end = textStr.indexOf('&', data_start);
data.push(textStr.substring(data_start, data_end));
data_start = textStr.indexOf('activePrimaryId=', position) + 16;
// -1+16=15,说明未检索到 activePrimaryId,似乎是群聊签到,尝试 activeId
if (data_start === 15) {
data_start = textStr.indexOf('activeId=', position) + 9;
}
data_end = textStr.indexOf('&', data_start);
data.push(textStr.substring(data_start, data_end));
return ({ courseId: data[0], classId: data[1], aid: data[2] });
}
// 提取聊天群组ID
function getchatIdHex(hexStr: string) {
return hexStr.substring(hexStr.indexOf('29120f') + 6, hexStr.indexOf('1a16636f'))
}
const conn = new webIM.connection({
isMultiLoginSessions: WebIMConfig.isMultiLoginSessions,
https: WebIMConfig.https,
url: WebIMConfig.xmppURL,
apiUrl: WebIMConfig.apiURL,
isAutoLogin: WebIMConfig.isAutoLogin,
heartBeatWait: WebIMConfig.heartBeatWait,
autoReconnectNumMax: WebIMConfig.autoReconnectNumMax,
autoReconnectInterval: WebIMConfig.autoReconnectInterval,
appKey: WebIMConfig.appkey,
isHttpDNS: WebIMConfig.isHttpDNS
});

async function configure() {
const config = getJsonObject('configs/storage.json');
Expand Down Expand Up @@ -186,7 +158,7 @@ async function Sign(realname: string, params: any, config: any, activity: Activi
let result = 'fail';
// 群聊签到,无课程
if (activity.courseId === 'null') {
let page = await preSign2(params.uf, params._d, params.vc3, activity.aid, Monitor.ChatIDHex, params._uid, params.tuid);
let page = await preSign2(params.uf, params._d, params.vc3, activity.aid, activity.chatID, params._uid, params.tuid);
let activityType = speculateType(page);
switch (activityType) {
case 'general': {
Expand Down Expand Up @@ -270,63 +242,44 @@ async function Sign(realname: string, params: any, config: any, activity: Activi
// 配置默认签到信息
const config = await configure();

const monitor = new Monitor();
// 两种 loginhex ,第一种登录失败,尝试第二种
const loginHex = [monitor.generateLoginHex(IM_Params, 0), monitor.generateLoginHex(IM_Params, 1)];
conn.open({
apiUrl: WebIMConfig.apiURL,
user: IM_Params.myTuid,
accessToken: IM_Params.myToken,
appKey: WebIMConfig.appkey
});

console.log(blue('[监听中]'));
conn.listen({
onClosed: () => {
console.log('[监听停止]');
process.exit(0);
},
onTextMessage: async (message: any) => {
if (message?.ext?.attachment?.att_chat_course?.url.includes('sign')) {
const IM_CourseInfo = {
aid: message.ext.attachment.att_chat_course.aid,
classId: message.ext.attachment.att_chat_course.courseInfo.classid,
courseId: message.ext.attachment.att_chat_course.courseInfo.courseid,
}
const PPTActiveInfo = await getPPTActiveInfo(IM_CourseInfo.aid, params.uf, params._d, params._uid, params.vc3);

const errors = []; // 记录错误次数
while (errors.length < 2) {
await new Promise<void>((resolve, reject) => {
const ws = new WebSocket(Monitor.WebSocketURL);
let data = null;
ws.on('message', async (rawData) => {
data = rawData.toString();
// console.log(data);
if (data === 'o') {
// 发送登录数据包
ws.send(`["${hexToBase64(loginHex[errors.length])}"]`)
} else if (data.charAt(0) === 'a') {
// 向父进程发送连接正常消息
if (process.send) process.send('success');
const temp = base64toHex(data.split('"')[1])
// 有签到活动,发送请求获取签到内容
if (temp.startsWith(Monitor.COMING_SIGN_PREFIX)) {
Monitor.ChatIDHex = getchatIdHex(temp)
ws.send(`["${hexToBase64(monitor.generateGetActivityHex())}"]`)
} else if (temp.includes('7369676e')) {
// 当前内容包含 sign ,说明是签到信息
const IM_CourseInfo = parseCourseInfo(temp);
const PPTActiveInfo = await getPPTActiveInfo(IM_CourseInfo.aid, params.uf, params._d, params._uid, params.vc3);

// 签到 & 发邮件
if (IM_Params !== 'AuthFailed') {
const result = await Sign(IM_Params.myName, params, config.monitor, {
classId: IM_CourseInfo.classId,
courseId: IM_CourseInfo.courseId,
aid: Number(IM_CourseInfo.aid),
otherId: PPTActiveInfo.otherId,
ifphoto: PPTActiveInfo.ifphoto
});
if (config.mailing.to) sendEmail(IM_CourseInfo.aid, params._uid, IM_Params.myName, result, config.mailing);
}

// // 当获取到消息内容后,请求保持连接
// ws.send(`["${hexToBase64(monitor.generateKeepAliveHex())}"]`)
// 还是直接重连吧
ws.terminate();
resolve();
}
} else if (data.charAt(0) === 'c') {
// 本次是第二次错误,再向子进程发送登录失败信息
if (process.send && errors.length === 1) process.send('authfail');
reject('authfail');
// 签到 & 发邮件
if (IM_Params !== 'AuthFailed') {
const result = await Sign(IM_Params.myName, params, config.monitor, {
classId: IM_CourseInfo.classId,
courseId: IM_CourseInfo.courseId,
aid: Number(IM_CourseInfo.aid),
otherId: PPTActiveInfo.otherId,
ifphoto: PPTActiveInfo.ifphoto
});
if (config.mailing.to) sendEmail(IM_CourseInfo.aid, params._uid, IM_Params.myName, result, config.mailing);
}
})
}).catch((err) => {
errors.push(err);
});
}
console.log('失败次数到达2次,程序终止')
}
},
onError: (msg: string) => {
console.log(red('[发生异常]'), msg);
process.exit(0);
},
})
})();
Loading

0 comments on commit f6e2200

Please sign in to comment.