Compare commits

..

10 Commits

Author SHA1 Message Date
手瓜一十雪
f8bf5afd3d fix:syntax error 2024-02-29 00:50:57 +08:00
手瓜一十雪
66c823e3bd fix:timestamp 2024-02-29 00:48:12 +08:00
linyuchen
8f80da8c5b Merge pull request #94 from MliKiowa/patch-1 2024-02-28 16:04:30 +08:00
手瓜一十雪
1ceee49d1a docs: update readme 2024-02-28 16:01:34 +08:00
linyuchen
c600c38a92 chore: ver 3.10.0 2024-02-27 23:23:53 +08:00
linyuchen
3eda104a78 docs: comment 2024-02-27 20:58:51 +08:00
linyuchen
b8aa3131b0 fix: 群通知重复上报 2024-02-27 20:31:58 +08:00
linyuchen
320aa964f9 doc: update 2024-02-27 20:08:26 +08:00
linyuchen
0fd75b338f feat: 加群邀请上报
fix: 加群和加好友post_type字段改为request
2024-02-27 20:06:20 +08:00
linyuchen
9faa56ec32 refactor: 统一时间戳为毫秒,优化发送消息逻辑代码
fix: 发送文件的文件名保持原样
2024-02-27 19:47:17 +08:00
13 changed files with 138 additions and 102 deletions

View File

@@ -9,6 +9,18 @@ TG群<https://t.me/+nLZEnpne-pQ1OWFl>
*V3之后不再需要LLAPI*
## 安装方法
### Linux 容器化快速安装
执行以下任意脚本按照提示设置NoVnc密码即可运行脚本问题与异常参考 [llonebot-docker](https://github.com/MliKiowa/llonebot-docker) 项目。
```bash
curl https://cdn.jsdelivr.net/gh/MliKiowa/llonebot-docker/fastboot.sh -o fastboot.sh & chmod +x fastboot.sh & sudo sh fastboot.sh
```
```bash
wget -O fastboot.sh https://cdn.jsdelivr.net/gh/MliKiowa/llonebot-docker/fastboot.sh & chmod +x fastboot.sh & sudo sh fastboot.sh
```
### 通用手动安装方法
1.安装[LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
@@ -169,4 +181,4 @@ TG群<https://t.me/+nLZEnpne-pQ1OWFl>
* [LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html)
* [LLAPI](https://github.com/Night-stars-1/LiteLoaderQQNT-Plugin-LLAPI)
* chronocat
* [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot)
* [koishi-plugin-adapter-onebot](https://github.com/koishijs/koishi-plugin-adapter-onebot)

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot",
"slug": "LLOneBot",
"description": "LiteLoaderQQNT的OneBotApi",
"version": "3.9.0",
"version": "3.10.1",
"thumbnail": "./icon.png",
"authors": [
{

View File

@@ -5,7 +5,7 @@ import {LLOneBotError} from "./types";
export let groups: Group[] = []
export let friends: Friend[] = []
export let msgHistory: Record<string, RawMessage> = {} // msgId: RawMessage
export const version = "3.9.0"
export const version = "3.10.1"
export let groupNotifies: Map<string, GroupNotify> = new Map<string, GroupNotify>();
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>();
export let llonebotError: LLOneBotError = {

View File

@@ -232,7 +232,7 @@ function onLoad() {
"unreadCount": number
}>(ReceiveCmd.UNREAD_GROUP_NOTIFY, async (payload) => {
if (payload.unreadCount) {
log("开始获取群通知详情")
// log("开始获取群通知详情")
let notify: GroupNotifies;
try {
notify = await NTQQApi.getGroupNotifies();
@@ -242,14 +242,23 @@ function onLoad() {
}
const notifies = notify.notifies.slice(0, payload.unreadCount)
log("获取群通知详情完成", notifies, payload);
// log("获取群通知详情完成", notifies, payload);
try {
for (const notify of notifies) {
notify.time = Date.now();
const notifyTime = parseInt(notify.seq) / 1000
log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
// log(`加群通知时间${notifyTime}`, `LLOneBot启动时间${startTime}`);
if (notifyTime < startTime) {
continue;
}
let existNotify = groupNotifies[notify.seq];
if (existNotify){
if (Date.now() - existNotify.time < 3000){
continue
}
}
log("收到群通知", notify);
groupNotifies[notify.seq] = notify;
const member1 = await getGroupMember(notify.group.groupCode, null, notify.user1.uid);
let member2: GroupMember;
if (notify.user2.uid) {
@@ -274,7 +283,6 @@ function onLoad() {
// postEvent(groupDecreaseEvent, true);
} else if ([GroupNotifyTypes.JOIN_REQUEST].includes(notify.type)) {
log("有加群请求");
groupNotifies[notify.seq] = notify;
let groupRequestEvent = new OB11GroupRequestEvent();
groupRequestEvent.group_id = parseInt(notify.group.groupCode);
let requestQQ = ""
@@ -289,6 +297,15 @@ function onLoad() {
groupRequestEvent.flag = notify.seq;
postOB11Event(groupRequestEvent);
}
else if(notify.type == GroupNotifyTypes.INVITE_ME){
let groupInviteEvent = new OB11GroupRequestEvent();
groupInviteEvent.group_id = parseInt(notify.group.groupCode);
let user_id = (await NTQQApi.getUserDetailInfo(notify.user2.uid))?.uin
groupInviteEvent.user_id = parseInt(user_id);
groupInviteEvent.sub_type = "invite";
groupInviteEvent.flag = notify.seq;
postOB11Event(groupInviteEvent);
}
}
} catch (e) {
log("解析群通知失败", e.stack);

View File

@@ -9,7 +9,7 @@ import {
SendTextElement
} from "./types";
import {NTQQApi} from "./ntcall";
import {encodeSilk, log} from "../common/utils";
import {encodeSilk} from "../common/utils";
import fs from "fs";
@@ -56,7 +56,7 @@ export class SendMsgElementConstructor {
}
static async pic(picPath: string): Promise<SendPicElement> {
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath);
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath, ElementType.PIC);
const imageSize = await NTQQApi.getImageSize(picPath);
const picElement = {
md5HexStr: md5,
@@ -81,8 +81,14 @@ export class SendMsgElementConstructor {
};
}
static async file(filePath: string, isVideo:boolean = false): Promise<SendFileElement> {
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(filePath);
static async file(filePath: string, isVideo: boolean = false): Promise<SendFileElement> {
let picHeight = 0;
let picWidth = 0;
if (isVideo) {
picHeight = 1024;
picWidth = 768;
}
const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(filePath, ElementType.FILE);
let element: SendFileElement = {
elementType: ElementType.FILE,
elementId: "",
@@ -90,18 +96,18 @@ export class SendMsgElementConstructor {
fileName,
"filePath": path,
"fileSize": (fileSize).toString(),
picHeight,
picWidth
}
}
if (isVideo){
element.fileElement.picHeight = 1024;
element.fileElement.picWidth = 768;
}
return element;
}
static video(filePath: string): Promise<SendFileElement> {
return SendMsgElementConstructor.file(filePath, true);
}
static async ptt(pttPath: string): Promise<SendPttElement> {
const {converted, path: silkPath, duration} = await encodeSilk(pttPath);
// log("生成语音", silkPath, duration);

View File

@@ -1,13 +1,14 @@
import {ipcMain} from "electron";
import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook";
import {log} from "../common/utils";
import {log, sleep} from "../common/utils";
import {
ChatType,
ElementType,
Friend,
FriendRequest,
Group,
GroupMember, GroupMemberRole,
GroupMember,
GroupMemberRole,
GroupNotifies,
GroupNotify,
GroupRequestOperateTypes,
@@ -19,6 +20,7 @@ import {
import * as fs from "fs";
import {addHistoryMsg, friendRequests, groupNotifies, msgHistory, selfInfo} from "../common/data";
import {v4 as uuidv4} from "uuid"
import path from "path";
interface IPCReceiveEvent {
eventName: string
@@ -346,7 +348,10 @@ export class NTQQApi {
} else {
ext = ""
}
const fileName = `${md5}${ext}`;
let fileName = `${path.basename(filePath)}`;
if (fileName.indexOf(".") === -1) {
fileName += ext;
}
const mediaPath = await callNTQQApi<string>({
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
args: [{
@@ -414,71 +419,57 @@ export class NTQQApi {
})
}
static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false, timeout = 10000) {
const sendTimeout = timeout
static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false, timeout = 10000) {
const peerUid = peer.peerUid;
return new Promise<RawMessage>((resolve, reject) => {
const peerUid = peer.peerUid;
let usingTime = 0;
let success = false;
let isTimeout = false;
const checkSuccess = () => {
if (!success) {
sendMessagePool[peerUid] = null;
isTimeout = true;
reject("发送超时")
}
// 等待上一个相同的peer发送完
let checkLastSendUsingTime = 0;
const waitLastSend = async () => {
if (checkLastSendUsingTime > timeout) {
throw ("发送超时")
}
setTimeout(checkSuccess, sendTimeout);
const checkLastSend = () => {
let lastSending = sendMessagePool[peerUid]
if (sendTimeout < usingTime) {
sendMessagePool[peerUid] = null;
isTimeout = true;
reject("发送超时")
}
if (!!lastSending) {
// log("有正在发送的消息,等待中...")
usingTime += 500;
setTimeout(checkLastSend, 500);
} else {
log("可以进行发送消息,设置发送成功回调", sendMessagePool)
sendMessagePool[peerUid] = (rawMessage: RawMessage) => {
sendMessagePool[peerUid] = null;
const checkSendComplete = () => {
if (isTimeout) {
return reject("发送超时")
}
if (msgHistory[rawMessage.msgId]?.sendStatus == 2) {
log(`${peerUid}发送消息成功`)
success = true;
resolve(rawMessage);
} else {
setTimeout(checkSendComplete, 500)
}
}
if (waitComplete) {
checkSendComplete();
} else {
success = true;
log(`${peerUid}发送消息成功`)
resolve(rawMessage);
}
}
}
let lastSending = sendMessagePool[peer.peerUid]
if (lastSending) {
// log("有正在发送的消息,等待中...")
await sleep(500);
checkLastSendUsingTime += 500;
return await waitLastSend();
} else {
return;
}
checkLastSend()
callNTQQApi({
methodName: NTQQApiMethod.SEND_MSG,
args: [{
msgId: "0",
peer, msgElements,
msgAttributeInfos: new Map(),
}, null]
}).then()
})
}
await waitLastSend();
let sentMessage: RawMessage = null;
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
delete sendMessagePool[peerUid];
sentMessage = rawMessage;
}
let checkSendCompleteUsingTime = 0;
const checkSendComplete = async (): Promise<RawMessage> => {
if (sentMessage && msgHistory[sentMessage.msgId]?.sendStatus == 2) {
// log(`给${peerUid}发送消息成功`)
return sentMessage;
} else {
checkSendCompleteUsingTime += 500;
if (checkSendCompleteUsingTime > timeout) {
throw ("发送超时")
}
await sleep(500);
return await checkSendComplete()
}
}
callNTQQApi({
methodName: NTQQApiMethod.SEND_MSG,
args: [{
msgId: "0",
peer, msgElements,
msgAttributeInfos: new Map(),
}, null]
}).then()
return checkSendComplete();
}
static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) {
@@ -557,6 +548,7 @@ export class NTQQApi {
if (!notify) {
throw `${seq}对应的加群通知不存在`
}
delete groupNotifies[seq];
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
args: [
@@ -638,7 +630,8 @@ export class NTQQApi {
}
)
}
static banGroup(groupQQ: string, shutUp: boolean){
static banGroup(groupQQ: string, shutUp: boolean) {
return callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.MUTE_GROUP,
args: [
@@ -676,14 +669,14 @@ export class NTQQApi {
})
}
static setGroupName(groupQQ: string, groupName: string){
static setGroupName(groupQQ: string, groupName: string) {
return callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.SET_GROUP_NAME,
args:[
args: [
{
groupCode: groupQQ,
groupName
},null
}, null
]
})
}

View File

@@ -264,7 +264,7 @@ export interface VideoElement {
export interface RawMessage {
msgId: string;
msgShortId?: number; // 自己维护的消息id
msgTime: string;
msgTime: string; // 时间戳,秒
msgSeq: string;
senderUid: string;
senderUin?: string; // 发送者QQ号
@@ -300,6 +300,7 @@ export interface RawMessage {
}
export enum GroupNotifyTypes {
INVITE_ME = 1,
INVITED_JOIN = 4, // 有人接受了邀请入群
JOIN_REQUEST = 7,
ADMIN_SET = 8,
@@ -315,7 +316,7 @@ export interface GroupNotifies {
}
export interface GroupNotify {
time: number; // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
seq: string, // 转成数字再除以1000应该就是时间戳
type: GroupNotifyTypes,
status: 0, // 未知

View File

@@ -236,7 +236,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
case OB11MessageDataType.voice: {
const file = sendMsg.data?.file
if (file) {
const {path, isLocal} = (await uri2local(uuidv4(), file))
const {path, isLocal} = (await uri2local(file))
if (path) {
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path)

View File

@@ -7,23 +7,23 @@ import {
OB11MessageDataType,
OB11User
} from "./types";
import {AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User} from '../ntqqapi/types';
import {getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data';
import {file2base64, getConfigUtil, log} from "../common/utils";
import {NTQQApi} from "../ntqqapi/ntcall";
import {EventType} from "./event/OB11BaseEvent";
import {encodeCQCode} from "./cqcode";
import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
import { getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo } from '../common/data';
import { file2base64, getConfigUtil, log } from "../common/utils";
import { NTQQApi } from "../ntqqapi/ntcall";
import { EventType } from "./event/OB11BaseEvent";
import { encodeCQCode } from "./cqcode";
export class OB11Constructor {
static async message(msg: RawMessage): Promise<OB11Message> {
const {enableLocalFile2Url, ob11: {messagePostFormat}} = getConfigUtil().getConfig()
const { enableLocalFile2Url, ob11: { messagePostFormat } } = getConfigUtil().getConfig()
const message_type = msg.chatType == ChatType.group ? "group" : "private";
const resMsg: OB11Message = {
self_id: parseInt(selfInfo.uin),
user_id: parseInt(msg.senderUin),
time: parseInt(msg.msgTime) || 0,
time: parseInt(msg.msgTime) || Date.now(),
message_id: msg.msgShortId,
real_id: msg.msgId,
message_type: msg.chatType == ChatType.group ? "group" : "private",
@@ -148,14 +148,14 @@ export class OB11Constructor {
if (message_data.data.url && !message_data.data.url.startsWith(IMAGE_HTTP_HOST + "/download")) {
message_data.data.file = message_data.data.url
} else {
let {err, data} = await file2base64(filePath);
let { err, data } = await file2base64(filePath);
if (err) {
log("文件转base64失败", filePath, err)
} else {
message_data.data.file = "base64://" + data
}
}
}else{
} else {
message_data.data.file = "file://" + filePath
}
}

View File

@@ -1,4 +1,4 @@
import {selfInfo} from "../../common/data";
import { selfInfo } from "../../common/data";
export enum EventType {
META = "meta_event",
@@ -10,7 +10,7 @@ export enum EventType {
export abstract class OB11BaseEvent {
time = new Date().getTime();
time = Math.floor(Date.now() / 1000);
self_id = parseInt(selfInfo.uin);
post_type: EventType;
}

View File

@@ -3,7 +3,7 @@ import {EventType} from "../OB11BaseEvent";
export class OB11FriendRequestEvent extends OB11BaseNoticeEvent {
// post_type = EventType.REQUEST
post_type = EventType.REQUEST
user_id: number;
request_type: "friend" = "friend";
comment: string;

View File

@@ -1,7 +1,9 @@
import {OB11GroupNoticeEvent} from "../notice/OB11GroupNoticeEvent";
import {EventType} from "../OB11BaseEvent";
export class OB11GroupRequestEvent extends OB11GroupNoticeEvent{
post_type = EventType.REQUEST;
request_type: "group" = "group";
sub_type: "add" | "invite" = "add";
comment: string;

View File

@@ -1,10 +1,13 @@
import {CONFIG_DIR, isGIF} from "../common/utils";
import {v4 as uuidv4} from "uuid";
import * as path from 'path';
import {OB11MessageData} from "./types";
const fs = require("fs").promises;
export async function uri2local(fileName: string, uri: string){
export async function uri2local(uri: string, fileName: string=null){
if (!fileName){
fileName = uuidv4();
}
let filePath = path.join(CONFIG_DIR, fileName)
let url = new URL(uri);
let res = {
@@ -33,6 +36,8 @@ export async function uri2local(fileName: string, uri: string){
let blob = await fetchRes.blob();
let buffer = await blob.arrayBuffer();
try {
fileName = path.basename(url.pathname) || fileName
filePath = path.join(CONFIG_DIR, fileName)
await fs.writeFile(filePath, Buffer.from(buffer));
} catch (e: any) {
res.errMsg = `${url}下载失败,` + e.toString()