2022-09-01 15:35:27 +00:00
|
|
|
|
import {
|
|
|
|
|
Client,
|
2023-06-30 11:26:45 +00:00
|
|
|
|
DiscussMessageEvent, Forwardable,
|
2022-09-01 15:35:27 +00:00
|
|
|
|
Friend,
|
|
|
|
|
Group,
|
|
|
|
|
GroupMessageEvent,
|
|
|
|
|
LogLevel,
|
2023-06-30 11:26:45 +00:00
|
|
|
|
Platform, PrivateMessage,
|
|
|
|
|
PrivateMessageEvent, XmlElem,
|
|
|
|
|
} from 'icqq';
|
2022-03-01 13:11:14 +00:00
|
|
|
|
import Buffer from 'buffer';
|
2022-02-23 06:09:54 +00:00
|
|
|
|
import { execSync } from 'child_process';
|
|
|
|
|
import random from '../utils/random';
|
|
|
|
|
import fs from 'fs';
|
|
|
|
|
import fsP from 'fs/promises';
|
2023-06-30 11:26:45 +00:00
|
|
|
|
import { Config } from 'icqq/lib/client';
|
2022-10-12 05:07:48 +00:00
|
|
|
|
import dataPath from '../helpers/dataPath';
|
2023-01-16 07:26:51 +00:00
|
|
|
|
import os from 'os';
|
2023-06-30 11:26:45 +00:00
|
|
|
|
import { Converter, Image, rand2uuid } from 'icqq/lib/message';
|
|
|
|
|
import { randomBytes } from 'crypto';
|
|
|
|
|
import { escapeXml, gzip, timestamp } from 'icqq/lib/common';
|
|
|
|
|
import { pb } from 'icqq/lib/core';
|
2024-01-15 09:58:48 +00:00
|
|
|
|
import env from '../models/env';
|
2022-02-23 06:09:54 +00:00
|
|
|
|
|
2024-01-15 09:58:48 +00:00
|
|
|
|
const LOG_LEVEL: LogLevel = env.OICQ_LOG_LEVEL;
|
2022-02-26 10:15:40 +00:00
|
|
|
|
|
2022-03-01 13:11:14 +00:00
|
|
|
|
type MessageHandler = (event: PrivateMessageEvent | GroupMessageEvent) => Promise<boolean | void>
|
|
|
|
|
|
2022-02-23 06:09:54 +00:00
|
|
|
|
interface CreateOicqParams {
|
2022-12-23 06:34:25 +00:00
|
|
|
|
id: number;
|
2022-02-23 06:09:54 +00:00
|
|
|
|
uin: number;
|
|
|
|
|
password: string;
|
|
|
|
|
platform: Platform;
|
2023-06-30 11:26:45 +00:00
|
|
|
|
signApi?: string;
|
2023-07-29 17:48:06 +00:00
|
|
|
|
signVer?: string;
|
2022-03-07 10:05:14 +00:00
|
|
|
|
// 当需要验证手机时调用此方法,应该返回收到的手机验证码
|
2022-02-23 06:09:54 +00:00
|
|
|
|
onVerifyDevice: (phone: string) => Promise<string>;
|
|
|
|
|
// 当滑块时调用此方法,返回 ticker,也可以返回假值改用扫码登录
|
|
|
|
|
onVerifySlider: (url: string) => Promise<string>;
|
2024-01-10 10:26:31 +00:00
|
|
|
|
signDockerId?: string;
|
2022-02-23 06:09:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OicqExtended??
|
|
|
|
|
export default class OicqClient extends Client {
|
2022-03-01 13:11:14 +00:00
|
|
|
|
private readonly onMessageHandlers: Array<MessageHandler> = [];
|
|
|
|
|
|
2024-01-10 10:26:31 +00:00
|
|
|
|
private constructor(uin: number, public readonly id: number, conf?: Config,
|
|
|
|
|
public readonly signDockerId?: string) {
|
2023-06-30 11:26:45 +00:00
|
|
|
|
super(conf);
|
2022-02-23 06:09:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-23 06:34:25 +00:00
|
|
|
|
private static existedBots = {} as { [id: number]: OicqClient };
|
|
|
|
|
|
2023-07-29 17:49:09 +00:00
|
|
|
|
private isOnMessageCreated = false;
|
|
|
|
|
|
2022-02-23 06:09:54 +00:00
|
|
|
|
public static create(params: CreateOicqParams) {
|
2022-12-23 06:34:25 +00:00
|
|
|
|
if (this.existedBots[params.id]) {
|
|
|
|
|
return Promise.resolve(this.existedBots[params.id]);
|
|
|
|
|
}
|
2022-02-23 06:09:54 +00:00
|
|
|
|
return new Promise<OicqClient>(async (resolve, reject) => {
|
2023-06-30 11:26:45 +00:00
|
|
|
|
const loginDeviceHandler = async ({ phone }: { url: string, phone: string }) => {
|
2022-02-23 06:09:54 +00:00
|
|
|
|
client.sendSmsCode();
|
|
|
|
|
const code = await params.onVerifyDevice(phone);
|
2022-12-23 06:34:25 +00:00
|
|
|
|
if (code === 'qrsubmit') {
|
|
|
|
|
client.login();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
client.submitSmsCode(code);
|
|
|
|
|
}
|
2023-06-30 11:26:45 +00:00
|
|
|
|
};
|
2022-02-23 06:09:54 +00:00
|
|
|
|
|
2023-06-30 11:26:45 +00:00
|
|
|
|
const loginSliderHandler = async ({ url }: { url: string }) => {
|
2022-02-23 06:09:54 +00:00
|
|
|
|
const res = await params.onVerifySlider(url);
|
|
|
|
|
if (res) {
|
|
|
|
|
client.submitSlider(res);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
client.login();
|
|
|
|
|
}
|
2023-06-30 11:26:45 +00:00
|
|
|
|
};
|
2022-02-23 06:09:54 +00:00
|
|
|
|
|
2023-06-30 11:26:45 +00:00
|
|
|
|
const loginErrorHandler = ({ message }: { code: number; message: string }) => {
|
2022-02-23 06:09:54 +00:00
|
|
|
|
reject(message);
|
2023-06-30 11:26:45 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const successLoginHandler = () => {
|
|
|
|
|
client.offTrap('system.login.device', loginDeviceHandler);
|
|
|
|
|
client.offTrap('system.login.slider', loginSliderHandler);
|
|
|
|
|
client.offTrap('system.login.error', loginErrorHandler);
|
|
|
|
|
client.offTrap('system.online', successLoginHandler);
|
2023-07-29 17:49:09 +00:00
|
|
|
|
|
|
|
|
|
if (!client.isOnMessageCreated) {
|
|
|
|
|
client.trap('message', client.onMessage);
|
|
|
|
|
client.isOnMessageCreated = true;
|
2023-11-05 18:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-23 06:09:54 +00:00
|
|
|
|
resolve(client);
|
2023-06-30 11:26:45 +00:00
|
|
|
|
};
|
2022-02-23 06:09:54 +00:00
|
|
|
|
|
2023-06-30 11:26:45 +00:00
|
|
|
|
if (!fs.existsSync(dataPath(`${params.uin}/device.json`))) {
|
2022-10-12 05:07:48 +00:00
|
|
|
|
await fsP.mkdir(dataPath(params.uin.toString()), { recursive: true });
|
2022-02-23 06:09:54 +00:00
|
|
|
|
|
|
|
|
|
const device = {
|
|
|
|
|
product: 'Q2TG',
|
|
|
|
|
device: 'ANGELKAWAII2',
|
2022-12-23 06:34:25 +00:00
|
|
|
|
board: 'rainbowcat',
|
2022-02-23 06:09:54 +00:00
|
|
|
|
brand: random.pick('GOOGLE', 'XIAOMI', 'HUAWEI', 'SAMSUNG', 'SONY'),
|
2022-12-23 06:34:25 +00:00
|
|
|
|
model: 'rainbowcat',
|
2022-03-01 13:11:14 +00:00
|
|
|
|
wifi_ssid: random.pick('OpenWrt', `Redmi-${random.hex(4).toUpperCase()}`,
|
|
|
|
|
`MiWifi-${random.hex(4).toUpperCase()}`, `TP-LINK-${random.hex(6).toUpperCase()}`),
|
2022-02-23 06:09:54 +00:00
|
|
|
|
bootloader: random.pick('U-Boot', 'GRUB', 'gummiboot'),
|
|
|
|
|
android_id: random.hex(16),
|
2023-01-16 07:26:51 +00:00
|
|
|
|
proc_version: `${os.type()} version ${os.release()}`,
|
2022-02-23 06:09:54 +00:00
|
|
|
|
mac_address: `8c:85:90:${random.hex(2)}:${random.hex(2)}:${random.hex(2)}`.toUpperCase(),
|
|
|
|
|
ip_address: `192.168.${random.int(1, 200)}.${random.int(10, 250)}`,
|
|
|
|
|
incremental: random.int(0, 4294967295),
|
|
|
|
|
imei: random.imei(),
|
|
|
|
|
};
|
|
|
|
|
|
2023-06-30 11:26:45 +00:00
|
|
|
|
await fsP.writeFile(dataPath(`${params.uin}/device.json`), JSON.stringify(device, null, 0), 'utf-8');
|
2022-02-23 06:09:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-23 06:34:25 +00:00
|
|
|
|
const client = new this(params.uin, params.id, {
|
2022-02-23 06:09:54 +00:00
|
|
|
|
platform: params.platform,
|
2023-06-30 11:26:45 +00:00
|
|
|
|
data_dir: dataPath(params.uin.toString()),
|
2022-03-01 13:11:14 +00:00
|
|
|
|
log_level: LOG_LEVEL,
|
2024-01-15 09:58:48 +00:00
|
|
|
|
ffmpeg_path: env.FFMPEG_PATH,
|
|
|
|
|
ffprobe_path: env.FFPROBE_PATH,
|
|
|
|
|
sign_api_addr: params.signApi || env.SIGN_API,
|
|
|
|
|
ver: params.signVer || env.SIGN_VER,
|
2024-01-10 10:26:31 +00:00
|
|
|
|
}, params.signDockerId);
|
2023-06-30 11:26:45 +00:00
|
|
|
|
client.on('system.login.device', loginDeviceHandler);
|
|
|
|
|
client.on('system.login.slider', loginSliderHandler);
|
|
|
|
|
client.on('system.login.error', loginErrorHandler);
|
|
|
|
|
client.on('system.online', successLoginHandler);
|
2022-12-23 06:34:25 +00:00
|
|
|
|
|
|
|
|
|
this.existedBots[params.id] = client;
|
2023-06-30 11:26:45 +00:00
|
|
|
|
client.login(params.uin, params.password);
|
2022-02-23 06:09:54 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-01 13:11:14 +00:00
|
|
|
|
private onMessage = async (event: PrivateMessageEvent | GroupMessageEvent | DiscussMessageEvent) => {
|
|
|
|
|
if (event.message_type === 'discuss') return;
|
|
|
|
|
for (const handler of this.onMessageHandlers) {
|
|
|
|
|
const res = await handler(event);
|
|
|
|
|
if (res) return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public addNewMessageEventHandler(handler: MessageHandler) {
|
|
|
|
|
this.onMessageHandlers.push(handler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public removeNewMessageEventHandler(handler: MessageHandler) {
|
|
|
|
|
this.onMessageHandlers.includes(handler) &&
|
|
|
|
|
this.onMessageHandlers.splice(this.onMessageHandlers.indexOf(handler), 1);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 15:35:27 +00:00
|
|
|
|
public getChat(roomId: number): Group | Friend {
|
2022-02-23 06:09:54 +00:00
|
|
|
|
if (roomId > 0) {
|
|
|
|
|
return this.pickFriend(roomId);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return this.pickGroup(-roomId);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-30 11:26:45 +00:00
|
|
|
|
|
|
|
|
|
public async makeForwardMsgSelf(msglist: Forwardable[] | Forwardable, dm?: boolean): Promise<{
|
|
|
|
|
resid: string,
|
|
|
|
|
tSum: number
|
|
|
|
|
}> {
|
|
|
|
|
if (!Array.isArray(msglist))
|
|
|
|
|
msglist = [msglist];
|
|
|
|
|
const nodes = [];
|
|
|
|
|
const makers: Converter[] = [];
|
|
|
|
|
let imgs: Image[] = [];
|
|
|
|
|
let cnt = 0;
|
|
|
|
|
for (const fake of msglist) {
|
|
|
|
|
const maker = new Converter(fake.message, { dm, cachedir: this.config.data_dir });
|
|
|
|
|
makers.push(maker);
|
|
|
|
|
const seq = randomBytes(2).readInt16BE();
|
|
|
|
|
const rand = randomBytes(4).readInt32BE();
|
|
|
|
|
let nickname = String(fake.nickname || fake.user_id);
|
|
|
|
|
if (!nickname && fake instanceof PrivateMessage)
|
|
|
|
|
nickname = this.fl.get(fake.user_id)?.nickname || this.sl.get(fake.user_id)?.nickname || nickname;
|
|
|
|
|
if (cnt < 4) {
|
|
|
|
|
cnt++;
|
|
|
|
|
}
|
|
|
|
|
nodes.push({
|
|
|
|
|
1: {
|
|
|
|
|
1: fake.user_id,
|
|
|
|
|
2: this.uin,
|
|
|
|
|
3: dm ? 166 : 82,
|
|
|
|
|
4: dm ? 11 : null,
|
|
|
|
|
5: seq,
|
|
|
|
|
6: fake.time || timestamp(),
|
|
|
|
|
7: rand2uuid(rand),
|
|
|
|
|
9: dm ? null : {
|
|
|
|
|
1: this.uin,
|
|
|
|
|
4: nickname,
|
|
|
|
|
},
|
|
|
|
|
14: dm ? nickname : null,
|
|
|
|
|
20: {
|
|
|
|
|
1: 0,
|
|
|
|
|
2: rand,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
3: {
|
|
|
|
|
1: maker.rich,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for (const maker of makers)
|
|
|
|
|
imgs = [...imgs, ...maker.imgs];
|
|
|
|
|
const contact = (dm ? this.pickFriend : this.pickGroup)(this.uin);
|
|
|
|
|
if (imgs.length)
|
|
|
|
|
await contact.uploadImages(imgs);
|
|
|
|
|
const compressed = await gzip(pb.encode({
|
|
|
|
|
1: nodes,
|
|
|
|
|
2: {
|
|
|
|
|
1: 'MultiMsg',
|
|
|
|
|
2: {
|
|
|
|
|
1: nodes,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}));
|
|
|
|
|
const _uploadMultiMsg = Reflect.get(contact, '_uploadMultiMsg') as Function;
|
|
|
|
|
const resid = await _uploadMultiMsg.apply(contact, compressed);
|
|
|
|
|
return {
|
|
|
|
|
tSum: nodes.length,
|
|
|
|
|
resid,
|
|
|
|
|
};
|
|
|
|
|
}
|
2022-02-23 06:09:54 +00:00
|
|
|
|
}
|