mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f8890b309b | ||
![]() |
b5e578733f | ||
![]() |
51602b987e | ||
![]() |
b501af6e0e | ||
![]() |
81821e74d8 | ||
![]() |
959eab441e | ||
![]() |
441c0c6946 | ||
![]() |
240cdade07 | ||
![]() |
0132d97bd9 | ||
![]() |
b34c7f045c | ||
![]() |
ab91313e69 | ||
![]() |
1f8966aaf4 | ||
![]() |
ec073da3f6 | ||
![]() |
80131e0472 |
@@ -32,7 +32,7 @@ let config = {
|
||||
targets: [
|
||||
...external.map(genCpModule),
|
||||
{src: './manifest.json', dest: 'dist'}, {src: './icon.jpg', dest: 'dist'},
|
||||
{src: './src/ntqqapi/external/ccpoke/poke-win32-x64.node', dest: 'dist/main/ccpoke/'},
|
||||
{src: './src/ntqqapi/external/crychic/crychic-win32-x64.node', dest: 'dist/main/'},
|
||||
]
|
||||
})]
|
||||
},
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot v3.20.7",
|
||||
"name": "LLOneBot v3.22.1",
|
||||
"slug": "LLOneBot",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
||||
"version": "3.20.7",
|
||||
"version": "3.22.1",
|
||||
"icon": "./icon.jpg",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -28,7 +28,7 @@ export const llonebotError: LLOneBotError = {
|
||||
|
||||
|
||||
export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
|
||||
let filterKey = isNumeric(uinOrUid) ? "uin" : "uid"
|
||||
let filterKey = isNumeric(uinOrUid.toString()) ? "uin" : "uid"
|
||||
let filterValue = uinOrUid
|
||||
let friend = friends.find(friend => friend[filterKey] === filterValue.toString())
|
||||
// if (!friend) {
|
||||
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
||||
import {DATA_DIR, TEMP_DIR} from "./index";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {getConfigUtil} from "../config";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import {spawn} from "node:child_process"
|
||||
|
||||
export async function encodeSilk(filePath: string) {
|
||||
function getFileHeader(filePath: string) {
|
||||
@@ -64,50 +64,44 @@ export async function encodeSilk(filePath: string) {
|
||||
if (getFileHeader(filePath) !== "02232153494c4b") {
|
||||
log(`语音文件${filePath}需要转换成silk`)
|
||||
const _isWav = await isWavFile(filePath);
|
||||
const wavPath = pttPath + ".wav"
|
||||
const convert = async () => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const ffmpegPath = getConfigUtil().getConfig().ffmpeg;
|
||||
if (ffmpegPath) {
|
||||
ffmpeg.setFfmpegPath(ffmpegPath);
|
||||
const pcmPath = pttPath + ".pcm"
|
||||
let sampleRate = 0
|
||||
const convert = () => {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || "ffmpeg"
|
||||
const cp = spawn(ffmpegPath, ["-y", "-i", filePath, "-ar", "24000", "-ac", "1", "-f", "s16le", pcmPath])
|
||||
cp.on("error", err => {
|
||||
log(`FFmpeg处理转换出错: `, err.message)
|
||||
return reject(err)
|
||||
})
|
||||
cp.on("exit", (code, signal) => {
|
||||
const EXIT_CODES = [0, 255]
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000
|
||||
const data = fs.readFileSync(pcmPath)
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
})
|
||||
return resolve(data)
|
||||
}
|
||||
ffmpeg(filePath).toFormat("wav")
|
||||
.audioChannels(1)
|
||||
.audioFrequency(24000)
|
||||
.on('end', function () {
|
||||
log('wav转换完成');
|
||||
log(`FFmpeg exit: code=${code ?? "unknown"} sig=${signal ?? "unknown"}`)
|
||||
reject(Error(`FFmpeg处理转换失败`))
|
||||
})
|
||||
.on('error', function (err) {
|
||||
log(`wav转换出错: `, err.message,);
|
||||
reject(err);
|
||||
})
|
||||
.save(wavPath)
|
||||
.on("end", () => {
|
||||
filePath = wavPath
|
||||
resolve(wavPath);
|
||||
});
|
||||
})
|
||||
}
|
||||
let wav: Buffer
|
||||
let input: Buffer
|
||||
if (!_isWav) {
|
||||
log(`语音文件${filePath}正在转换成wav`)
|
||||
await convert()
|
||||
input = await convert()
|
||||
} else {
|
||||
wav = fs.readFileSync(filePath)
|
||||
input = fs.readFileSync(filePath)
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
||||
const {fmt} = getWavFileInfo(wav)
|
||||
const {fmt} = getWavFileInfo(input)
|
||||
// log(`wav文件信息`, fmt)
|
||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
||||
wav = undefined
|
||||
await convert()
|
||||
input = await convert()
|
||||
}
|
||||
}
|
||||
wav ||= fs.readFileSync(filePath);
|
||||
const silk = await encode(wav, 0);
|
||||
const silk = await encode(input, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
fs.unlink(wavPath, (err) => {
|
||||
});
|
||||
// const gDuration = await guessDuration(pttPath)
|
||||
log(`语音文件${filePath}转换成功!`, pttPath, `时长:`, silk.duration)
|
||||
return {
|
||||
converted: true,
|
||||
@@ -127,7 +121,7 @@ export async function encodeSilk(filePath: string) {
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration: duration,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
|
@@ -47,7 +47,7 @@ import {dbUtil} from "../common/db";
|
||||
import {setConfig} from "./setConfig";
|
||||
import {NTQQUserApi} from "../ntqqapi/api/user";
|
||||
import {NTQQGroupApi} from "../ntqqapi/api/group";
|
||||
import {registerPokeHandler} from "../ntqqapi/external/ccpoke";
|
||||
import {crychic} from "../ntqqapi/external/crychic";
|
||||
import {OB11FriendPokeEvent, OB11GroupPokeEvent} from "../onebot11/event/notice/OB11PokeEvent";
|
||||
import {checkNewVersion, upgradeLLOneBot} from "../common/utils/upgrade";
|
||||
import {log} from "../common/utils/log";
|
||||
@@ -183,7 +183,8 @@ function onLoad() {
|
||||
|
||||
async function startReceiveHook() {
|
||||
if (getConfigUtil().getConfig().enablePoke) {
|
||||
registerPokeHandler((id, isGroup) => {
|
||||
crychic.loadNode()
|
||||
crychic.registerPokeHandler((id, isGroup) => {
|
||||
log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`)
|
||||
let pokeEvent: OB11FriendPokeEvent | OB11GroupPokeEvent;
|
||||
if (isGroup) {
|
||||
|
@@ -186,6 +186,17 @@ export class NTQQGroupApi{
|
||||
})
|
||||
}
|
||||
|
||||
static async getGroupAtAllRemainCount(groupCode: string){
|
||||
return await callNTQQApi<GeneralCallResult & {"atInfo":{"canAtAll": boolean,"RemainAtAllCountForUin": number,"RemainAtAllCountForGroup": number,"atTimesMsg": string,"canNotAtAllMsg":""}}>({
|
||||
methodName: NTQQApiMethod.GROUP_AT_ALL_REMAIN_COUNT,
|
||||
args: [
|
||||
{
|
||||
groupCode
|
||||
}, null
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 头衔不可用
|
||||
static async setGroupTitle(groupQQ: string, uid: string, title: string) {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AtType,
|
||||
ElementType,
|
||||
ElementType, FaceIndex,
|
||||
FaceType,
|
||||
PicType,
|
||||
SendArkElement,
|
||||
SendFaceElement,
|
||||
@@ -18,9 +19,15 @@ import {calculateFileMD5, isGIF} from "../common/utils/file";
|
||||
import {log} from "../common/utils/log";
|
||||
import {defaultVideoThumb, getVideoInfo} from "../common/utils/video";
|
||||
import {encodeSilk} from "../common/utils/audio";
|
||||
import {isNull} from "../common/utils";
|
||||
|
||||
|
||||
export class SendMsgElementConstructor {
|
||||
|
||||
static poke(groupCode: string, uin: string) {
|
||||
return null
|
||||
}
|
||||
|
||||
static text(content: string): SendTextElement {
|
||||
return {
|
||||
elementType: ElementType.TEXT,
|
||||
@@ -115,8 +122,8 @@ export class SendMsgElementConstructor {
|
||||
throw "文件异常,大小为0";
|
||||
}
|
||||
const pathLib = require("path");
|
||||
let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
||||
thumb = pathLib.dirname(thumb)
|
||||
let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`)
|
||||
thumbDir = pathLib.dirname(thumbDir)
|
||||
// log("thumb 目录", thumb)
|
||||
let videoInfo = {
|
||||
width: 1920, height: 1080,
|
||||
@@ -133,33 +140,46 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
const createThumb = new Promise<string>((resolve, reject) => {
|
||||
const thumbFileName = `${md5}_0.png`
|
||||
const thumbPath = pathLib.join(thumb, thumbFileName)
|
||||
const thumbPath = pathLib.join(thumbDir, thumbFileName)
|
||||
log("开始生成视频缩略图", filePath);
|
||||
let completed = false;
|
||||
|
||||
function useDefaultThumb() {
|
||||
if (completed) return;
|
||||
log("获取视频封面失败,使用默认封面");
|
||||
fs.writeFile(thumbPath, defaultVideoThumb).then(() => {
|
||||
resolve(thumbPath);
|
||||
}).catch(reject)
|
||||
}
|
||||
|
||||
setTimeout(useDefaultThumb, 5000);
|
||||
ffmpeg(filePath)
|
||||
.on("end", () => {
|
||||
})
|
||||
.on("error", (err) => {
|
||||
log("获取视频封面失败,使用默认封面", err)
|
||||
if (diyThumbPath) {
|
||||
fs.copyFile(diyThumbPath, thumbPath).then(() => {
|
||||
completed = true;
|
||||
resolve(thumbPath);
|
||||
}).catch(reject)
|
||||
} else {
|
||||
fs.writeFile(thumbPath, defaultVideoThumb).then(() => {
|
||||
resolve(thumbPath);
|
||||
}).catch(reject)
|
||||
useDefaultThumb()
|
||||
}
|
||||
})
|
||||
.screenshots({
|
||||
timestamps: [0],
|
||||
filename: thumbFileName,
|
||||
folder: thumb,
|
||||
folder: thumbDir,
|
||||
size: videoInfo.width + "x" + videoInfo.height
|
||||
}).on("end", () => {
|
||||
log("生成视频缩略图", thumbPath)
|
||||
completed = true;
|
||||
resolve(thumbPath);
|
||||
});
|
||||
})
|
||||
})
|
||||
let thumbPath = new Map()
|
||||
const _thumbPath = await createThumb;
|
||||
log("生成缩略图", _thumbPath)
|
||||
const thumbSize = (await fs.stat(_thumbPath)).size;
|
||||
// log("生成缩略图", _thumbPath)
|
||||
thumbPath.set(0, _thumbPath)
|
||||
@@ -190,6 +210,7 @@ export class SendMsgElementConstructor {
|
||||
// sourceVideoCodecFormat: 2
|
||||
}
|
||||
}
|
||||
log("videoElement", element)
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -228,12 +249,58 @@ export class SendMsgElementConstructor {
|
||||
}
|
||||
|
||||
static face(faceId: number): SendFaceElement {
|
||||
faceId = parseInt(faceId.toString());
|
||||
return {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: "",
|
||||
faceElement: {
|
||||
faceIndex: faceId,
|
||||
faceType: 1
|
||||
faceType: faceId < 222 ? FaceType.normal : FaceType.normal2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static dice(resultId: number | null): SendFaceElement {
|
||||
// 实际测试并不能控制结果
|
||||
|
||||
// 随机1到6
|
||||
if (isNull(resultId)) resultId = Math.floor(Math.random() * 6) + 1;
|
||||
return {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: "",
|
||||
faceElement: {
|
||||
faceIndex: FaceIndex.dice,
|
||||
faceType: FaceType.dice,
|
||||
"faceText": "[骰子]",
|
||||
"packId": "1",
|
||||
"stickerId": "33",
|
||||
"sourceType": 1,
|
||||
"stickerType": 2,
|
||||
resultId: resultId.toString(),
|
||||
"surpriseId": "",
|
||||
// "randomType": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 猜拳(石头剪刀布)表情
|
||||
static rps(resultId: number | null): SendFaceElement {
|
||||
// 实际测试并不能控制结果
|
||||
if (isNull(resultId)) resultId = Math.floor(Math.random() * 3) + 1;
|
||||
return {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: "",
|
||||
faceElement: {
|
||||
"faceIndex": FaceIndex.RPS,
|
||||
"faceText": "[包剪锤]",
|
||||
"faceType": 3,
|
||||
"packId": "1",
|
||||
"stickerId": "34",
|
||||
"sourceType": 1,
|
||||
"stickerType": 2,
|
||||
"resultId": resultId.toString(),
|
||||
"surpriseId": "",
|
||||
// "randomType": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
src/ntqqapi/external/ccpoke/index.ts
vendored
28
src/ntqqapi/external/ccpoke/index.ts
vendored
@@ -1,28 +0,0 @@
|
||||
import {log} from "../../../common/utils/log";
|
||||
|
||||
let pokeEngine: any = null
|
||||
|
||||
type PokeHandler = (id: string, isGroup: boolean)=>void
|
||||
|
||||
let pokeRecords: Record<string, number> = {}
|
||||
export function registerPokeHandler(handler: PokeHandler){
|
||||
if(!pokeEngine){
|
||||
try {
|
||||
pokeEngine = require("./ccpoke/poke-win32-x64.node")
|
||||
pokeEngine.performHooks();
|
||||
}catch (e) {
|
||||
log("戳一戳引擎加载失败", e)
|
||||
return
|
||||
}
|
||||
}
|
||||
pokeEngine.setHandlerForPokeHook((id: string, isGroup: boolean)=>{
|
||||
let existTime = pokeRecords[id]
|
||||
if (existTime){
|
||||
if (Date.now() - existTime < 1500){
|
||||
return
|
||||
}
|
||||
}
|
||||
pokeRecords[id] = Date.now()
|
||||
handler(id, isGroup);
|
||||
})
|
||||
}
|
BIN
src/ntqqapi/external/ccpoke/poke-win32-x64.node
vendored
BIN
src/ntqqapi/external/ccpoke/poke-win32-x64.node
vendored
Binary file not shown.
BIN
src/ntqqapi/external/crychic/crychic-win32-x64.node
vendored
Normal file
BIN
src/ntqqapi/external/crychic/crychic-win32-x64.node
vendored
Normal file
Binary file not shown.
53
src/ntqqapi/external/crychic/index.ts
vendored
Normal file
53
src/ntqqapi/external/crychic/index.ts
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import {log} from "../../../common/utils";
|
||||
import {NTQQApi} from "../../ntcall";
|
||||
|
||||
type PokeHandler = (id: string, isGroup: boolean) => void
|
||||
type CrychicHandler = (event: string, id: string, isGroup: boolean) => void
|
||||
|
||||
let pokeRecords: Record<string, number> = {}
|
||||
|
||||
class Crychic{
|
||||
private crychic: any = undefined
|
||||
|
||||
loadNode(){
|
||||
if (!this.crychic){
|
||||
try {
|
||||
this.crychic = require("./crychic-win32-x64.node")
|
||||
this.crychic.init()
|
||||
}catch (e) {
|
||||
log("crychic加载失败", e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
registerPokeHandler(fn: PokeHandler){
|
||||
this.registerHandler((event, id, isGroup)=>{
|
||||
if (event === "poke"){
|
||||
let existTime = pokeRecords[id]
|
||||
if (existTime) {
|
||||
if (Date.now() - existTime < 1500) {
|
||||
return
|
||||
}
|
||||
}
|
||||
pokeRecords[id] = Date.now()
|
||||
fn(id, isGroup);
|
||||
}
|
||||
})
|
||||
}
|
||||
registerHandler(fn: CrychicHandler){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.setCryHandler(fn)
|
||||
}
|
||||
sendFriendPoke(friendUid: string){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.sendFriendPoke(parseInt(friendUid))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
sendGroupPoke(groupCode: string, memberUin: string){
|
||||
if (!this.crychic) return;
|
||||
this.crychic.sendGroupPoke(parseInt(memberUin), parseInt(groupCode))
|
||||
NTQQApi.fetchUnitedCommendConfig().then()
|
||||
}
|
||||
}
|
||||
|
||||
export const crychic = new Crychic()
|
@@ -1,8 +1,8 @@
|
||||
import {BrowserWindow} from 'electron';
|
||||
import {NTQQApiClass} from "./ntcall";
|
||||
import {NTQQApiClass, NTQQApiMethod} from "./ntcall";
|
||||
import {NTQQMsgApi, sendMessagePool} from "./api/msg"
|
||||
import {ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User} from "./types";
|
||||
import {friends, getGroupMember, groups, selfInfo, tempGroupCodeMap, uidMaps} from "../common/data";
|
||||
import {friends, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap, uidMaps} from "../common/data";
|
||||
import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
|
||||
import {v4 as uuidv4} from "uuid"
|
||||
import {postOB11Event} from "../onebot11/server/postOB11Event";
|
||||
@@ -11,7 +11,7 @@ import fs from "fs";
|
||||
import {dbUtil} from "../common/db";
|
||||
import {NTQQGroupApi} from "./api/group";
|
||||
import {log} from "../common/utils/log";
|
||||
import {sleep} from "../common/utils/helper";
|
||||
import {isNumeric, sleep} from "../common/utils/helper";
|
||||
import {OB11Constructor} from "../onebot11/constructor";
|
||||
|
||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||
@@ -61,6 +61,12 @@ let receiveHooks: Array<{
|
||||
id: string
|
||||
}> = []
|
||||
|
||||
let callHooks: Array<{
|
||||
method: NTQQApiMethod[],
|
||||
hookFunc: ((callParams: unknown[]) => void | Promise<void>)
|
||||
}> = []
|
||||
|
||||
|
||||
export function hookNTQQApiReceive(window: BrowserWindow) {
|
||||
const originalSend = window.webContents.send;
|
||||
const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
|
||||
@@ -137,6 +143,27 @@ export function hookNTQQApiCall(window: BrowserWindow) {
|
||||
HOOK_LOG && log("call NTQQ api", thisArg, args);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
try {
|
||||
const _args: unknown[] = args[3][1];
|
||||
const cmdName: NTQQApiMethod = _args[0] as NTQQApiMethod;
|
||||
const callParams = _args.slice(1);
|
||||
callHooks.forEach(hook => {
|
||||
if (hook.method.includes(cmdName)) {
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
let _ = hook.hookFunc(callParams)
|
||||
if (hook.hookFunc.constructor.name === "AsyncFunction") {
|
||||
(_ as Promise<void>).then()
|
||||
}
|
||||
} catch (e) {
|
||||
log("hook call error", e, _args)
|
||||
}
|
||||
}).then()
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
return target.apply(thisArg, args);
|
||||
@@ -187,6 +214,16 @@ export function registerReceiveHook<PayloadType>(method: ReceiveCmd | ReceiveCmd
|
||||
return id;
|
||||
}
|
||||
|
||||
export function registerCallHook(method: NTQQApiMethod | NTQQApiMethod[], hookFunc: (callParams: unknown[]) => void | Promise<void>): void {
|
||||
if (!Array.isArray(method)) {
|
||||
method = [method]
|
||||
}
|
||||
callHooks.push({
|
||||
method,
|
||||
hookFunc
|
||||
})
|
||||
}
|
||||
|
||||
export function removeReceiveHook(id: string) {
|
||||
const index = receiveHooks.findIndex(h => h.id === id)
|
||||
receiveHooks.splice(index, 1);
|
||||
@@ -454,3 +491,23 @@ registerReceiveHook<{
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
|
||||
const peerUid = payload[0] as string;
|
||||
log("激活的聊天窗口被删除,准备重新激活", peerUid);
|
||||
let chatType = ChatType.friend;
|
||||
if (isNumeric(peerUid)) {
|
||||
chatType = ChatType.group;
|
||||
}
|
||||
else{
|
||||
// 检查是否好友
|
||||
if (!(await getFriend(peerUid))){
|
||||
chatType = ChatType.temp;
|
||||
}
|
||||
}
|
||||
const peer = {peerUid, chatType}
|
||||
await sleep(1000);
|
||||
NTQQMsgApi.activateChat(peer).then((r) => {
|
||||
log("重新激活聊天窗口", peer, {result: r.result, errMsg: r.errMsg})
|
||||
});
|
||||
})
|
@@ -26,6 +26,7 @@ export enum NTQQApiMethod {
|
||||
ACTIVE_CHAT_HISTORY = "nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat", // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
||||
HISTORY_MSG = "nodeIKernelMsgService/getMsgsIncludeSelf",
|
||||
GET_MULTI_MSG = "nodeIKernelMsgService/getMultiMsg",
|
||||
DELETE_ACTIVE_CHAT = "nodeIKernelMsgService/deleteActiveChatByUid",
|
||||
|
||||
LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike",
|
||||
SELF_INFO = "fetchAuthData",
|
||||
@@ -50,6 +51,7 @@ export enum NTQQApiMethod {
|
||||
GET_GROUP_NOTICE = "nodeIKernelGroupService/getSingleScreenNotifies",
|
||||
HANDLE_GROUP_REQUEST = "nodeIKernelGroupService/operateSysNotify",
|
||||
QUIT_GROUP = "nodeIKernelGroupService/quitGroup",
|
||||
GROUP_AT_ALL_REMAIN_COUNT = "nodeIKernelGroupService/getGroupRemainAtTimes",
|
||||
// READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange"
|
||||
HANDLE_FRIEND_REQUEST = "nodeIKernelBuddyService/approvalFriendRequest",
|
||||
KICK_MEMBER = "nodeIKernelGroupService/kickMember",
|
||||
@@ -77,7 +79,9 @@ export enum NTQQApiMethod {
|
||||
|
||||
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
||||
GET_SKEY = "nodeIKernelTipOffService/getPskey",
|
||||
UPDATE_SKEY = "updatePskey"
|
||||
UPDATE_SKEY = "updatePskey",
|
||||
|
||||
FETCH_UNITED_COMMEND_CONFIG = "nodeIKernelUnitedConfigService/fetchUnitedCommendConfig" // 发包需要调用的
|
||||
}
|
||||
|
||||
enum NTQQApiChannel {
|
||||
@@ -194,4 +198,15 @@ export class NTQQApi {
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
static async fetchUnitedCommendConfig() {
|
||||
return await callNTQQApi<GeneralCallResult>({
|
||||
methodName: NTQQApiMethod.FETCH_UNITED_COMMEND_CONFIG,
|
||||
args:[
|
||||
{
|
||||
groups: ['100243']
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
@@ -212,9 +212,28 @@ export interface GrayTipElement {
|
||||
}
|
||||
}
|
||||
|
||||
export enum FaceType {
|
||||
normal=1, // 小黄脸
|
||||
normal2=2, // 新小黄脸, 从faceIndex 222开始?
|
||||
dice=3 // 骰子
|
||||
}
|
||||
|
||||
export enum FaceIndex {
|
||||
dice = 358,
|
||||
RPS = 359 // 石头剪刀布
|
||||
}
|
||||
|
||||
export interface FaceElement {
|
||||
faceIndex: number,
|
||||
faceType: 1
|
||||
faceType: FaceType,
|
||||
faceText?: string,
|
||||
packId?: string,
|
||||
stickerId?: string,
|
||||
sourceType?: number,
|
||||
stickerType?: number,
|
||||
resultId?: string,
|
||||
surpriseId?: string,
|
||||
randomType?: number
|
||||
}
|
||||
|
||||
export interface MarketFaceElement {
|
||||
|
@@ -2,19 +2,15 @@ import {
|
||||
AtType,
|
||||
ChatType,
|
||||
ElementType,
|
||||
Group, PicSubType,
|
||||
Friend,
|
||||
Group,
|
||||
GroupMemberRole,
|
||||
PicSubType,
|
||||
RawMessage,
|
||||
SendArkElement,
|
||||
SendMessageElement
|
||||
} from "../../../ntqqapi/types";
|
||||
import {
|
||||
friends,
|
||||
getFriend,
|
||||
getGroup,
|
||||
getGroupMember,
|
||||
getUidByUin,
|
||||
selfInfo,
|
||||
} from "../../../common/data";
|
||||
import {friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo,} from "../../../common/data";
|
||||
import {
|
||||
OB11MessageCustomMusic,
|
||||
OB11MessageData,
|
||||
@@ -23,7 +19,7 @@ import {
|
||||
OB11MessageNode,
|
||||
OB11PostSendMsg
|
||||
} from '../../types';
|
||||
import {Peer} from "../../../ntqqapi/api/msg";
|
||||
import {NTQQMsgApi, Peer} from "../../../ntqqapi/api/msg";
|
||||
import {SendMsgElementConstructor} from "../../../ntqqapi/constructor";
|
||||
import BaseAction from "../BaseAction";
|
||||
import {ActionName, BaseCheckResult} from "../types";
|
||||
@@ -31,10 +27,11 @@ import * as fs from "node:fs";
|
||||
import {decodeCQCode} from "../../cqcode";
|
||||
import {dbUtil} from "../../../common/db";
|
||||
import {ALLOW_SEND_TEMP_MSG} from "../../../common/config";
|
||||
import {NTQQMsgApi} from "../../../ntqqapi/api/msg";
|
||||
import {log} from "../../../common/utils/log";
|
||||
import {sleep} from "../../../common/utils/helper";
|
||||
import {uri2local} from "../../../common/utils";
|
||||
import {crychic} from "../../../ntqqapi/external/crychic";
|
||||
import {NTQQGroupApi} from "../../../ntqqapi/api";
|
||||
|
||||
function checkSendMessage(sendMsgList: OB11MessageData[]) {
|
||||
function checkUri(uri: string): boolean {
|
||||
@@ -93,7 +90,7 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa
|
||||
return message;
|
||||
}
|
||||
|
||||
export async function createSendElements(messageData: OB11MessageData[], group: Group | undefined, ignoreTypes: OB11MessageDataType[] = []) {
|
||||
export async function createSendElements(messageData: OB11MessageData[], target: Group | Friend | undefined, ignoreTypes: OB11MessageDataType[] = []) {
|
||||
let sendElements: SendMessageElement[] = []
|
||||
let deleteAfterSentFiles: string[] = []
|
||||
for (let sendMsg of messageData) {
|
||||
@@ -109,17 +106,31 @@ export async function createSendElements(messageData: OB11MessageData[], group:
|
||||
}
|
||||
break;
|
||||
case OB11MessageDataType.at: {
|
||||
if (!group) {
|
||||
if (!target) {
|
||||
continue
|
||||
}
|
||||
let atQQ = sendMsg.data?.qq;
|
||||
if (atQQ) {
|
||||
atQQ = atQQ.toString()
|
||||
if (atQQ === "all") {
|
||||
// todo:查询剩余的at全体次数
|
||||
const groupCode = (target as Group)?.groupCode;
|
||||
let remainAtAllCount = 1
|
||||
let isAdmin: boolean = true;
|
||||
if (groupCode) {
|
||||
try {
|
||||
remainAtAllCount = (await NTQQGroupApi.getGroupAtAllRemainCount(groupCode)).atInfo.RemainAtAllCountForUin
|
||||
log(`群${groupCode}剩余at全体次数`, remainAtAllCount);
|
||||
const self = await getGroupMember((target as Group)?.groupCode, selfInfo.uin);
|
||||
isAdmin = self.role === GroupMemberRole.admin || self.role === GroupMemberRole.owner;
|
||||
} catch (e) {}
|
||||
}
|
||||
if(isAdmin && remainAtAllCount > 0) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
|
||||
}
|
||||
} else {
|
||||
// const atMember = group?.members.find(m => m.uin == atQQ)
|
||||
const atMember = await getGroupMember(group?.groupCode, atQQ);
|
||||
const atMember = await getGroupMember((target as Group)?.groupCode, atQQ);
|
||||
if (atMember) {
|
||||
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
|
||||
}
|
||||
@@ -197,7 +208,30 @@ export async function createSendElements(messageData: OB11MessageData[], group:
|
||||
case OB11MessageDataType.json: {
|
||||
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data))
|
||||
}
|
||||
break
|
||||
break;
|
||||
case OB11MessageDataType.poke: {
|
||||
let qq = sendMsg.data?.qq || sendMsg.data?.id
|
||||
if (qq) {
|
||||
if ("groupCode" in target) {
|
||||
crychic.sendGroupPoke(target.groupCode, qq.toString())
|
||||
} else {
|
||||
if (!qq) {
|
||||
qq = parseInt(target.uin)
|
||||
}
|
||||
crychic.sendFriendPoke(qq.toString())
|
||||
}
|
||||
sendElements.push(SendMsgElementConstructor.poke("", ""))
|
||||
}
|
||||
}
|
||||
break;
|
||||
case OB11MessageDataType.dice:{
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.dice(resultId));
|
||||
}break;
|
||||
case OB11MessageDataType.RPS:{
|
||||
const resultId = sendMsg.data?.result
|
||||
sendElements.push(SendMsgElementConstructor.rps(resultId));
|
||||
}break;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -261,6 +295,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
let isTempMsg = false;
|
||||
let group: Group | undefined = undefined;
|
||||
let friend: Friend | undefined = undefined;
|
||||
const genGroupPeer = async () => {
|
||||
group = await getGroup(payload.group_id.toString())
|
||||
peer.chatType = ChatType.group
|
||||
@@ -269,7 +304,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
|
||||
const genFriendPeer = () => {
|
||||
const friend = friends.find(f => f.uin == payload.user_id.toString())
|
||||
friend = friends.find(f => f.uin == payload.user_id.toString())
|
||||
if (friend) {
|
||||
// peer.name = friend.nickName
|
||||
peer.peerUid = friend.uid
|
||||
@@ -294,7 +329,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
} else {
|
||||
throw ("发送消息参数错误, 请指定group_id或user_id")
|
||||
}
|
||||
const messages = convertMessage2List(payload.message);
|
||||
const messages = convertMessage2List(payload.message, !!payload.auto_escape);
|
||||
if (this.getSpecialMsgNum(payload, OB11MessageDataType.node)) {
|
||||
try {
|
||||
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
|
||||
@@ -318,7 +353,12 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
}
|
||||
// log("send msg:", peer, sendElements)
|
||||
const {sendElements, deleteAfterSentFiles} = await createSendElements(messages, group)
|
||||
const {sendElements, deleteAfterSentFiles} = await createSendElements(messages, group || friend)
|
||||
if (sendElements.length === 1){
|
||||
if (sendElements[0] === null){
|
||||
return {message_id: 0}
|
||||
}
|
||||
}
|
||||
const returnMsg = await sendMsg(peer, sendElements, deleteAfterSentFiles)
|
||||
deleteAfterSentFiles.map(f => fs.unlink(f, () => {
|
||||
}));
|
||||
@@ -478,8 +518,6 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private genMusicElement(url: string, audio: string, title: string, content: string, image: string): SendArkElement {
|
||||
const musicJson = {
|
||||
app: 'com.tencent.structmsg',
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
} from "./types";
|
||||
import {
|
||||
AtType,
|
||||
ChatType,
|
||||
ChatType, FaceIndex,
|
||||
GrayTipElementSubType,
|
||||
Group,
|
||||
GroupMember,
|
||||
@@ -227,8 +227,19 @@ export class OB11Constructor {
|
||||
message_data["type"] = OB11MessageDataType.json;
|
||||
message_data["data"]["data"] = element.arkElement.bytesData;
|
||||
} else if (element.faceElement) {
|
||||
const faceId = element.faceElement.faceIndex;
|
||||
if (faceId === FaceIndex.dice){
|
||||
message_data["type"] = OB11MessageDataType.dice
|
||||
message_data["data"]["result"] = element.faceElement.resultId;
|
||||
}
|
||||
else if (faceId === FaceIndex.RPS){
|
||||
message_data["type"] = OB11MessageDataType.RPS
|
||||
message_data["data"]["result"] = element.faceElement.resultId;
|
||||
}
|
||||
else{
|
||||
message_data["type"] = OB11MessageDataType.face;
|
||||
message_data["data"]["id"] = element.faceElement.faceIndex.toString();
|
||||
}
|
||||
} else if (element.marketFaceElement) {
|
||||
message_data["type"] = OB11MessageDataType.mface;
|
||||
message_data["data"]["text"] = element.marketFaceElement.faceName;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import {OB11Message, OB11MessageAt, OB11MessageData} from "../types";
|
||||
import {getGroup, selfInfo} from "../../common/data";
|
||||
import {getFriend, getGroup, getUidByUin, selfInfo} from "../../common/data";
|
||||
import {OB11BaseMetaEvent} from "../event/meta/OB11BaseMetaEvent";
|
||||
import {OB11BaseNoticeEvent} from "../event/notice/OB11BaseNoticeEvent";
|
||||
import {WebSocket as WebSocketClass} from "ws";
|
||||
@@ -63,7 +63,6 @@ export function unregisterWsEventSender(ws: WebSocketClass) {
|
||||
|
||||
export function postWsEvent(event: PostEventType) {
|
||||
for (const ws of eventWSList) {
|
||||
log(ws)
|
||||
new Promise(() => {
|
||||
wsReply(ws, event);
|
||||
}).then()
|
||||
@@ -116,6 +115,7 @@ export function postOB11Event(msg: PostEventType, reportSelf = false) {
|
||||
peerUid: msg.user_id.toString()
|
||||
}
|
||||
if (msg.message_type == "private") {
|
||||
peer.peerUid = getUidByUin(msg.user_id.toString())
|
||||
if (msg.sub_type === "group") {
|
||||
peer.chatType = ChatType.temp
|
||||
}
|
||||
@@ -140,6 +140,7 @@ export function postOB11Event(msg: PostEventType, reportSelf = false) {
|
||||
}
|
||||
replyMessage = replyMessage.concat(convertMessage2List(reply, resJson.auto_escape))
|
||||
const {sendElements, deleteAfterSentFiles} = await createSendElements(replyMessage, group)
|
||||
log(`发送消息给`, peer, sendElements)
|
||||
sendMsg(peer, sendElements, deleteAfterSentFiles, false).then()
|
||||
} else if (resJson.delete) {
|
||||
NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then()
|
||||
|
@@ -116,7 +116,10 @@ export enum OB11MessageDataType {
|
||||
markdown = "markdown",
|
||||
node = "node", // 合并转发消息节点
|
||||
forward = "forward", // 合并转发消息,用于上报
|
||||
xml = "xml"
|
||||
xml = "xml",
|
||||
poke = "poke",
|
||||
dice = "dice",
|
||||
RPS = "rps"
|
||||
}
|
||||
|
||||
export interface OB11MessageMFace{
|
||||
@@ -125,6 +128,20 @@ export interface OB11MessageMFace{
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface OB11MessageDice{
|
||||
type: OB11MessageDataType.dice,
|
||||
data: {
|
||||
result: number
|
||||
}
|
||||
}
|
||||
export interface OB11MessageRPS{
|
||||
type: OB11MessageDataType.RPS,
|
||||
data: {
|
||||
result: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface OB11MessageText {
|
||||
type: OB11MessageDataType.text,
|
||||
data: {
|
||||
@@ -132,6 +149,14 @@ export interface OB11MessageText {
|
||||
}
|
||||
}
|
||||
|
||||
export interface OB11MessagePoke{
|
||||
type: OB11MessageDataType.poke
|
||||
data: {
|
||||
qq?: number,
|
||||
id?: number
|
||||
}
|
||||
}
|
||||
|
||||
interface OB11MessageFileBase {
|
||||
data: {
|
||||
thumb?: string;
|
||||
@@ -217,7 +242,8 @@ export type OB11MessageData =
|
||||
OB11MessageFace | OB11MessageMFace |
|
||||
OB11MessageAt | OB11MessageReply |
|
||||
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
|
||||
OB11MessageNode | OB11MessageCustomMusic | OB11MessageJson
|
||||
OB11MessageNode | OB11MessageCustomMusic | OB11MessageJson | OB11MessagePoke |
|
||||
OB11MessageDice | OB11MessageRPS
|
||||
|
||||
export interface OB11PostSendMsg {
|
||||
message_type?: "private" | "group"
|
||||
@@ -225,6 +251,7 @@ export interface OB11PostSendMsg {
|
||||
group_id?: string,
|
||||
message: OB11MessageMixType;
|
||||
messages?: OB11MessageMixType; // 兼容 go-cqhttp
|
||||
auto_escape?: boolean
|
||||
}
|
||||
|
||||
export interface OB11Version {
|
||||
|
@@ -133,8 +133,8 @@ async function onSettingWindowCreated(view: Element) {
|
||||
]),
|
||||
SettingList([
|
||||
SettingItem(
|
||||
'接收戳一戳消息, 暂时只支持Windows版的LLOneBot',
|
||||
`重启QQ后生效,如果导致QQ崩溃请勿开启此项`,
|
||||
'戳一戳消息, 暂时只支持Windows版的LLOneBot',
|
||||
`重启QQ后生效,如果导致QQ崩溃请勿开启此项, 群戳一戳只能收到群号`,
|
||||
SettingSwitch('enablePoke', config.enablePoke),
|
||||
),
|
||||
SettingItem(
|
||||
|
@@ -1 +1 @@
|
||||
export const version = "3.20.7"
|
||||
export const version = "3.22.1"
|
Reference in New Issue
Block a user