Skip to content
This repository has been archived by the owner on Nov 2, 2023. It is now read-only.

Commit

Permalink
Merge pull request #94 from takayama-lily/dev
Browse files Browse the repository at this point in the history
-
  • Loading branch information
takayama-lily authored Nov 22, 2020
2 parents ff6cf5b + 3a0cf37 commit e272a06
Show file tree
Hide file tree
Showing 19 changed files with 578 additions and 451 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
[![node engine](https://img.shields.io/node/v/oicq.svg)](https://nodejs.org) ← 注意版本

* QQ(安卓)协议的nodejs实现。也参考了一些其他开源仓库如mirai、miraiGo等。
* 以高效和稳定为第一目的,在此基础上不断完善功能。
* 将会逐步支持手机协议的大部分功能。
* 以高效和稳定为第一目的,在此基础上不断完善,将会逐步支持手机协议的大部分功能。
* 使用 [CQHTTP](https://cqhttp.cc) 风格的API、事件和参数(少量差异),并且原生支持经典的CQ码。
* 本项目使用AGPL-3.0许可证,旨在学习。不推荐也不提供商业化使用的支持。
* 有bug请告诉我!PR请基于dev分支!
Expand All @@ -19,21 +18,28 @@
```

```js
const oicq = require("oicq");
const uin = 123456789;
const bot = oicq.createClient(uin);
const {createClient} = require("oicq");
const uin = 123456789; // your account
const bot = createClient(uin);

bot.on("system.login.captcha", ()=>{
process.stdin.once("data", input=>{
bot.captchaLogin(input);
});
});

bot.on("message", data=>console.log(data));
bot.on("system.online", ()=>console.log("bot online!"));
bot.on("message", data=>{
console.log(data);
if (data.group_id > 0)
bot.sendGroupMsg(data.group_id, "hello");
else
bot.sendPrivateMsg(data.user_id, "hello");
});
bot.on("request", data=>console.log(data));
bot.on("notice", data=>console.log(data));

const password_md5 = "202cb962ac59075b964b07152d234b70";
const password_md5 = "202cb962ac59075b964b07152d234b70"; // your password md5
bot.login(password_md5);
```

Expand Down
16 changes: 10 additions & 6 deletions client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ export interface GroupInfo {
owner_id?: number,
last_join_time?: number,
last_sent_time?: number,
shutup_time_whole?: number,
shutup_time_me?: number,
shutup_time_whole?: number, //全员禁言到期时间
shutup_time_me?: number, //我的禁言到期时间
create_time?: number,
grade?: number,
max_admin_count?: number,
active_member_count?: number,
update_time?: number,
update_time?: number, //当前群资料的最后更新时间
}
export interface MemberInfo {
group_id?: number,
Expand All @@ -75,8 +75,8 @@ export interface MemberInfo {
title?: string,
title_expire_time?: number,
card_changeable?: boolean,
shutup_time?: number,
update_time?: number,
shutup_time?: number, //禁言到期时间
update_time?: number, //此群员资料的最后更新时间
}
export interface MessageId {
message_id: string
Expand Down Expand Up @@ -194,7 +194,7 @@ export class Client extends events.EventEmitter {
getFriendList(): RetFriendList;
getStrangerList(): RetStrangerList;
getGroupList(): RetGroupList;
getGroupMemberList(group_id: Uin): Promise<RetMemberList>;
getGroupMemberList(group_id: Uin, no_cache?: boolean): Promise<RetMemberList>;
getStrangerInfo(user_id: Uin, no_cache?: boolean): Promise<RetStrangerInfo>;
getGroupInfo(group_id: Uin, no_cache?: boolean): Promise<RetGroupInfo>;
getGroupMemberInfo(group_id: Uin, user_id: Uin, no_cache?: boolean): Promise<RetMemberInfo>;
Expand Down Expand Up @@ -247,6 +247,10 @@ export class Client extends events.EventEmitter {
once(event: string, listener: (data: EventData) => void): this;
on(event: string, listener: (data: EventData) => void): this;
off(event: string, listener: (data: EventData) => void): this;

//重载完成之前bot不接受其他任何请求,也不会上报任何事件
reloadFriendList(): Promise<RetCommon>;
reloadGroupList(): Promise<RetCommon>;
}

export function createClient(uin: Uin, config?: ConfBot): Client;
93 changes: 56 additions & 37 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const fs = require("fs");
const path = require("path");
const os = require("os");
const spawn = require("child_process");
const crypto = require("crypto");
const {randomBytes} = require("crypto");
const log4js = require("log4js");
const device = require("./device");
const {checkUin, timestamp} = require("./lib/common");
Expand Down Expand Up @@ -40,27 +40,25 @@ class Client extends net.Socket {
static ONLINE = Symbol("ONLINE");
}
class AndroidClient extends Client {
reconn_flag = true;
status = Client.OFFLINE;

nickname = "";
age = 0;
sex = "unknown";
online_status = 0;
fl = new Map(); //friendList
sl = new Map(); //strangerList
gl = new Map(); //groupList
gml = new Map(); //groupMemberList
fl = new Map; //friendList
sl = new Map; //strangerList
gl = new Map; //groupList
gml = new Map; //groupMemberList

recv_timestamp = 0;
send_timestamp = 0xffffffff;
heartbeat = null;
seq_id = 0;
handlers = new Map();
seq_cache = new Map();
handlers = new Map;
seq_cache = new Map;

session_id = crypto.randomBytes(4);
random_key = crypto.randomBytes(16);
session_id = randomBytes(4);
random_key = randomBytes(16);

sig = {
srm_token: BUF0,
Expand All @@ -80,15 +78,16 @@ class AndroidClient extends Client {

sync_finished = false;
sync_cookie;
const1 = crypto.randomBytes(4).readUInt32BE();
const2 = crypto.randomBytes(4).readUInt32BE();
const3 = crypto.randomBytes(1)[0];
const1 = randomBytes(4).readUInt32BE();
const2 = randomBytes(4).readUInt32BE();
const3 = randomBytes(1)[0];

stat = {
start_time: timestamp(),
lost_times: 0,
recv_pkt_cnt: 0,
sent_pkt_cnt: 0,
lost_pkt_cnt: 0,
recv_msg_cnt: 0,
sent_msg_cnt: 0,
};
Expand Down Expand Up @@ -123,24 +122,21 @@ class AndroidClient extends Client {
this.on("error", (err)=>{
this.logger.error(err.message);
});
this.on("close", (e_flag)=>{
this.on("close", ()=>{
this.read();
++this.stat.lost_times;
if (this.remoteAddress)
this.logger.info(`${this.remoteAddress}:${this.remotePort} closed`);
this.stopHeartbeat();
if (this.status === Client.OFFLINE) {
this.logger.error("网络不通畅。");
return this.em("system.offline.network", {message: "网络不通畅"});
}
this.status = Client.OFFLINE;
if (this.reconn_flag) {
if (e_flag)
this.reconn_flag = false;
} else if (this.status === Client.ONLINE) {
++this.stat.lost_times;
setTimeout(()=>{
this._connect(this.register.bind(this));
}, 500);
}
this.status = Client.OFFLINE;
});

// 在这里拆分包
Expand All @@ -149,7 +145,6 @@ class AndroidClient extends Client {
let len_buf = this.read(4);
let len = len_buf.readInt32BE();
if (this.readableLength >= len - 4) {
this.reconn_flag = true;
this.recv_timestamp = Date.now();
const packet = this.read(len - 4);
++this.stat.recv_pkt_cnt;
Expand Down Expand Up @@ -178,11 +173,16 @@ class AndroidClient extends Client {
resource.initGL.call(this)
]);
this.logger.info(`加载了${this.fl.size}个好友,${this.gl.size}个群。`);
await core.getMsg.call(this);
this.sync_finished = true;
this.logger.info("初始化完毕,开始处理消息。");
this.em("system.online");
});

this.on("internal.wt.failed", (message)=>{
this.logger.error(message);
this.terminate();
this.em("system.offline.network", {message});
});
}

_connect(callback = ()=>{}) {
Expand Down Expand Up @@ -212,6 +212,7 @@ class AndroidClient extends Client {
this.write(packet, ()=>{
const id = setTimeout(()=>{
this.handlers.delete(seq_id);
++this.stat.lost_pkt_cnt;
reject(new TimeoutError());
this.em("internal.timeout", {seq_id});
}, timeout);
Expand Down Expand Up @@ -239,11 +240,12 @@ class AndroidClient extends Client {
try {
await wt.heartbeat.call(this);
} catch {
core.getMsg.call(this);
try {
await wt.heartbeat.call(this);
} catch {
this.logger.warn("Heartbeat timeout!");
if (Date.now() - this.recv_timestamp > 15000)
if (Date.now() - this.recv_timestamp > 3000)
this.destroy();
}
}
Expand All @@ -259,24 +261,21 @@ class AndroidClient extends Client {
if (!await wt.register.call(this))
throw new Error();
} catch (e) {
this.logger.error("上线失败。");
this.terminate();
this.em("system.offline.network", {message: "register失败"});
return;
return this.emit("internal.wt.failed", "register失败。");
}
this.status = Client.ONLINE;
if (!this.online_status)
this.online_status = 11;
if (this.platform === 1)
this.setOnlineStatus(this.online_status);
this.setOnlineStatus(this.online_status);
this.startHeartbeat();
await core.getMsg.call(this);
if (!this.listenerCount("internal.kickoff")) {
this.once("internal.kickoff", (data)=>{
this.status = Client.INIT;
this.stopHeartbeat();
this.logger.warn(data.info);
let sub_type;
if (data.info.includes("另一")) {
if (data.info.includes("一台")) {
sub_type = "kickoff";
if (this.config.kickoff) {
this.logger.info("3秒后重新连接..");
Expand Down Expand Up @@ -410,19 +409,20 @@ class AndroidClient extends Client {

captchaLogin(captcha) {
if (!this.captcha_sign)
return this.logger.error("未收到图片验证码或已过期,你不能调用captchaLogin函数。");
return this.logger.warn("未收到图片验证码或已过期,你不能调用captchaLogin函数。");
this._connect(()=>{
wt.captchaLogin.call(this, captcha);
});
}

terminate() {
this.reconn_flag = false;
if (this.status === Client.ONLINE)
this.status = Client.INIT;
this.destroy();
}

async logout() {
if (this.isOnline) {
if (this.isOnline()) {
try {
await wt.register.call(this, true);
} catch {}
Expand All @@ -448,11 +448,24 @@ class AndroidClient extends Client {
return buildApiRet(0, this.gl);
}

async getGroupMemberList(group_id) {
async reloadFriendList() {
this.sync_finished = false;
const success = await resource.initFL.call(this);
this.sync_finished = true;
return buildApiRet(success?0:102);
}
async reloadGroupList() {
this.sync_finished = false;
const success = await resource.initGL.call(this);
this.sync_finished = true;
return buildApiRet(success?0:102);
}

async getGroupMemberList(group_id, no_cache = false) {
group_id = parseInt(group_id);
if (!checkUin(group_id))
return buildApiRet(100);
if (!this.gml.has(group_id))
if (!this.gml.has(group_id) || no_cache)
this.gml.set(group_id, resource.getGML.call(this, group_id));
let mlist = this.gml.get(group_id);
if (mlist instanceof Promise)
Expand Down Expand Up @@ -648,6 +661,7 @@ class AndroidClient extends Client {
status: this.online_status,
msg_cnt_per_min: this.calcMsgCnt(),
statistics: this.stat,
config: this.config
})
}
getLoginInfo() {
Expand All @@ -668,7 +682,12 @@ process.OICQ = {
logger
};

console.log("OICQ程序启动。当前内核版本:v" + version.version);
console.log(`
###########################################################################
# Package Version: oicq@${version.version} (Release on ${version.upday})
# View Changelogs:https://github.com/takayama-lily/oicq/releases #
###########################################################################
`);

function createDataDir(dir, uin) {
if (!fs.existsSync(dir))
Expand Down
Loading

0 comments on commit e272a06

Please sign in to comment.