mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1a015ac8d3 | ||
![]() |
6390620ddd | ||
![]() |
0d19005dc3 | ||
![]() |
c6479dd2c4 | ||
![]() |
8871331b7c | ||
![]() |
e01148b86a | ||
![]() |
2f87e3818e | ||
![]() |
2c8a594c38 | ||
![]() |
1508dab7fe | ||
![]() |
958b21e47e | ||
![]() |
781c3311ae | ||
![]() |
52850d172e | ||
![]() |
52a065542e | ||
![]() |
fd10469685 | ||
![]() |
a2ee75b113 | ||
![]() |
0f7f243b98 | ||
![]() |
97d7996a50 | ||
![]() |
b658d164f9 | ||
![]() |
f150ae478b | ||
![]() |
d1f68553f1 |
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"manifest_version": 4,
|
||||
"type": "extension",
|
||||
"name": "LLOneBot v3.26.4",
|
||||
"name": "LLOneBot v3.26.7",
|
||||
"slug": "LLOneBot",
|
||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
||||
"version": "3.26.4",
|
||||
"version": "3.26.7",
|
||||
"icon": "./icon.jpg",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -23,7 +23,7 @@
|
||||
"file-type": "^19.0.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"level": "^8.0.1",
|
||||
"silk-wasm": "^3.3.4",
|
||||
"silk-wasm": "^3.6.0",
|
||||
"utf-8-validate": "^6.0.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.16.0"
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import fs from 'fs'
|
||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm'
|
||||
import fsPromise from 'fs/promises'
|
||||
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm'
|
||||
import { log } from './log'
|
||||
import path from 'node:path'
|
||||
import { DATA_DIR, TEMP_DIR } from './index'
|
||||
import { TEMP_DIR } from './index'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getConfigUtil } from '../config'
|
||||
import { spawn } from 'node:child_process'
|
||||
@@ -60,10 +60,11 @@ export async function encodeSilk(filePath: string) {
|
||||
// }
|
||||
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath)
|
||||
const pttPath = path.join(TEMP_DIR, uuidv4())
|
||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
||||
if (!isSilk(file)) {
|
||||
log(`语音文件${filePath}需要转换成silk`)
|
||||
const _isWav = await isWavFile(filePath)
|
||||
const _isWav = isWav(file)
|
||||
const pcmPath = pttPath + '.pcm'
|
||||
let sampleRate = 0
|
||||
const convert = () => {
|
||||
@@ -79,7 +80,8 @@ export async function encodeSilk(filePath: string) {
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000
|
||||
const data = fs.readFileSync(pcmPath)
|
||||
fs.unlink(pcmPath, (err) => {})
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
})
|
||||
return resolve(data)
|
||||
}
|
||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
||||
@@ -91,7 +93,7 @@ export async function encodeSilk(filePath: string) {
|
||||
if (!_isWav) {
|
||||
input = await convert()
|
||||
} else {
|
||||
input = fs.readFileSync(filePath)
|
||||
input = file
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
||||
const { fmt } = getWavFileInfo(input)
|
||||
// log(`wav文件信息`, fmt)
|
||||
@@ -108,7 +110,7 @@ export async function encodeSilk(filePath: string) {
|
||||
duration: silk.duration / 1000,
|
||||
}
|
||||
} else {
|
||||
const silk = fs.readFileSync(filePath)
|
||||
const silk = file
|
||||
let duration = 0
|
||||
try {
|
||||
duration = getDuration(silk) / 1000
|
||||
@@ -128,3 +130,41 @@ export async function encodeSilk(filePath: string) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export async function decodeSilk(inputFilePath: string, outFormat: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac' = 'mp3') {
|
||||
const silkArrayBuffer = await fsPromise.readFile(inputFilePath)
|
||||
const data = (await decode(silkArrayBuffer, 24000)).data
|
||||
const fileName = path.join(TEMP_DIR, path.basename(inputFilePath))
|
||||
const outPCMPath = fileName + '.pcm'
|
||||
const outFilePath = fileName + '.' + outFormat
|
||||
await fsPromise.writeFile(outPCMPath, data)
|
||||
const convert = () => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const ffmpegPath = getConfigUtil().getConfig().ffmpeg || process.env.FFMPEG_PATH || 'ffmpeg'
|
||||
const cp = spawn(ffmpegPath, [
|
||||
'-y',
|
||||
'-f', 's16le', // PCM format
|
||||
'-ar', '24000', // Sample rate
|
||||
'-ac', '1', // Number of audio channels
|
||||
'-i', outPCMPath,
|
||||
outFilePath,
|
||||
])
|
||||
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)) {
|
||||
fs.unlink(outPCMPath, (err) => {
|
||||
})
|
||||
return resolve(outFilePath)
|
||||
}
|
||||
const exitErr = `FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`
|
||||
log(exitErr)
|
||||
reject(Error(`FFmpeg处理转换失败,${exitErr}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
return convert()
|
||||
}
|
@@ -25,7 +25,7 @@ import {
|
||||
selfInfo,
|
||||
uidMaps,
|
||||
} from '../common/data'
|
||||
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook } from '../ntqqapi/hook'
|
||||
import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, startHook } from '../ntqqapi/hook'
|
||||
import { OB11Constructor } from '../onebot11/constructor'
|
||||
import {
|
||||
ChatType,
|
||||
@@ -56,6 +56,7 @@ import { getConfigUtil } from '../common/config'
|
||||
import { checkFfmpeg } from '../common/utils/video'
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||
import '../ntqqapi/native/wrapper'
|
||||
import { sentMessages } from '@/ntqqapi/api'
|
||||
|
||||
let running = false
|
||||
|
||||
@@ -200,6 +201,7 @@ function onLoad() {
|
||||
}
|
||||
|
||||
async function startReceiveHook() {
|
||||
startHook().then()
|
||||
if (getConfigUtil().getConfig().enablePoke) {
|
||||
crychic.loadNode()
|
||||
crychic.registerPokeHandler((id, isGroup) => {
|
||||
@@ -226,6 +228,10 @@ function onLoad() {
|
||||
const recallMsgIds: string[] = [] // 避免重复上报
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
||||
for (const message of payload.msgList) {
|
||||
const sentMessage = sentMessages[message.msgId]
|
||||
if (sentMessage){
|
||||
Object.assign(sentMessage, message)
|
||||
}
|
||||
log('message update', message.msgId, message)
|
||||
if (message.recallTime != '0') {
|
||||
if (recallMsgIds.includes(message.msgId)) {
|
||||
@@ -436,14 +442,33 @@ function onLoad() {
|
||||
uidMaps[value] = key
|
||||
}
|
||||
})
|
||||
startReceiveHook().then()
|
||||
NTQQGroupApi.getGroups(true).then(groups=> {
|
||||
for (let group of groups) {
|
||||
}
|
||||
}
|
||||
).catch(log)
|
||||
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
|
||||
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
||||
try{
|
||||
log('start get groups')
|
||||
const _groups = await NTQQGroupApi.getGroups()
|
||||
log('_groups', _groups)
|
||||
await Promise.all(
|
||||
_groups.map(async (group) => {
|
||||
try {
|
||||
const members = await NTQQGroupApi.getGroupMembers(group.groupCode)
|
||||
group.members = members
|
||||
groups.push(group)
|
||||
} catch (e) {
|
||||
log('获取群成员失败', e)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
catch (e) {
|
||||
log('获取群列表失败', e)
|
||||
}
|
||||
finally {
|
||||
log('start activate group member info')
|
||||
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
|
||||
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
||||
startReceiveHook().then()
|
||||
}
|
||||
|
||||
|
||||
const config = getConfigUtil().getConfig()
|
||||
if (config.ob11.enableHttp) {
|
||||
ob11HTTPServer.start(config.ob11.httpPort)
|
||||
|
@@ -10,15 +10,23 @@ import {
|
||||
ElementType,
|
||||
IMAGE_HTTP_HOST,
|
||||
IMAGE_HTTP_HOST_NT, PicElement,
|
||||
RawMessage,
|
||||
} from '../types'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { ReceiveCmdS } from '../hook'
|
||||
import { log } from '@/common/utils'
|
||||
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
||||
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
||||
import { Peer } from '@/ntqqapi/api/msg'
|
||||
|
||||
export class NTQQFileApi {
|
||||
static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise<string> {
|
||||
return (await wrapperApi.NodeIQQNTWrapperSession.getRichMediaService().getVideoPlayUrlV2(peer,
|
||||
msgId,
|
||||
elementId,
|
||||
0,
|
||||
{ downSourceType: 1, triggerType: 1 })).urlResult?.domainUrl[0]?.url;
|
||||
}
|
||||
static async getFileType(filePath: string) {
|
||||
return await callNTQQApi<{ ext: string }>({
|
||||
className: NTQQApiClass.FS_API,
|
||||
|
@@ -35,14 +35,20 @@ export class NTQQGroupApi {
|
||||
})
|
||||
}
|
||||
static async getGroups(forced = false) {
|
||||
let cbCmd = ReceiveCmdS.GROUPS
|
||||
if (process.platform != 'win32') {
|
||||
cbCmd = ReceiveCmdS.GROUPS_STORE
|
||||
}
|
||||
// let cbCmd = ReceiveCmdS.GROUPS
|
||||
// if (process.platform != 'win32') {
|
||||
// cbCmd = ReceiveCmdS.GROUPS_STORE
|
||||
// }
|
||||
const result = await callNTQQApi<{
|
||||
updateType: number
|
||||
groupList: Group[]
|
||||
}>({ methodName: NTQQApiMethod.GROUPS, args: [{ force_update: forced }, undefined], cbCmd })
|
||||
}>({
|
||||
methodName: NTQQApiMethod.GROUPS,
|
||||
args: [{ force_update: forced }, undefined],
|
||||
cbCmd: [ReceiveCmdS.GROUPS, ReceiveCmdS.GROUPS_STORE],
|
||||
afterFirstCmd: false,
|
||||
})
|
||||
log('get groups result', result)
|
||||
return result.groupList
|
||||
}
|
||||
static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> {
|
||||
|
@@ -7,7 +7,9 @@ import { log } from '../../common/utils/log'
|
||||
import { sleep } from '../../common/utils/helper'
|
||||
import { isQQ998 } from '../../common/utils'
|
||||
|
||||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunnc
|
||||
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {} // peerUid: callbackFunc
|
||||
|
||||
export let sentMessages: Record<string, RawMessage> = {} // msgId: RawMessage
|
||||
|
||||
export interface Peer {
|
||||
chatType: ChatType
|
||||
@@ -40,17 +42,20 @@ async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 100
|
||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||
delete sendMessagePool[peerUid]
|
||||
sentMessage = rawMessage
|
||||
sentMessages[rawMessage.msgId] = rawMessage
|
||||
}
|
||||
|
||||
let checkSendCompleteUsingTime = 0
|
||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||
if (sentMessage) {
|
||||
if (waitComplete) {
|
||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
||||
if (sentMessage.sendStatus == 2) {
|
||||
delete sentMessages[sentMessage.msgId]
|
||||
return sentMessage
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete sentMessages[sentMessage.msgId]
|
||||
return sentMessage
|
||||
}
|
||||
// log(`给${peerUid}发送消息成功`)
|
||||
|
@@ -22,6 +22,7 @@ import { NTQQGroupApi } from './api/group'
|
||||
import { log } from '@/common/utils'
|
||||
import { isNumeric, sleep } from '@/common/utils'
|
||||
import { OB11Constructor } from '../onebot11/constructor'
|
||||
import { OB11GroupCardEvent } from '../onebot11/event/notice/OB11GroupCardEvent'
|
||||
|
||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||
|
||||
@@ -324,207 +325,222 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
|
||||
}
|
||||
}
|
||||
|
||||
// 群列表变动
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
} else {
|
||||
if (process.platform == 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
} else {
|
||||
if (process.platform != 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
export async function startHook() {
|
||||
|
||||
registerReceiveHook<{
|
||||
groupCode: string
|
||||
dataSource: number
|
||||
members: Set<GroupMember>
|
||||
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
|
||||
const groupCode = payload.groupCode
|
||||
const members = Array.from(payload.members.values())
|
||||
// log("群成员信息变动", groupCode, members)
|
||||
for (const member of members) {
|
||||
const existMember = await getGroupMember(groupCode, member.uin)
|
||||
if (existMember) {
|
||||
Object.assign(existMember, member)
|
||||
// 群列表变动
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
}
|
||||
}
|
||||
// const existGroup = groups.find(g => g.groupCode == groupCode);
|
||||
// if (existGroup) {
|
||||
// log("对比群成员", existGroup.members, members)
|
||||
// for (const member of members) {
|
||||
// const existMember = existGroup.members.find(m => m.uin == member.uin);
|
||||
// if (existMember) {
|
||||
// log("对比群名片", existMember.cardName, member.cardName)
|
||||
// if (existMember.cardName != member.cardName) {
|
||||
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName));
|
||||
// }
|
||||
// Object.assign(existMember, member);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
})
|
||||
else {
|
||||
if (process.platform == 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS_STORE, (payload) => {
|
||||
// updateType 3是群列表变动,2是群成员变动
|
||||
// log("群列表变动", payload.updateType, payload.groupList)
|
||||
if (payload.updateType != 2) {
|
||||
updateGroups(payload.groupList).then()
|
||||
}
|
||||
else {
|
||||
if (process.platform != 'win32') {
|
||||
processGroupEvent(payload).then()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{
|
||||
groupCode: string
|
||||
dataSource: number
|
||||
members: Set<GroupMember>
|
||||
}>(ReceiveCmdS.GROUP_MEMBER_INFO_UPDATE, async (payload) => {
|
||||
const groupCode = payload.groupCode
|
||||
const members = Array.from(payload.members.values())
|
||||
// log("群成员信息变动", groupCode, members)
|
||||
for (const member of members) {
|
||||
const existMember = await getGroupMember(groupCode, member.uin)
|
||||
if (existMember) {
|
||||
if (member.cardName != existMember.cardName) {
|
||||
log('群成员名片变动', `${groupCode}: ${existMember.uin}`, existMember.cardName, '->', member.cardName)
|
||||
postOb11Event(
|
||||
new OB11GroupCardEvent(parseInt(groupCode), parseInt(member.uin), member.cardName, existMember.cardName),
|
||||
)
|
||||
}
|
||||
Object.assign(existMember, member)
|
||||
}
|
||||
}
|
||||
// const existGroup = groups.find(g => g.groupCode == groupCode);
|
||||
// if (existGroup) {
|
||||
// log("对比群成员", existGroup.members, members)
|
||||
// for (const member of members) {
|
||||
// const existMember = existGroup.members.find(m => m.uin == member.uin);
|
||||
// if (existMember) {
|
||||
// log("对比群名片", existMember.cardName, member.cardName)
|
||||
// if (existMember.cardName != member.cardName) {
|
||||
// postOB11Event(new OB11GroupCardEvent(parseInt(existGroup.groupCode), parseInt(member.uin), member.cardName, existMember.cardName));
|
||||
// }
|
||||
// Object.assign(existMember, member);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
})
|
||||
|
||||
// 好友列表变动
|
||||
registerReceiveHook<{
|
||||
data:CategoryFriend[]
|
||||
}>(ReceiveCmdS.FRIENDS, (payload) => {
|
||||
rawFriends.length = 0;
|
||||
rawFriends.push(...payload.data);
|
||||
for (const fData of payload.data) {
|
||||
const _friends = fData.buddyList
|
||||
for (let friend of _friends) {
|
||||
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
|
||||
let existFriend = friends.find((f) => f.uin == friend.uin)
|
||||
if (!existFriend) {
|
||||
friends.push(friend)
|
||||
} else {
|
||||
Object.assign(existFriend, friend)
|
||||
registerReceiveHook<{
|
||||
data: CategoryFriend[]
|
||||
}>(ReceiveCmdS.FRIENDS, (payload) => {
|
||||
rawFriends.length = 0;
|
||||
rawFriends.push(...payload.data);
|
||||
for (const fData of payload.data) {
|
||||
const _friends = fData.buddyList
|
||||
for (let friend of _friends) {
|
||||
NTQQMsgApi.activateChat({ peerUid: friend.uid, chatType: ChatType.friend }).then()
|
||||
let existFriend = friends.find((f) => f.uin == friend.uin)
|
||||
if (!existFriend) {
|
||||
friends.push(friend)
|
||||
}
|
||||
else {
|
||||
Object.assign(existFriend, friend)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
|
||||
// 保存一下uid
|
||||
for (const message of payload.msgList) {
|
||||
const uid = message.senderUid
|
||||
const uin = message.senderUin
|
||||
if (uid && uin) {
|
||||
if (message.chatType === ChatType.temp) {
|
||||
dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => {
|
||||
if (!receivedTempUinMap[uin]) {
|
||||
receivedTempUinMap[uin] = uid
|
||||
dbUtil.setReceivedTempUinMap(receivedTempUinMap)
|
||||
}
|
||||
})
|
||||
}
|
||||
uidMaps[uid] = uin
|
||||
}
|
||||
}
|
||||
|
||||
// 自动清理新消息文件
|
||||
const { autoDeleteFile } = getConfigUtil().getConfig()
|
||||
if (!autoDeleteFile) {
|
||||
return
|
||||
}
|
||||
for (const message of payload.msgList) {
|
||||
// log("收到新消息,push到历史记录", message.msgId)
|
||||
// dbUtil.addMsg(message).then()
|
||||
// 清理文件
|
||||
|
||||
for (const msgElement of message.elements) {
|
||||
setTimeout(() => {
|
||||
const picPath = msgElement.picElement?.sourcePath
|
||||
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
|
||||
const pttPath = msgElement.pttElement?.filePath
|
||||
const filePath = msgElement.fileElement?.filePath
|
||||
const videoPath = msgElement.videoElement?.filePath
|
||||
const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()]
|
||||
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
|
||||
if (msgElement.picElement) {
|
||||
pathList.push(...Object.values(msgElement.picElement.thumbPath))
|
||||
}
|
||||
const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement
|
||||
if (aioOpGrayTipElement) {
|
||||
tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat
|
||||
}
|
||||
|
||||
// log("需要清理的文件", pathList);
|
||||
for (const path of pathList) {
|
||||
if (path) {
|
||||
fs.unlink(picPath, () => {
|
||||
log('删除文件成功', path)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, getConfigUtil().getConfig().autoDeleteFileSecond * 1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
|
||||
const message = msgRecord
|
||||
const peerUid = message.peerUid
|
||||
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
|
||||
// log("收到自己发送成功的消息", message.msgId, message.msgSeq);
|
||||
dbUtil.addMsg(message).then()
|
||||
const sendCallback = sendMessagePool[peerUid]
|
||||
if (sendCallback) {
|
||||
try {
|
||||
sendCallback(message)
|
||||
} catch (e) {
|
||||
log('receive self msg error', e.stack)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
|
||||
selfInfo.online = info.info.status !== 20
|
||||
})
|
||||
|
||||
let activatedPeerUids: string[] = []
|
||||
registerReceiveHook<{
|
||||
changedRecentContactLists: {
|
||||
listType: number
|
||||
sortedContactList: string[]
|
||||
changedList: {
|
||||
id: string // peerUid
|
||||
chatType: ChatType
|
||||
}[]
|
||||
}[]
|
||||
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
|
||||
for (const recentContact of payload.changedRecentContactLists) {
|
||||
for (const changedContact of recentContact.changedList) {
|
||||
if (activatedPeerUids.includes(changedContact.id)) continue
|
||||
activatedPeerUids.push(changedContact.id)
|
||||
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
|
||||
if (changedContact.chatType === ChatType.temp) {
|
||||
log('收到临时会话消息', peer)
|
||||
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
|
||||
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
|
||||
let lastTempMsg = msgList.pop()
|
||||
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
|
||||
if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) {
|
||||
OB11Constructor.message(lastTempMsg).then((r) => postOb11Event(r))
|
||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.NEW_MSG, ReceiveCmdS.NEW_ACTIVE_MSG], (payload) => {
|
||||
// 保存一下uid
|
||||
for (const message of payload.msgList) {
|
||||
const uid = message.senderUid
|
||||
const uin = message.senderUin
|
||||
if (uid && uin) {
|
||||
if (message.chatType === ChatType.temp) {
|
||||
dbUtil.getReceivedTempUinMap().then((receivedTempUinMap) => {
|
||||
if (!receivedTempUinMap[uin]) {
|
||||
receivedTempUinMap[uin] = uid
|
||||
dbUtil.setReceivedTempUinMap(receivedTempUinMap)
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
NTQQMsgApi.activateChat(peer).then()
|
||||
}
|
||||
uidMaps[uid] = uin
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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 { autoDeleteFile } = getConfigUtil().getConfig()
|
||||
if (!autoDeleteFile) {
|
||||
return
|
||||
}
|
||||
for (const message of payload.msgList) {
|
||||
// log("收到新消息,push到历史记录", message.msgId)
|
||||
// dbUtil.addMsg(message).then()
|
||||
// 清理文件
|
||||
|
||||
for (const msgElement of message.elements) {
|
||||
setTimeout(() => {
|
||||
const picPath = msgElement.picElement?.sourcePath
|
||||
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
|
||||
const pttPath = msgElement.pttElement?.filePath
|
||||
const filePath = msgElement.fileElement?.filePath
|
||||
const videoPath = msgElement.videoElement?.filePath
|
||||
const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()]
|
||||
const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath]
|
||||
if (msgElement.picElement) {
|
||||
pathList.push(...Object.values(msgElement.picElement.thumbPath))
|
||||
}
|
||||
const aioOpGrayTipElement = msgElement.grayTipElement?.aioOpGrayTipElement
|
||||
if (aioOpGrayTipElement) {
|
||||
tempGroupCodeMap[aioOpGrayTipElement.peerUid] = aioOpGrayTipElement.fromGrpCodeOfTmpChat
|
||||
}
|
||||
|
||||
// log("需要清理的文件", pathList);
|
||||
for (const path of pathList) {
|
||||
if (path) {
|
||||
fs.unlink(picPath, () => {
|
||||
log('删除文件成功', path)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, getConfigUtil().getConfig().autoDeleteFileSecond * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
const peer = { peerUid, chatType }
|
||||
await sleep(1000)
|
||||
NTQQMsgApi.activateChat(peer).then((r) => {
|
||||
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
|
||||
})
|
||||
})
|
||||
|
||||
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => {
|
||||
const message = msgRecord
|
||||
const peerUid = message.peerUid
|
||||
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
|
||||
// log("收到自己发送成功的消息", message.msgId, message.msgSeq);
|
||||
dbUtil.addMsg(message).then()
|
||||
const sendCallback = sendMessagePool[peerUid]
|
||||
if (sendCallback) {
|
||||
try {
|
||||
sendCallback(message)
|
||||
} catch (e) {
|
||||
log('receive self msg error', e.stack)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => {
|
||||
selfInfo.online = info.info.status !== 20
|
||||
})
|
||||
|
||||
let activatedPeerUids: string[] = []
|
||||
registerReceiveHook<{
|
||||
changedRecentContactLists: {
|
||||
listType: number
|
||||
sortedContactList: string[]
|
||||
changedList: {
|
||||
id: string // peerUid
|
||||
chatType: ChatType
|
||||
}[]
|
||||
}[]
|
||||
}>(ReceiveCmdS.RECENT_CONTACT, async (payload) => {
|
||||
for (const recentContact of payload.changedRecentContactLists) {
|
||||
for (const changedContact of recentContact.changedList) {
|
||||
if (activatedPeerUids.includes(changedContact.id)) continue
|
||||
activatedPeerUids.push(changedContact.id)
|
||||
const peer = { peerUid: changedContact.id, chatType: changedContact.chatType }
|
||||
if (changedContact.chatType === ChatType.temp) {
|
||||
log('收到临时会话消息', peer)
|
||||
NTQQMsgApi.activateChatAndGetHistory(peer).then(() => {
|
||||
NTQQMsgApi.getMsgHistory(peer, '', 20).then(({ msgList }) => {
|
||||
let lastTempMsg = msgList.pop()
|
||||
log('激活窗口之前的第一条临时会话消息:', lastTempMsg)
|
||||
if (Date.now() / 1000 - parseInt(lastTempMsg.msgTime) < 5) {
|
||||
OB11Constructor.message(lastTempMsg).then((r) => postOb11Event(r))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
NTQQMsgApi.activateChat(peer).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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 })
|
||||
})
|
||||
})
|
||||
|
||||
}
|
@@ -107,7 +107,7 @@ interface NTQQApiParams {
|
||||
channel?: NTQQApiChannel
|
||||
classNameIsRegister?: boolean
|
||||
args?: unknown[]
|
||||
cbCmd?: ReceiveCmd | null
|
||||
cbCmd?: ReceiveCmd | ReceiveCmd[] | null
|
||||
cmdCB?: (payload: any) => boolean
|
||||
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
|
||||
timeoutSecond?: number
|
||||
@@ -147,7 +147,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
success = true
|
||||
resolve(r)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据
|
||||
const secondCallback = () => {
|
||||
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
|
||||
@@ -158,7 +159,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
success = true
|
||||
resolve(payload)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
removeReceiveHook(hookId)
|
||||
success = true
|
||||
resolve(payload)
|
||||
@@ -170,7 +172,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
log(`${methodName} callback`, result)
|
||||
if (result?.result == 0 || result === undefined) {
|
||||
afterFirstCmd && secondCallback()
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
success = true
|
||||
reject(`ntqq api call failed, ${result.errMsg}`)
|
||||
}
|
||||
@@ -188,7 +191,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
||||
channel,
|
||||
{
|
||||
sender: {
|
||||
send: (..._args: unknown[]) => {},
|
||||
send: (..._args: unknown[]) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{ type: 'request', callbackId: uuid, eventName },
|
||||
|
@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction'
|
||||
import fs from 'fs/promises'
|
||||
import { dbUtil } from '@/common/db'
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import { log, sleep, uri2local } from '@/common/utils'
|
||||
import { checkFileReceived, log, sleep, uri2local } from '@/common/utils'
|
||||
import { NTQQFileApi } from '@/ntqqapi/api'
|
||||
import { ActionName } from '../types'
|
||||
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||
@@ -38,20 +38,21 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
||||
log('找到了文件 element', element)
|
||||
// 构建下载函数
|
||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
||||
await sleep(1000) // 这里延时是为何?
|
||||
// 等待文件下载完成
|
||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||
log('下载完成后的msg', msg)
|
||||
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
||||
await checkFileReceived(cache.filePath, 10 * 1000)
|
||||
dbUtil.addFileCache(file, cache).then()
|
||||
}
|
||||
}
|
||||
}
|
||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||
const cache = await dbUtil.getFileCache(payload.file)
|
||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||
let cache = await dbUtil.getFileCache(payload.file)
|
||||
if (!cache) {
|
||||
throw new Error('file not found')
|
||||
}
|
||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||
if (cache.downloadFunc) {
|
||||
await cache.downloadFunc()
|
||||
}
|
||||
|
@@ -1,5 +1,9 @@
|
||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
||||
import { ActionName } from '../types'
|
||||
import {decodeSilk} from "@/common/utils/audio";
|
||||
import { getConfigUtil } from '@/common/config'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
interface Payload extends GetFilePayload {
|
||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||
@@ -9,7 +13,13 @@ export default class GetRecord extends GetFileBase {
|
||||
actionName = ActionName.GetRecord
|
||||
|
||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
||||
let res = super._handle(payload)
|
||||
let res = await super._handle(payload)
|
||||
res.file = await decodeSilk(res.file, payload.out_format)
|
||||
res.file_name = path.basename(res.file)
|
||||
res.file_size = fs.statSync(res.file).size.toString()
|
||||
if (getConfigUtil().getConfig().enableLocalFile2Url){
|
||||
res.base64 = fs.readFileSync(res.file, 'base64')
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
@@ -188,9 +188,7 @@ export class OB11Constructor {
|
||||
element.picElement.sourcePath,
|
||||
)
|
||||
},
|
||||
})
|
||||
.then()
|
||||
// 不在自动下载图片
|
||||
}).then()
|
||||
}
|
||||
else if (element.videoElement || element.fileElement) {
|
||||
const videoOrFileElement = element.videoElement || element.fileElement
|
||||
@@ -200,6 +198,13 @@ export class OB11Constructor {
|
||||
message_data['data']['path'] = videoOrFileElement.filePath
|
||||
message_data['data']['file_id'] = videoOrFileElement.fileUuid
|
||||
message_data['data']['file_size'] = videoOrFileElement.fileSize
|
||||
if (element.videoElement) {
|
||||
message_data['data']['url'] = await NTQQFileApi.getVideoUrl({
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
}, msg.msgId, element.elementId,
|
||||
)
|
||||
}
|
||||
dbUtil
|
||||
.addFileCache(videoOrFileElement.fileUuid, {
|
||||
msgId: msg.msgId,
|
||||
|
@@ -10,6 +10,7 @@ import { WebSocket as WebSocketClass } from 'ws'
|
||||
import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent'
|
||||
import { log } from '../../../common/utils/log'
|
||||
import { getConfigUtil } from '../../../common/config'
|
||||
import { version } from '../../../version'
|
||||
|
||||
export let rwsList: ReverseWebsocket[] = []
|
||||
|
||||
@@ -85,6 +86,7 @@ export class ReverseWebsocket {
|
||||
'X-Self-ID': selfInfo.uin,
|
||||
Authorization: `Bearer ${token}`,
|
||||
'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段
|
||||
'User-Agent': `LLOneBot/${version}`,
|
||||
},
|
||||
})
|
||||
registerWsEventSender(this.websocket)
|
||||
|
@@ -27,6 +27,7 @@ class OB11WebsocketServer extends WebsocketServerBase {
|
||||
}
|
||||
try {
|
||||
let handleResult = await action.websocketHandle(params, echo)
|
||||
handleResult.echo = echo
|
||||
wsReply(wsClient, handleResult)
|
||||
} catch (e) {
|
||||
wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo))
|
||||
|
@@ -1 +1 @@
|
||||
export const version = '3.26.4'
|
||||
export const version = '3.26.7'
|
||||
|
Reference in New Issue
Block a user