mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f8bf5afd3d | ||
![]() |
66c823e3bd | ||
![]() |
8f80da8c5b | ||
![]() |
1ceee49d1a | ||
![]() |
c600c38a92 | ||
![]() |
3eda104a78 | ||
![]() |
b8aa3131b0 | ||
![]() |
320aa964f9 | ||
![]() |
0fd75b338f | ||
![]() |
9faa56ec32 |
14
README.md
14
README.md
@@ -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)
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"name": "LLOneBot",
|
||||
"slug": "LLOneBot",
|
||||
"description": "LiteLoaderQQNT的OneBotApi",
|
||||
"version": "3.9.0",
|
||||
"version": "3.10.1",
|
||||
"thumbnail": "./icon.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -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 = {
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
]
|
||||
})
|
||||
}
|
||||
|
@@ -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, // 未知
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user