Skip to content

Commit

Permalink
Merge pull request #2 from Clansty/easymob
Browse files Browse the repository at this point in the history
Easymob
  • Loading branch information
clansty authored Sep 25, 2021
2 parents 85bfc69 + 14da08b commit f300df4
Show file tree
Hide file tree
Showing 23 changed files with 968 additions and 208 deletions.
1 change: 0 additions & 1 deletion config.example.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
username: '学习通登录名'
password: 学习通密码
dbName: YJSNPI
bot:
uin: qq机器人用户名
password: 机器人密码
Expand Down
53 changes: 53 additions & 0 deletions handlers/handleEasemobMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import ImMessageCheckin from '../types/ImMessageCheckin'
import {error, info, success, warn} from '../utils/log'
import getCheckinDetail from '../requests/getCheckinDetail'
import * as db from '../providers/db'
import handlerSimpleCheckin from './handlerSimpleCheckin'
import pushQMsg from '../utils/pushQMsg'

export default async (message: ImMessageCheckin) => {
try {
if (!message.ext.attachment) return
if (message.ext.attachment.attachmentType !== 15) {
//不是签到信息
return
}
const aid = message.ext.attachment.att_chat_course.aid
const courseName = message.ext.attachment.att_chat_course.courseInfo.coursename
if (!aid) {
warn('处理 IM 消息时出现异常,找不到 aid')
return
}
switch (message.ext.attachment.att_chat_course.atype) {
case 2:
const checkinInfo = await getCheckinDetail(await db.getMeta<string>('cookie'), aid)
info('收到', checkinInfo.type, '类型签到')
let mts = `收到 ${courseName} 的签到\n类型:${checkinInfo.type}`
if (checkinInfo.type !== 'qr') {
try {
const res = await handlerSimpleCheckin(aid)
mts += `\n自动签到:${res}`
if (res === 'success') {
success('签到成功', aid)
}
else
warn('签到失败', aid, res)
} catch (e) {
error('签到失败', aid, e)
mts += `\n自动签到:抛错\n${e}`
}
}
else {
info('收到二维码签到')
mts = `收到 ${courseName} 的二维码签到,需要提供一张二维码`
}
pushQMsg(mts)
break
default:
const activityName=message.ext.attachment.att_chat_course.atypeName
pushQMsg(`收到 ${courseName}${activityName} 类型活动`)
}
} catch (e) {
error('处理 IM 消息时出现异常,可能不是活动消息', e)
}
}
12 changes: 12 additions & 0 deletions handlers/handlerSimpleCheckin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as db from '../providers/db'
import {genSimpleCheckinParams} from '../utils/genCheckinParams'
import checkin from '../requests/checkin'

export default async (activeId: string | number) => {
const cookie = await db.getMeta<string>('cookie')
const name = await db.getMeta<string>('name')
const uid = await db.getMeta<number>('uid')

const params = genSimpleCheckinParams({uid, name, activeId})
return await checkin(cookie, params)
}
65 changes: 34 additions & 31 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import config from "./providers/config";
import validateCookie from "./requests/validateCookie";
import { info, warn } from "./utils/log";
import * as db from "./providers/db";
import { bot, loginBot } from "./providers/bot";
import loginAndSaveInfo from "./utils/loginAndSaveInfo";
import axios from "axios";
import attachGroupMessageHandler from "./handlers/attachGroupMessageHandler";
import validateCookie from './requests/validateCookie'
import {info, success, warn} from './utils/log'
import * as db from './providers/db'
import {bot, loginBot} from './providers/bot'
import loginAndSaveInfo from './utils/loginAndSaveInfo'
import axios from 'axios'
import attachGroupMessageHandler from './handlers/attachGroupMessageHandler'
import {imConnect} from './providers/easemob'

(async () => {
//初始化数据库连接和 bot
axios.defaults.proxy = false;
await db.connect(config.dbName);
await loginBot();
console.info("系统初始化完毕");
//验证及获取 cookie
let isCookieValid = false;
let cookie = await db.getMeta<string>("cookie");
if (cookie) {
isCookieValid = await validateCookie(cookie);
if (isCookieValid) info("Cookie 有效");
else warn("Cookie 无效");
}
if (!isCookieValid) {
cookie = await loginAndSaveInfo();
}
//登录步骤完成
const schoolname = await db.getMeta<string>("schoolname");
const name = await db.getMeta<string>("name");
info(`欢迎来自 ${schoolname}${name}`);
//机器人接收二维码和解码签到事件
attachGroupMessageHandler(bot);
})();
//初始化数据库连接和 bot
axios.defaults.proxy = false
await db.connect()
await loginBot()
//验证及获取 cookie
let isCookieValid = false
let cookie = await db.getMeta<string>('cookie')
if (cookie) {
isCookieValid = await validateCookie(cookie)
if (isCookieValid) info('Cookie 有效')
else warn('Cookie 无效')
}
if (!isCookieValid) {
cookie = await loginAndSaveInfo()
}
//登录步骤完成
const schoolname = await db.getMeta<string>('schoolname')
const name = await db.getMeta<string>('name')
success(`欢迎来自 ${schoolname}${name}`)
//机器人接收二维码和解码签到事件
attachGroupMessageHandler(bot)
//连接 IM
info('准备连接 IM')
await imConnect()
info('系统初始化完毕')
})()
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@types/jsdom": "^16.2.13",
"@types/node": "^16.9.2",
"ts-node": "^10.2.1",
"tsc": "^2.0.3",
Expand All @@ -21,7 +22,9 @@
"axios": "^0.21.4",
"axios-cookiejar-support": "^2.0.0",
"chalk": "^4.1.2",
"cheerio": "^1.0.0-rc.10",
"jimp": "^0.16.1",
"jsdom": "^17.0.0",
"oicq": "^1.20.2",
"qrcode-reader": "^1.0.4",
"tough-cookie": "^4.0.0",
Expand Down
1 change: 0 additions & 1 deletion providers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import YAML from "yaml";
interface Config {
username: string;
password: string;
dbName: string;
bot: {
uin: number;
password: string;
Expand Down
53 changes: 26 additions & 27 deletions providers/db.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
import fs from "fs/promises";
import fsOrigin from "fs";
import fs from 'fs/promises'
import fsOrigin from 'fs'

let meta: Record<string, any> = {};
let thisDbName: string;
let meta: Record<string, any> = {}

export const connect = async (dbName: string) => {
thisDbName = dbName;
try {
// 按道理可以改成同步的,但是懒得手动返回 Promise 了
const metaStream = await fs.readFile(
`./build/data/data-${thisDbName}.json`
);
const metaStr = metaStream.toString();
meta = JSON.parse(metaStr);
} catch (error) {} // 试图读取数据,没有数据就当无事发生
};
export const connect = async () => {
try {
// 按道理可以改成同步的,但是懒得手动返回 Promise 了
const metaStream = await fs.readFile(
'./data/superstar-data.json',
)
const metaStr = metaStream.toString()
meta = JSON.parse(metaStr)
} catch (error) {
} // 试图读取数据,没有数据就当无事发生
}

export const getMeta = async <T>(name: string) => {
const data = meta[name];
return data ? (data as T) : null;
};
const data = meta[name]
return data ? (data as T) : null
}

export const setMeta = async <T>(name: string, value: T) => {
meta[name] = value;
};
meta[name] = value
}

process.on("SIGINT", () => {
fsOrigin.writeFileSync(
`./build/data/data-${thisDbName}.json`,
JSON.stringify(meta, null, 2)
);
process.exit(0);
});
process.on('SIGINT', () => {
fsOrigin.writeFileSync(
'./data/superstar-data.json',
JSON.stringify(meta, null, 2),
)
process.exit(0)
})
118 changes: 118 additions & 0 deletions providers/easemob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//提供学习通即时通信协议

import jsdom from 'jsdom'

const {JSDOM} = jsdom //在jsdom中导出JSDOM对象
const {window} = new JSDOM('<!doctype html><html><body></body></html>', {url: 'https://im.chaoxing.com/webim/me'}); //导出JSDOM中的window对象
(global as any).window = window; //将window对象设置为nodejs中全局对象;
(global as any).navigator = window.navigator;
(global as any).location = window.location;
(global as any).document = window.document;
(global as any).WebSocket = window.WebSocket
import '../sdk/Easemob-chat-3.6.3'
import {info, success, warn} from '../utils/log'
import * as db from './db'
import getImToken from '../requests/getImToken'
import handleEasemobMessage from '../handlers/handleEasemobMessage'
import sleep from '../utils/sleep'

window.WebIM.config = {
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: Number.POSITIVE_INFINITY,
autoReconnectInterval: 2,
isWebRTC: true,
heartBeatWait: 2000,
delivery: false,
}
window.WebIM.conn = new window.WebIM.connection({
appKey: window.WebIM.config.appkey,
isHttpDNS: window.WebIM.config.isHttpDNS,
isMultiLoginSessions: window.WebIM.config.isMultiLoginSessions,
host: window.WebIM.config.Host,
https: window.WebIM.config.https,
url: window.WebIM.config.xmppURL,
apiUrl: window.WebIM.config.apiURL,
isAutoLogin: false,
heartBeatWait: window.WebIM.config.heartBeatWait,
autoReconnectNumMax: window.WebIM.config.autoReconnectNumMax,
autoReconnectInterval: window.WebIM.config.autoReconnectInterval,
isStropheLog: window.WebIM.config.isStropheLog,
delivery: window.WebIM.config.delivery,
isDebug: window.WebIM.config.isDebug,
})

window.WebIM.conn.listen({
onOpened: function (message) {
success('IM 协议连接成功')
},
onClosed: function (message) {
warn('IM 协议连接关闭')
},
onTextMessage: function (message) {
info('IM 协议收到文本消息', JSON.stringify(message))
handleEasemobMessage(message)
},
onEmojiMessage: function (message) {
},
onPictureMessage: function (message) {
},
onCmdMessage: function (message) {
},
onAudioMessage: function (message) {
},
onLocationMessage: function (message) {
},
onFileMessage: function (message) {
},
onVideoMessage: function (message) {
},
onPresence: function (message) {
},
onRoster: function (message) {
},
onInviteMessage: function (message) {
},
onOnline: function () {
},
onOffline: function () {
warn('IM 下线')
},
onError: async function (message) {
warn('IM 协议错误', message)
if(message.type===40){
window.WebIM.conn.close()
warn('IM 协议身份验证失败,重新获取 token')
await sleep(2000)
imConnect()
}
},
onBlacklistUpdate: function (list) {
},
})

const connect = (user: string | number, accessToken: string) => {
const options = {
apiUrl: 'https://a1-vip6.easecdn.com',
user,
accessToken,
appKey: 'cx-dev#cxstudy',
}
window.WebIM.conn.open(options)
}

export const imConnect = async () => {
const cookie = await db.getMeta<string>('cookie')
const uid = await db.getMeta<number>('uid')
const imToken = await getImToken(cookie)
connect(uid, imToken)
}
9 changes: 7 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
## 功能(已实现部分)

- [x] 自动取 Cookie,过期自动更新
- [ ] 定时检测签到,在群内发送提醒并自动签到
- [ ] 定时检测签到
- [x] 支持通过学习通 IM 协议实时获取签到推送
- [x] 在群内发送提醒并自动签到
- [x] 通过 QQ 机器人接收群内发送的二维码实现二维码签到
本功能只需要一张已过期的二维码就可以
- [ ] 通过 REST API 设置课程信息
- [ ] 支持多用户共同签到

## 部署方法

1. 安装 MongoDB 和 NodeJS
1. 安装 NodeJS 12.16 以上版本
2. `yarn install`
3. `cp config.example.yaml config.yaml` 并在 `config.yaml` 中填写配置项
4. `yarn build`
Expand All @@ -30,11 +33,13 @@

- [mkdir700/chaoxing_auto_sign](https://github.com/mkdir700/chaoxing_auto_sign)
- [SSmJaE/XueXiTonsSign_Electron](https://github.com/SSmJaE/XueXiTonsSign_Electron)
- [cyanray/cx-auto-sign](https://github.com/cyanray/cx-auto-sign)

更多有关学习通签到的项目值得尝试

| 项目地址 | 开发语言 | 备注 |
| ------------------------------------------------------- | ---------- | ---------------------------------------------- |
| https://github.com/cyanray/cx-auto-sign | C# | 超星学习通自动签到工具,通过IM协议监测签到活动。 |
| https://github.com/mkdir700/chaoxing_auto_sign | Python | 超星学习通自动签到脚本&多用户多任务&API |
| https://github.com/PrintNow/ChaoxingSign | PHP | PHP版超星自动签到,支持多用户,二次开发便捷!|
| https://github.com/Wzb3422/auto-sign-chaoxing | TypeScript | 超星学习通自动签到,梦中刷网课 |
Expand Down
2 changes: 1 addition & 1 deletion requests/getCheckinDetail.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios'
import {MOBILE_AGENT} from '../constants'
import {CheckinDetailRet} from '../types/CheckinDetailRet'
import CheckinDetailRet from '../types/CheckinDetailRet'
import {error} from '../utils/log'

/**
Expand Down
Loading

0 comments on commit f300df4

Please sign in to comment.