Skip to content

Commit

Permalink
移除 cq-websocket 依赖;添加 cq-http 支持;实现QQ机器人推送、签到;一些优化
Browse files Browse the repository at this point in the history
  • Loading branch information
cxOrz committed Mar 30, 2023
1 parent 1582522 commit 322d9c2
Show file tree
Hide file tree
Showing 17 changed files with 1,007 additions and 1,242 deletions.
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ pnpm start

### 监听模式

每次需要时启用 2 - 4 小时较为合适,请勿挂着不关。
支持开启QQ机器人、邮件推送、pushplus推送;

**QQ 机器人**:根据 [go-cqhttp](https://docs.go-cqhttp.org/guide/quick_start.html) 文档,配置正向 WebSocket、QQ号、密码,并运行 go-cqhttp 程序,即可运行监听模式并启用该选项。

如需发送二维码让机器人识别并签到,请配置 `env.json``SecretId``SecretKey`,将使用腾讯云OCR进行识别和处理。

监听模式每次需要时启用 2 - 4 小时较为合适,最好不要挂着不关。

## 高级 🎲

Expand All @@ -110,16 +116,6 @@ docker run -d -p 80:80 -p 5000:5000 chaoxing-sign-cli

> 出现问题?先仔细阅读相关说明,若仍无法解决请发 issue
### 连接 QQ 机器人(go-cqhttp)

在使用 QQ 机器人之前请先按照 [go-cqhttp 基础教程](https://docs.go-cqhttp.org/guide/quick_start.html)安装好 [go-cqhttp 服务器](https://docs.go-cqhttp.org/)。本项目使用正向 WebSocket 连接至 go-cqhttp 服务器。请按照 [go-cqhttp 配置文档](https://docs.go-cqhttp.org/guide/config.html)配置好正向服务器以及其连接密钥(可选,为了安全建议配置好密钥)。

#### 配置本项目的 go-cqhttp 连接

**构建本项目后**打开 `apps/server/build/env.json` 文件,编辑`cqserver`组内的值。更详细的参数介绍请参阅[官方文档](https://github.com/Tsuk1ko/node-cq-websocket/blob/master/docs/api/CQWebSocket.md#cqwebsocketoption)

若想读取消息中的签到二维码文本,请配置好`env.json`内的腾讯云 OCR 密钥。

### 展示

演示地址:https://prod.d6afmntd8nh5y.amplifyapp.com (海外服务器较慢,功能阉割仅供演示UI)
Expand Down
3 changes: 1 addition & 2 deletions apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "server",
"version": "3.8.9",
"version": "4.2.5",
"description": "超星学习通签到项目",
"scripts": {
"build": "tsc && node ./src/scripts/cplibs.js",
Expand All @@ -14,7 +14,6 @@
},
"dependencies": {
"@koa/router": "^12.0.0",
"@tsuk1ko/cq-websocket": "^2.3.1",
"crypto-js": "^4.1.1",
"form-data": "^4.0.0",
"jsdom": "^20.0.0",
Expand Down
10 changes: 0 additions & 10 deletions apps/server/src/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,5 @@
"tencent": {
"secretId": "",
"secretKey": ""
},
"cqserver": {
"host": "127.0.0.1",
"port": 8080,
"enableAPI": true,
"enableEvent": true,
"accessToken": "",
"reconnection": true,
"reconnectionAttempts": 10,
"reconnectionDelay": 5000
}
}
42 changes: 36 additions & 6 deletions apps/server/src/functions/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { cookieSerialize, request } from '../utils/request';
* 返回一个签到信息对象 {activeId, name, courseId, classId, otherId}
* @param {{courseId:string, classId:string}[]} courses
*/
export const traverseCourseActivity = async (args: BasicCookie & { courses: CourseType[] }): Promise<string | Activity> => {
export const traverseCourseActivity = async (args: BasicCookie & { courses: CourseType[]; }): Promise<string | Activity> => {
console.log('正在查询有效签到活动,等待时间视网络情况而定...');
const { courses, ...cookies } = args;
let i = 0;
Expand All @@ -31,7 +31,7 @@ export const traverseCourseActivity = async (args: BasicCookie & { courses: Cour
try {
// 任务数组中任意一个成功就返回;否则,抛出异常
return await Promise.any(tasks);
} catch (error) {}
} catch (error) { }
// 每轮请求任务组之后,清空任务数组供下轮使用
tasks = [];
}
Expand All @@ -44,7 +44,7 @@ export const traverseCourseActivity = async (args: BasicCookie & { courses: Cour
/**
* @returns 签到信息对象
*/
export const getActivity = async (args: BasicCookie & { course: CourseType }): Promise<string | Activity> => {
export const getActivity = async (args: BasicCookie & { course: CourseType; }): Promise<string | Activity> => {
const { course, ...cookies } = args;
const result = await request(
`${ACTIVELIST.URL}?fid=0&courseId=${course.courseId}&classId=${course.classId}&_=${new Date().getTime()}`,
Expand Down Expand Up @@ -87,7 +87,7 @@ export const getActivity = async (args: BasicCookie & { course: CourseType }): P
/**
* 根据 activeId 请求获得签到信息
*/
export const getPPTActiveInfo = async ({ activeId, ...cookies }: BasicCookie & { activeId: string }) => {
export const getPPTActiveInfo = async ({ activeId, ...cookies }: BasicCookie & { activeId: string; }) => {
const result = await request(`${PPTACTIVEINFO.URL}?activeId=${activeId}`, {
secure: true,
headers: {
Expand All @@ -99,7 +99,7 @@ export const getPPTActiveInfo = async ({ activeId, ...cookies }: BasicCookie & {
};

// 预签到请求
export const preSign = async (args: BasicCookie & { activeId: string; courseId: string; classId: string }) => {
export const preSign = async (args: BasicCookie & { activeId: string; courseId: string; classId: string; }) => {
const { activeId, classId, courseId, ...cookies } = args;
await request(
`${PRESIGN.URL}?courseId=${courseId}&classId=${classId}&activePrimaryId=${activeId}&general=1&sys=1&ls=1&appType=15&&tid=&uid=${args._uid}&ut=s`,
Expand All @@ -113,7 +113,7 @@ export const preSign = async (args: BasicCookie & { activeId: string; courseId:
console.log(`[预签]已请求`);
};

export const preSign2 = async (args: BasicCookie & { activeId: string; chatId: string; _uid: string; tuid: string }) => {
export const preSign2 = async (args: BasicCookie & { activeId: string; chatId: string; _uid: string; tuid: string; }) => {
const { activeId, chatId, tuid, ...cookies } = args;
const result = await request(
`${CHAT_GROUP.PRESTUSIGN.URL}?activeId=${activeId}&code=&uid=${cookies._uid}&courseId=null&classId=0&general=0&chatId=${chatId}&appType=0&tid=${tuid}&atype=null&sys=0`,
Expand Down Expand Up @@ -142,3 +142,33 @@ export const speculateType = (text: string) => {
// 普通、手势
return 'general';
};

/**
* 解析签到类型
* @param iptPPTActiveInfo getPPTActiveInfo 的返回对象
*/
export const getSignType = (iptPPTActiveInfo: any) => {
switch (iptPPTActiveInfo.otherId) {
case 0:
if (iptPPTActiveInfo.ifphoto == 1) { return "拍照签到"; } else { return "普通签到"; }
case 2: return "二维码签到";
case 3: return "手势签到";
case 4: return "位置签到";
case 5: return "签到码签到";
default: return "未知";
}
};

/**
* 解析签到结果
* @param iptResult 签到结果
* @returns 返回具体的中文结果
*/
export const getSignResult = (iptResult: string) => {
switch (iptResult) {
case 'success': return "成功";
case 'fail': return "失败";
case 'fail-need-qrcode': return "请发送二维码";
default: return iptResult;
}
};
80 changes: 80 additions & 0 deletions apps/server/src/functions/cq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export default class CQ {
#ws_url: string;
#ws_instance?: WebSocket;
#target_type: string;
#target_id: number;
#cache: Map<string, any>;

constructor(ws_url: string);
constructor(ws_url: string, target_type: string, target_id: number);
constructor(arg1: string, arg2?: string, arg3?: number) {
this.#ws_url = arg1;
this.#target_type = arg2 || 'private';
this.#target_id = arg3 || 1001;
this.#cache = new Map();
}

static hasImage = (msg: string) => {
return msg.includes('[CQ:image');
};

connect() {
if (!this.#ws_instance) this.#ws_instance = new WebSocket(this.#ws_url);
// 连接失败处理
this.#ws_instance.onerror = function (e) {
console.log('[连接异常]', e);
};
return this;
}

send(text: string, target_id: number): void;
send(text: string, target_id: number, action: string, payload: any): void;
send(arg1: string, arg2: number, arg3?: string, arg4?: any): void {
if (arg3) { this.#ws_instance?.send(JSON.stringify({ action: arg3, params: arg4 })); return; };
const payload: any = {
action: arg3 || 'send_msg',
params: {
message_type: this.#target_type,
message: arg1,
auto_escape: true
}
};
this.#target_type === 'private' ? payload.params.user_id = arg2 : payload.params.group_id = arg2;
this.#ws_instance?.send(JSON.stringify(payload));
}

close() {
this.#ws_instance?.close();
}

getTargetID() {
return this.#target_id;
}

getCache(key: string) {
return this.#cache.get(key);
}

setCache(key: string, object: any) {
this.#cache.set(key, object);
}

clearCache() {
this.#cache.clear();
}

onMessage(handler: (this: CQ, data: string) => void) {
if (this.#ws_instance) {
const context = this;
this.#ws_instance.onmessage = function (e) {
const data = JSON.parse(e.data);
const isTarget = !!(data.group_id === context.#target_id || data.user_id === context.#target_id);
// 仅处理消息类型,且来自目标用户或群
if (data.post_type === 'message' && isTarget) {
handler.call(context, data.raw_message);
}
};
}
}

}
22 changes: 8 additions & 14 deletions apps/server/src/functions/general.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CHAT_GROUP, PPTSIGN } from '../configs/api';
import { cookieSerialize, request } from '../utils/request';

export const GeneralSign = async (args: BasicCookie & { name: string; activeId: string; fid: string }): Promise<string> => {
export const GeneralSign = async (args: BasicCookie & { name: string; activeId: string; fid: string; }): Promise<string> => {
const { name, activeId, fid, ...cookies } = args;
const url = `${PPTSIGN.URL}?activeId=${activeId}&uid=${cookies._uid}&clientip=&latitude=-1&longitude=-1&appType=15&fid=${fid}&name=${name}`;
const result = await request(url, {
Expand All @@ -10,18 +10,15 @@ export const GeneralSign = async (args: BasicCookie & { name: string; activeId:
Cookie: cookieSerialize(cookies),
},
});
if (result.data === 'success') {
console.log(`[通用]签到成功`);
return 'success';
}
console.log(result.data);
return result.data;
const msg = result.data === 'success' ? '[通用]签到成功' : `[通用]${result.data}`;
console.log(msg);
return msg;
};

/**
* 群聊签到方式,无课程
*/
export const GeneralSign_2 = async (args: BasicCookie & { activeId: string }): Promise<string> => {
export const GeneralSign_2 = async (args: BasicCookie & { activeId: string; }): Promise<string> => {
const { activeId, ...cookies } = args;
const url = `${CHAT_GROUP.SIGN.URL}?activeId=${activeId}&uid=${cookies._uid}&clientip=`;
const result = await request(url, {
Expand All @@ -30,10 +27,7 @@ export const GeneralSign_2 = async (args: BasicCookie & { activeId: string }): P
Cookie: cookieSerialize(cookies),
},
});
if (result.data === 'success') {
console.log(`[通用]签到成功`);
return 'success';
}
console.log(result.data);
return result.data;
const msg = result.data === 'success' ? '[通用]签到成功' : `[通用]${result.data}`;
console.log(msg);
return msg;
};
18 changes: 6 additions & 12 deletions apps/server/src/functions/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ export const LocationSign = async (
Cookie: cookieSerialize(cookies),
},
});
if (result.data === 'success') {
console.log(`[位置]签到成功`);
return 'success';
}
console.log(result.data);
return result.data;
const msg = result.data === 'success' ? '[位置]签到成功' : `[位置]${result.data}`;
console.log(msg);
return msg;
};

/**
Expand All @@ -42,10 +39,7 @@ export const LocationSign_2 = async (
},
formdata
);
if (result.data === 'success') {
console.log(`[位置]签到成功`);
return 'success';
}
console.log(result.data);
return result.data;
const msg = result.data === 'success' ? '[位置]签到成功' : `[位置]${result.data}`;
console.log(msg);
return msg;
};
16 changes: 6 additions & 10 deletions apps/server/src/functions/photo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ export const PhotoSign = async (
Cookie: cookieSerialize(cookies),
},
});
if (result.data === 'success') {
console.log(`[拍照]签到成功`);
return 'success';
}
return result.data;
const msg = result.data === 'success' ? '[拍照]签到成功' : `[拍照]${result.data}`;
console.log(msg);
return msg;
};

export const PhotoSign_2 = async (args: BasicCookie & { objectId: string; activeId: string }): Promise<string> => {
Expand All @@ -34,11 +32,9 @@ export const PhotoSign_2 = async (args: BasicCookie & { objectId: string; active
Cookie: cookieSerialize(cookies),
},
});
if (result.data === 'success') {
console.log(`[拍照]签到成功`);
return 'success';
}
return result.data;
const msg = result.data === 'success' ? '[拍照]签到成功' : `[拍照]${result.data}`;
console.log(msg);
return msg;
};

// 在Termux或其他终端中使用,从云盘获取图片
Expand Down
17 changes: 6 additions & 11 deletions apps/server/src/functions/qrcode.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { PPTSIGN } from '../configs/api';
import { cookieSerialize, request } from '../utils/request';

export const QRCodeSign = async (args: BasicCookie & { enc: string; name: string; fid: string; activeId: string }) => {
export const QRCodeSign = async (args: BasicCookie & { enc: string; name: string; fid: string; activeId: string; }) => {
const { enc, name, fid, activeId, ...cookies } = args;
const url = `${PPTSIGN.URL}?enc=${enc}&name=${encodeURI(name)}&activeId=${activeId}&uid=${
cookies._uid
}&clientip=&useragent=&latitude=-1&longitude=-1&fid=${fid}&appType=15`;
const url = `${PPTSIGN.URL}?enc=${enc}&name=${encodeURI(name)}&activeId=${activeId}&uid=${cookies._uid
}&clientip=&useragent=&latitude=-1&longitude=-1&fid=${fid}&appType=15`;
const result = await request(url, {
secure: true,
headers: {
Cookie: cookieSerialize(cookies),
},
});
if (result.data === 'success') {
console.log(`[二维码]签到成功`);
return 'success';
} else {
console.log(result.data);
return result.data;
}
const msg = result.data === 'success' ? '[二维码]签到成功' : `[二维码]${result.data}`;
console.log(msg);
return msg;
};
Loading

0 comments on commit 322d9c2

Please sign in to comment.