mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2a1aa8c649 | ||
![]() |
1633734e08 | ||
![]() |
dff92e6f27 | ||
![]() |
dba5e30d5d | ||
![]() |
2d04ab2e72 | ||
![]() |
1a015ac8d3 | ||
![]() |
6390620ddd | ||
![]() |
0d19005dc3 | ||
![]() |
c6479dd2c4 | ||
![]() |
8871331b7c | ||
![]() |
e01148b86a | ||
![]() |
2f87e3818e | ||
![]() |
2c8a594c38 | ||
![]() |
1508dab7fe | ||
![]() |
958b21e47e | ||
![]() |
781c3311ae | ||
![]() |
52850d172e | ||
![]() |
52a065542e | ||
![]() |
fd10469685 | ||
![]() |
a2ee75b113 | ||
![]() |
0f7f243b98 | ||
![]() |
97d7996a50 | ||
![]() |
b658d164f9 | ||
![]() |
f150ae478b | ||
![]() |
d1f68553f1 | ||
![]() |
f47f0800de | ||
![]() |
b7ddefc950 | ||
![]() |
25b3325a44 | ||
![]() |
c281b87bab | ||
![]() |
c0946ddda2 | ||
![]() |
1128cf679c | ||
![]() |
ff65a42350 | ||
![]() |
c459587dcd | ||
![]() |
6f8ea9677f | ||
![]() |
38197527fa | ||
![]() |
21b2bd2c8e |
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 4,
|
"manifest_version": 4,
|
||||||
"type": "extension",
|
"type": "extension",
|
||||||
"name": "LLOneBot v3.26.3",
|
"name": "LLOneBot v3.27.0",
|
||||||
"slug": "LLOneBot",
|
"slug": "LLOneBot",
|
||||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发",
|
||||||
"version": "3.26.3",
|
"version": "3.27.0",
|
||||||
"icon": "./icon.jpg",
|
"icon": "./icon.jpg",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"level": "^8.0.1",
|
"level": "^8.0.1",
|
||||||
"silk-wasm": "^3.3.4",
|
"silk-wasm": "^3.6.0",
|
||||||
"utf-8-validate": "^6.0.3",
|
"utf-8-validate": "^6.0.3",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"ws": "^8.16.0"
|
"ws": "^8.16.0"
|
||||||
|
@@ -43,6 +43,7 @@ export class ConfigUtil {
|
|||||||
enableQOAutoQuote: false
|
enableQOAutoQuote: false
|
||||||
}
|
}
|
||||||
let defaultConfig: Config = {
|
let defaultConfig: Config = {
|
||||||
|
enableLLOB: true,
|
||||||
ob11: ob11Default,
|
ob11: ob11Default,
|
||||||
heartInterval: 60000,
|
heartInterval: 60000,
|
||||||
token: '',
|
token: '',
|
||||||
|
@@ -17,6 +17,7 @@ export interface CheckVersion {
|
|||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
enableLLOB: boolean
|
||||||
ob11: OB11Config
|
ob11: OB11Config
|
||||||
token?: string
|
token?: string
|
||||||
heartInterval?: number // ms
|
heartInterval?: number // ms
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { encode, getDuration, getWavFileInfo, isWav } from 'silk-wasm'
|
|
||||||
import fsPromise from 'fs/promises'
|
import fsPromise from 'fs/promises'
|
||||||
|
import { decode, encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm'
|
||||||
import { log } from './log'
|
import { log } from './log'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { DATA_DIR, TEMP_DIR } from './index'
|
import { TEMP_DIR } from './index'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { getConfigUtil } from '../config'
|
import { getConfigUtil } from '../config'
|
||||||
import { spawn } from 'node:child_process'
|
import { spawn } from 'node:child_process'
|
||||||
@@ -60,10 +60,11 @@ export async function encodeSilk(filePath: string) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const file = await fsPromise.readFile(filePath)
|
||||||
const pttPath = path.join(TEMP_DIR, uuidv4())
|
const pttPath = path.join(TEMP_DIR, uuidv4())
|
||||||
if (getFileHeader(filePath) !== '02232153494c4b') {
|
if (!isSilk(file)) {
|
||||||
log(`语音文件${filePath}需要转换成silk`)
|
log(`语音文件${filePath}需要转换成silk`)
|
||||||
const _isWav = await isWavFile(filePath)
|
const _isWav = isWav(file)
|
||||||
const pcmPath = pttPath + '.pcm'
|
const pcmPath = pttPath + '.pcm'
|
||||||
let sampleRate = 0
|
let sampleRate = 0
|
||||||
const convert = () => {
|
const convert = () => {
|
||||||
@@ -79,7 +80,8 @@ export async function encodeSilk(filePath: string) {
|
|||||||
if (code == null || EXIT_CODES.includes(code)) {
|
if (code == null || EXIT_CODES.includes(code)) {
|
||||||
sampleRate = 24000
|
sampleRate = 24000
|
||||||
const data = fs.readFileSync(pcmPath)
|
const data = fs.readFileSync(pcmPath)
|
||||||
fs.unlink(pcmPath, (err) => {})
|
fs.unlink(pcmPath, (err) => {
|
||||||
|
})
|
||||||
return resolve(data)
|
return resolve(data)
|
||||||
}
|
}
|
||||||
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`)
|
||||||
@@ -91,7 +93,7 @@ export async function encodeSilk(filePath: string) {
|
|||||||
if (!_isWav) {
|
if (!_isWav) {
|
||||||
input = await convert()
|
input = await convert()
|
||||||
} else {
|
} else {
|
||||||
input = fs.readFileSync(filePath)
|
input = file
|
||||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000]
|
||||||
const { fmt } = getWavFileInfo(input)
|
const { fmt } = getWavFileInfo(input)
|
||||||
// log(`wav文件信息`, fmt)
|
// log(`wav文件信息`, fmt)
|
||||||
@@ -108,7 +110,7 @@ export async function encodeSilk(filePath: string) {
|
|||||||
duration: silk.duration / 1000,
|
duration: silk.duration / 1000,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const silk = fs.readFileSync(filePath)
|
const silk = file
|
||||||
let duration = 0
|
let duration = 0
|
||||||
try {
|
try {
|
||||||
duration = getDuration(silk) / 1000
|
duration = getDuration(silk) / 1000
|
||||||
@@ -128,3 +130,41 @@ export async function encodeSilk(filePath: string) {
|
|||||||
return {}
|
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()
|
||||||
|
}
|
@@ -1,30 +1,50 @@
|
|||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
|
import { log } from '@/common/utils/log'
|
||||||
|
|
||||||
export class RequestUtil {
|
export class RequestUtil {
|
||||||
// 适用于获取服务器下发cookies时获取,仅GET
|
// 适用于获取服务器下发cookies时获取,仅GET
|
||||||
static async HttpsGetCookies(url: string): Promise<Map<string, string>> {
|
static async HttpsGetCookies(url: string): Promise<{ [key: string]: string }> {
|
||||||
return new Promise<Map<string, string>>((resolve, reject) => {
|
const client = url.startsWith('https') ? https : http;
|
||||||
const protocol = url.startsWith('https://') ? https : http;
|
return new Promise((resolve, reject) => {
|
||||||
protocol.get(url, (res) => {
|
client.get(url, (res) => {
|
||||||
const cookiesHeader = res.headers['set-cookie'];
|
let cookies: { [key: string]: string } = {};
|
||||||
if (!cookiesHeader) {
|
const handleRedirect = (res: http.IncomingMessage) => {
|
||||||
resolve(new Map<string, string>());
|
//console.log(res.headers.location);
|
||||||
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||||
|
if (res.headers.location) {
|
||||||
|
const redirectUrl = new URL(res.headers.location, url);
|
||||||
|
RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => {
|
||||||
|
// 合并重定向过程中的cookies
|
||||||
|
log('redirectCookies', redirectCookies)
|
||||||
|
cookies = { ...cookies, ...redirectCookies };
|
||||||
|
resolve(cookies);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const cookiesMap = new Map<string, string>();
|
resolve(cookies);
|
||||||
cookiesHeader.forEach((cookieStr) => {
|
}
|
||||||
cookieStr.split(';').forEach((cookiePart) => {
|
} else {
|
||||||
const trimmedPart = cookiePart.trim();
|
resolve(cookies);
|
||||||
if (trimmedPart.includes('=')) {
|
}
|
||||||
const [key, value] = trimmedPart.split('=').map(part => part.trim());
|
};
|
||||||
cookiesMap.set(key, decodeURIComponent(value)); // 解码cookie值
|
res.on('data', () => { }); // Necessary to consume the stream
|
||||||
|
res.on('end', () => {
|
||||||
|
handleRedirect(res);
|
||||||
|
});
|
||||||
|
if (res.headers['set-cookie']) {
|
||||||
|
// console.log(res.headers['set-cookie']);
|
||||||
|
log('set-cookie', url, res.headers['set-cookie']);
|
||||||
|
res.headers['set-cookie'].forEach((cookie) => {
|
||||||
|
const parts = cookie.split(';')[0].split('=');
|
||||||
|
const key = parts[0];
|
||||||
|
const value = parts[1];
|
||||||
|
if (key && value && key.length > 0 && value.length > 0) {
|
||||||
|
cookies[key] = value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
resolve(cookiesMap);
|
|
||||||
}
|
}
|
||||||
}).on('error', (error) => {
|
}).on('error', (err) => {
|
||||||
reject(error);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
CHANNEL_UPDATE,
|
CHANNEL_UPDATE,
|
||||||
} from '../common/channels'
|
} from '../common/channels'
|
||||||
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
|
import { ob11WebsocketServer } from '../onebot11/server/ws/WebsocketServer'
|
||||||
import { DATA_DIR } from '../common/utils'
|
import { DATA_DIR, qqPkgInfo } from '../common/utils'
|
||||||
import {
|
import {
|
||||||
friendRequests,
|
friendRequests,
|
||||||
getFriend,
|
getFriend,
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
selfInfo,
|
selfInfo,
|
||||||
uidMaps,
|
uidMaps,
|
||||||
} from '../common/data'
|
} 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 { OB11Constructor } from '../onebot11/constructor'
|
||||||
import {
|
import {
|
||||||
ChatType,
|
ChatType,
|
||||||
@@ -56,6 +56,7 @@ import { getConfigUtil } from '../common/config'
|
|||||||
import { checkFfmpeg } from '../common/utils/video'
|
import { checkFfmpeg } from '../common/utils/video'
|
||||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../onebot11/event/notice/OB11GroupDecreaseEvent'
|
||||||
import '../ntqqapi/native/wrapper'
|
import '../ntqqapi/native/wrapper'
|
||||||
|
import { sentMessages } from '@/ntqqapi/api'
|
||||||
|
|
||||||
let running = false
|
let running = false
|
||||||
|
|
||||||
@@ -200,7 +201,12 @@ function onLoad() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startReceiveHook() {
|
async function startReceiveHook() {
|
||||||
|
startHook().then()
|
||||||
if (getConfigUtil().getConfig().enablePoke) {
|
if (getConfigUtil().getConfig().enablePoke) {
|
||||||
|
if ( qqPkgInfo.buildVersion > '23873'){
|
||||||
|
log(`当前版本${qqPkgInfo.buildVersion}不支持发送戳一戳模块`)
|
||||||
|
return
|
||||||
|
}
|
||||||
crychic.loadNode()
|
crychic.loadNode()
|
||||||
crychic.registerPokeHandler((id, isGroup) => {
|
crychic.registerPokeHandler((id, isGroup) => {
|
||||||
log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`)
|
log(`收到戳一戳消息了!是否群聊:${isGroup},id:${id}`)
|
||||||
@@ -226,6 +232,10 @@ function onLoad() {
|
|||||||
const recallMsgIds: string[] = [] // 避免重复上报
|
const recallMsgIds: string[] = [] // 避免重复上报
|
||||||
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
|
||||||
for (const message of payload.msgList) {
|
for (const message of payload.msgList) {
|
||||||
|
const sentMessage = sentMessages[message.msgId]
|
||||||
|
if (sentMessage){
|
||||||
|
Object.assign(sentMessage, message)
|
||||||
|
}
|
||||||
log('message update', message.msgId, message)
|
log('message update', message.msgId, message)
|
||||||
if (message.recallTime != '0') {
|
if (message.recallTime != '0') {
|
||||||
if (recallMsgIds.includes(message.msgId)) {
|
if (recallMsgIds.includes(message.msgId)) {
|
||||||
@@ -429,6 +439,11 @@ function onLoad() {
|
|||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
log('llonebot pid', process.pid)
|
log('llonebot pid', process.pid)
|
||||||
|
const config = getConfigUtil().getConfig()
|
||||||
|
if (!config.enableLLOB){
|
||||||
|
log('LLOneBot 开关设置为关闭,不启动LLOneBot')
|
||||||
|
return
|
||||||
|
}
|
||||||
llonebotError.otherError = ''
|
llonebotError.otherError = ''
|
||||||
startTime = Date.now()
|
startTime = Date.now()
|
||||||
dbUtil.getReceivedTempUinMap().then((m) => {
|
dbUtil.getReceivedTempUinMap().then((m) => {
|
||||||
@@ -436,15 +451,33 @@ function onLoad() {
|
|||||||
uidMaps[value] = key
|
uidMaps[value] = key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
startReceiveHook().then()
|
try{
|
||||||
NTQQGroupApi.getGroups(true).then(groups=> {
|
log('start get groups')
|
||||||
for (let group of 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(log)
|
catch (e) {
|
||||||
|
log('获取群列表失败', e)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
log('start activate group member info')
|
||||||
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
|
NTQQGroupApi.activateMemberInfoChange().then().catch(log)
|
||||||
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
NTQQGroupApi.activateMemberListChange().then().catch(log)
|
||||||
const config = getConfigUtil().getConfig()
|
startReceiveHook().then()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (config.ob11.enableHttp) {
|
if (config.ob11.enableHttp) {
|
||||||
ob11HTTPServer.start(config.ob11.httpPort)
|
ob11HTTPServer.start(config.ob11.httpPort)
|
||||||
}
|
}
|
||||||
|
@@ -10,15 +10,23 @@ import {
|
|||||||
ElementType,
|
ElementType,
|
||||||
IMAGE_HTTP_HOST,
|
IMAGE_HTTP_HOST,
|
||||||
IMAGE_HTTP_HOST_NT, PicElement,
|
IMAGE_HTTP_HOST_NT, PicElement,
|
||||||
RawMessage,
|
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { ReceiveCmdS } from '../hook'
|
import { ReceiveCmdS } from '../hook'
|
||||||
import { log } from '@/common/utils'
|
import { log } from '@/common/utils'
|
||||||
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
||||||
|
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
||||||
|
import { Peer } from '@/ntqqapi/api/msg'
|
||||||
|
|
||||||
export class NTQQFileApi {
|
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) {
|
static async getFileType(filePath: string) {
|
||||||
return await callNTQQApi<{ ext: string }>({
|
return await callNTQQApi<{ ext: string }>({
|
||||||
className: NTQQApiClass.FS_API,
|
className: NTQQApiClass.FS_API,
|
||||||
|
@@ -35,14 +35,20 @@ export class NTQQGroupApi {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
static async getGroups(forced = false) {
|
static async getGroups(forced = false) {
|
||||||
let cbCmd = ReceiveCmdS.GROUPS
|
// let cbCmd = ReceiveCmdS.GROUPS
|
||||||
if (process.platform != 'win32') {
|
// if (process.platform != 'win32') {
|
||||||
cbCmd = ReceiveCmdS.GROUPS_STORE
|
// cbCmd = ReceiveCmdS.GROUPS_STORE
|
||||||
}
|
// }
|
||||||
const result = await callNTQQApi<{
|
const result = await callNTQQApi<{
|
||||||
updateType: number
|
updateType: number
|
||||||
groupList: Group[]
|
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
|
return result.groupList
|
||||||
}
|
}
|
||||||
static async getGroupMembers(groupQQ: string, num = 3000): Promise<GroupMember[]> {
|
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 { sleep } from '../../common/utils/helper'
|
||||||
import { isQQ998 } from '../../common/utils'
|
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 {
|
export interface Peer {
|
||||||
chatType: ChatType
|
chatType: ChatType
|
||||||
@@ -40,17 +42,20 @@ async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 100
|
|||||||
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
|
||||||
delete sendMessagePool[peerUid]
|
delete sendMessagePool[peerUid]
|
||||||
sentMessage = rawMessage
|
sentMessage = rawMessage
|
||||||
|
sentMessages[rawMessage.msgId] = rawMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkSendCompleteUsingTime = 0
|
let checkSendCompleteUsingTime = 0
|
||||||
const checkSendComplete = async (): Promise<RawMessage> => {
|
const checkSendComplete = async (): Promise<RawMessage> => {
|
||||||
if (sentMessage) {
|
if (sentMessage) {
|
||||||
if (waitComplete) {
|
if (waitComplete) {
|
||||||
if ((await dbUtil.getMsgByLongId(sentMessage.msgId)).sendStatus == 2) {
|
if (sentMessage.sendStatus == 2) {
|
||||||
|
delete sentMessages[sentMessage.msgId]
|
||||||
return sentMessage
|
return sentMessage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
delete sentMessages[sentMessage.msgId]
|
||||||
return sentMessage
|
return sentMessage
|
||||||
}
|
}
|
||||||
// log(`给${peerUid}发送消息成功`)
|
// log(`给${peerUid}发送消息成功`)
|
||||||
|
@@ -6,6 +6,7 @@ import { NTQQWindowApi, NTQQWindows } from './window'
|
|||||||
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
|
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
|
||||||
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
||||||
import * as https from 'https'
|
import * as https from 'https'
|
||||||
|
import { RequestUtil } from '@/common/utils/request'
|
||||||
|
|
||||||
let userInfoCache: Record<string, User> = {} // uid: User
|
let userInfoCache: Record<string, User> = {} // uid: User
|
||||||
|
|
||||||
@@ -98,7 +99,17 @@ export class NTQQUserApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
static async getQzoneCookies() {
|
||||||
|
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin + '&clientkey=' + (await this.getClientKey()).clientKey + '&u1=https%3A%2F%2Fuser.qzone.qq.com%2F' + selfInfo.uin + '%2Finfocenter&keyindex=19%27'
|
||||||
|
let cookies: { [key: string]: string; } = {};
|
||||||
|
try {
|
||||||
|
cookies = await RequestUtil.HttpsGetCookies(requestUrl);
|
||||||
|
} catch (e: any) {
|
||||||
|
log('获取QZone Cookies失败', e)
|
||||||
|
cookies = {}
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
static async getSkey(): Promise<string> {
|
static async getSkey(): Promise<string> {
|
||||||
const clientKeyData = await this.getClientKey()
|
const clientKeyData = await this.getClientKey()
|
||||||
if (clientKeyData.result !== 0) {
|
if (clientKeyData.result !== 0) {
|
||||||
@@ -106,31 +117,17 @@ export class NTQQUserApi {
|
|||||||
}
|
}
|
||||||
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
|
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
|
||||||
+ '&clientkey=' + clientKeyData.clientKey
|
+ '&clientkey=' + clientKeyData.clientKey
|
||||||
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
|
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex;
|
||||||
|
return (await RequestUtil.HttpsGetCookies(url))?.skey;
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const req = https.get(url, (res) => {
|
|
||||||
const rawCookies = res.headers['set-cookie']
|
|
||||||
const cookies = {}
|
|
||||||
rawCookies.forEach(cookie => {
|
|
||||||
// 使用正则表达式匹配 cookie 名称和值
|
|
||||||
const regex = /([^=;]+)=([^;]*)/
|
|
||||||
const match = regex.exec(cookie)
|
|
||||||
if (match) {
|
|
||||||
cookies[match[1].trim()] = match[2].trim()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
resolve(cookies['skey'])
|
|
||||||
})
|
|
||||||
req.on('error', e => {
|
|
||||||
reject(e)
|
|
||||||
});
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@cacheFunc(60 * 30 * 1000)
|
@cacheFunc(60 * 30 * 1000)
|
||||||
static async getCookies(domain: string) {
|
static async getCookies(domain: string) {
|
||||||
|
if (domain.endsWith("qzone.qq.com")) {
|
||||||
|
let data = (await NTQQUserApi.getQzoneCookies());
|
||||||
|
const CookieValue = 'p_skey=' + data.p_skey + '; skey=' + data.skey + '; p_uin=o' + selfInfo.uin + '; uin=o' + selfInfo.uin;
|
||||||
|
return { bkn: NTQQUserApi.genBkn(data.p_skey), cookies: CookieValue };
|
||||||
|
}
|
||||||
const skey = await this.getSkey();
|
const skey = await this.getSkey();
|
||||||
const pskey = (await this.getPSkey([domain])).get(domain);
|
const pskey = (await this.getPSkey([domain])).get(domain);
|
||||||
if (!pskey || !skey) {
|
if (!pskey || !skey) {
|
||||||
|
@@ -22,6 +22,7 @@ import { NTQQGroupApi } from './api/group'
|
|||||||
import { log } from '@/common/utils'
|
import { log } from '@/common/utils'
|
||||||
import { isNumeric, sleep } from '@/common/utils'
|
import { isNumeric, sleep } from '@/common/utils'
|
||||||
import { OB11Constructor } from '../onebot11/constructor'
|
import { OB11Constructor } from '../onebot11/constructor'
|
||||||
|
import { OB11GroupCardEvent } from '../onebot11/event/notice/OB11GroupCardEvent'
|
||||||
|
|
||||||
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
|
||||||
|
|
||||||
@@ -324,13 +325,16 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function startHook() {
|
||||||
|
|
||||||
// 群列表变动
|
// 群列表变动
|
||||||
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROUPS, (payload) => {
|
||||||
// updateType 3是群列表变动,2是群成员变动
|
// updateType 3是群列表变动,2是群成员变动
|
||||||
// log("群列表变动", payload.updateType, payload.groupList)
|
// log("群列表变动", payload.updateType, payload.groupList)
|
||||||
if (payload.updateType != 2) {
|
if (payload.updateType != 2) {
|
||||||
updateGroups(payload.groupList).then()
|
updateGroups(payload.groupList).then()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (process.platform == 'win32') {
|
if (process.platform == 'win32') {
|
||||||
processGroupEvent(payload).then()
|
processGroupEvent(payload).then()
|
||||||
}
|
}
|
||||||
@@ -341,7 +345,8 @@ registerReceiveHook<{ groupList: Group[]; updateType: number }>(ReceiveCmdS.GROU
|
|||||||
// log("群列表变动", payload.updateType, payload.groupList)
|
// log("群列表变动", payload.updateType, payload.groupList)
|
||||||
if (payload.updateType != 2) {
|
if (payload.updateType != 2) {
|
||||||
updateGroups(payload.groupList).then()
|
updateGroups(payload.groupList).then()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (process.platform != 'win32') {
|
if (process.platform != 'win32') {
|
||||||
processGroupEvent(payload).then()
|
processGroupEvent(payload).then()
|
||||||
}
|
}
|
||||||
@@ -359,6 +364,12 @@ registerReceiveHook<{
|
|||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
const existMember = await getGroupMember(groupCode, member.uin)
|
const existMember = await getGroupMember(groupCode, member.uin)
|
||||||
if (existMember) {
|
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)
|
Object.assign(existMember, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,7 +402,8 @@ registerReceiveHook<{
|
|||||||
let existFriend = friends.find((f) => f.uin == friend.uin)
|
let existFriend = friends.find((f) => f.uin == friend.uin)
|
||||||
if (!existFriend) {
|
if (!existFriend) {
|
||||||
friends.push(friend)
|
friends.push(friend)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Object.assign(existFriend, friend)
|
Object.assign(existFriend, friend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,7 +515,8 @@ registerReceiveHook<{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
NTQQMsgApi.activateChat(peer).then()
|
NTQQMsgApi.activateChat(peer).then()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,7 +529,8 @@ registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
|
|||||||
let chatType = ChatType.friend
|
let chatType = ChatType.friend
|
||||||
if (isNumeric(peerUid)) {
|
if (isNumeric(peerUid)) {
|
||||||
chatType = ChatType.group
|
chatType = ChatType.group
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 检查是否好友
|
// 检查是否好友
|
||||||
if (!(await getFriend(peerUid))) {
|
if (!(await getFriend(peerUid))) {
|
||||||
chatType = ChatType.temp
|
chatType = ChatType.temp
|
||||||
@@ -528,3 +542,5 @@ registerCallHook(NTQQApiMethod.DELETE_ACTIVE_CHAT, async (payload) => {
|
|||||||
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
|
log('重新激活聊天窗口', peer, { result: r.result, errMsg: r.errMsg })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
}
|
@@ -107,7 +107,7 @@ interface NTQQApiParams {
|
|||||||
channel?: NTQQApiChannel
|
channel?: NTQQApiChannel
|
||||||
classNameIsRegister?: boolean
|
classNameIsRegister?: boolean
|
||||||
args?: unknown[]
|
args?: unknown[]
|
||||||
cbCmd?: ReceiveCmd | null
|
cbCmd?: ReceiveCmd | ReceiveCmd[] | null
|
||||||
cmdCB?: (payload: any) => boolean
|
cmdCB?: (payload: any) => boolean
|
||||||
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
|
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd
|
||||||
timeoutSecond?: number
|
timeoutSecond?: number
|
||||||
@@ -147,7 +147,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
success = true
|
success = true
|
||||||
resolve(r)
|
resolve(r)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据
|
// 这里的callback比较特殊,QQ后端先返回是否调用成功,再返回一条结果数据
|
||||||
const secondCallback = () => {
|
const secondCallback = () => {
|
||||||
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
|
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
|
||||||
@@ -158,7 +159,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
success = true
|
success = true
|
||||||
resolve(payload)
|
resolve(payload)
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
removeReceiveHook(hookId)
|
removeReceiveHook(hookId)
|
||||||
success = true
|
success = true
|
||||||
resolve(payload)
|
resolve(payload)
|
||||||
@@ -170,7 +172,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
log(`${methodName} callback`, result)
|
log(`${methodName} callback`, result)
|
||||||
if (result?.result == 0 || result === undefined) {
|
if (result?.result == 0 || result === undefined) {
|
||||||
afterFirstCmd && secondCallback()
|
afterFirstCmd && secondCallback()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
success = true
|
success = true
|
||||||
reject(`ntqq api call failed, ${result.errMsg}`)
|
reject(`ntqq api call failed, ${result.errMsg}`)
|
||||||
}
|
}
|
||||||
@@ -188,7 +191,8 @@ export function callNTQQApi<ReturnType>(params: NTQQApiParams) {
|
|||||||
channel,
|
channel,
|
||||||
{
|
{
|
||||||
sender: {
|
sender: {
|
||||||
send: (..._args: unknown[]) => {},
|
send: (..._args: unknown[]) => {
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ type: 'request', callbackId: uuid, eventName },
|
{ type: 'request', callbackId: uuid, eventName },
|
||||||
|
@@ -188,6 +188,7 @@ export const IMAGE_HTTP_HOST = 'https://gchat.qpic.cn'
|
|||||||
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
export const IMAGE_HTTP_HOST_NT = 'https://multimedia.nt.qq.com.cn'
|
||||||
|
|
||||||
export interface PicElement {
|
export interface PicElement {
|
||||||
|
picSubType: PicSubType
|
||||||
picType: PicType // 有这玩意儿吗
|
picType: PicType // 有这玩意儿吗
|
||||||
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
originImageUrl: string // http url, 没有host,host是https://gchat.qpic.cn/, 带download参数的是https://multimedia.nt.qq.com.cn
|
||||||
originImageMd5?: string
|
originImageMd5?: string
|
||||||
@@ -226,6 +227,7 @@ export interface GrayTipElement {
|
|||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
jsonGrayTipElement: {
|
jsonGrayTipElement: {
|
||||||
|
busiId: number
|
||||||
jsonStr: string
|
jsonStr: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import BaseAction from '../BaseAction'
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import { dbUtil } from '@/common/db'
|
import { dbUtil } from '@/common/db'
|
||||||
import { getConfigUtil } from '@/common/config'
|
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 { NTQQFileApi } from '@/ntqqapi/api'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
import { FileElement, RawMessage, VideoElement } from '@/ntqqapi/types'
|
||||||
@@ -38,20 +38,21 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
|
|||||||
log('找到了文件 element', element)
|
log('找到了文件 element', element)
|
||||||
// 构建下载函数
|
// 构建下载函数
|
||||||
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, cache.elementId, '', '', true)
|
||||||
await sleep(1000) // 这里延时是为何?
|
// 等待文件下载完成
|
||||||
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
msg = await dbUtil.getMsgByLongId(cache.msgId)
|
||||||
log('下载完成后的msg', msg)
|
log('下载完成后的msg', msg)
|
||||||
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
cache.filePath = this.getElement(msg, cache.elementId).filePath
|
||||||
|
await checkFileReceived(cache.filePath, 10 * 1000)
|
||||||
dbUtil.addFileCache(file, cache).then()
|
dbUtil.addFileCache(file, cache).then()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
protected async _handle(payload: GetFilePayload): Promise<GetFileResponse> {
|
||||||
const cache = await dbUtil.getFileCache(payload.file)
|
let cache = await dbUtil.getFileCache(payload.file)
|
||||||
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
throw new Error('file not found')
|
throw new Error('file not found')
|
||||||
}
|
}
|
||||||
|
const { autoDeleteFile, enableLocalFile2Url, autoDeleteFileSecond } = getConfigUtil().getConfig()
|
||||||
if (cache.downloadFunc) {
|
if (cache.downloadFunc) {
|
||||||
await cache.downloadFunc()
|
await cache.downloadFunc()
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile'
|
||||||
import { ActionName } from '../types'
|
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 {
|
interface Payload extends GetFilePayload {
|
||||||
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
out_format: 'mp3' | 'amr' | 'wma' | 'm4a' | 'spx' | 'ogg' | 'wav' | 'flac'
|
||||||
@@ -9,7 +13,13 @@ export default class GetRecord extends GetFileBase {
|
|||||||
actionName = ActionName.GetRecord
|
actionName = ActionName.GetRecord
|
||||||
|
|
||||||
protected async _handle(payload: Payload): Promise<GetFileResponse> {
|
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
|
return res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,7 @@ import GetFile from './file/GetFile'
|
|||||||
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
|
import { GoCQHTTGetForwardMsgAction } from './go-cqhttp/GetForwardMsg'
|
||||||
import { GetCookies } from './user/GetCookie'
|
import { GetCookies } from './user/GetCookie'
|
||||||
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
|
import { SetMsgEmojiLike } from './msg/SetMsgEmojiLike'
|
||||||
import { ForwardFriendSingleMsg, ForwardSingleGroupMsg } from './msg/ForwardSingleMsg'
|
import { ForwardFriendSingleMsg, ForwardGroupSingleMsg } from './msg/ForwardSingleMsg'
|
||||||
import { GetGroupEssence } from './group/GetGroupEssence'
|
import { GetGroupEssence } from './group/GetGroupEssence'
|
||||||
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
|
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
|
||||||
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation'
|
import { GoCQHTTHandleQuickOperation } from './go-cqhttp/QuickOperation'
|
||||||
@@ -91,7 +91,7 @@ export const actionHandlers = [
|
|||||||
new GetCookies(),
|
new GetCookies(),
|
||||||
new SetMsgEmojiLike(),
|
new SetMsgEmojiLike(),
|
||||||
new ForwardFriendSingleMsg(),
|
new ForwardFriendSingleMsg(),
|
||||||
new ForwardSingleGroupMsg(),
|
new ForwardGroupSingleMsg(),
|
||||||
//以下为go-cqhttp api
|
//以下为go-cqhttp api
|
||||||
new GetGroupEssence(),
|
new GetGroupEssence(),
|
||||||
new GetGroupHonorInfo(),
|
new GetGroupHonorInfo(),
|
||||||
|
@@ -43,6 +43,6 @@ export class ForwardFriendSingleMsg extends ForwardSingleMsg {
|
|||||||
actionName = ActionName.ForwardFriendSingleMsg
|
actionName = ActionName.ForwardFriendSingleMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ForwardSingleGroupMsg extends ForwardSingleMsg {
|
export class ForwardGroupSingleMsg extends ForwardSingleMsg {
|
||||||
actionName = ActionName.ForwardGroupSingleMsg
|
actionName = ActionName.ForwardGroupSingleMsg
|
||||||
}
|
}
|
||||||
|
@@ -327,7 +327,7 @@ export async function sendMsg(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
log('发送消息总大小', totalSize, 'bytes')
|
log('发送消息总大小', totalSize, 'bytes')
|
||||||
let timeout = ((totalSize / 1024 / 512) * 1000) + 5000 // 512kb/s
|
let timeout = ((totalSize / 1024 / 100) * 1000) + 5000 // 100kb/s
|
||||||
log('设置消息超时时间', timeout)
|
log('设置消息超时时间', timeout)
|
||||||
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout)
|
const returnMsg = await NTQQMsgApi.sendMsg(peer, sendElements, waitComplete, timeout)
|
||||||
log('消息发送结果', returnMsg)
|
log('消息发送结果', returnMsg)
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import BaseAction from '../BaseAction'
|
import BaseAction from '../BaseAction'
|
||||||
import { NTQQUserApi } from '../../../ntqqapi/api'
|
import { NTQQUserApi } from '@/ntqqapi/api'
|
||||||
import { groups } from '../../../common/data'
|
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
|
@@ -24,7 +24,7 @@ import {
|
|||||||
User,
|
User,
|
||||||
VideoElement,
|
VideoElement,
|
||||||
} from '../ntqqapi/types'
|
} from '../ntqqapi/types'
|
||||||
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap } from '../common/data'
|
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap, uidMaps } from '../common/data'
|
||||||
import { EventType } from './event/OB11BaseEvent'
|
import { EventType } from './event/OB11BaseEvent'
|
||||||
import { encodeCQCode } from './cqcode'
|
import { encodeCQCode } from './cqcode'
|
||||||
import { dbUtil } from '../common/db'
|
import { dbUtil } from '../common/db'
|
||||||
@@ -47,6 +47,7 @@ import { mFaceCache } from '../ntqqapi/constructor'
|
|||||||
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
||||||
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
|
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
|
||||||
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
|
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
|
||||||
|
import { OB11GroupPokeEvent } from './event/notice/OB11PokeEvent'
|
||||||
|
|
||||||
let lastRKeyUpdateTime = 0
|
let lastRKeyUpdateTime = 0
|
||||||
|
|
||||||
@@ -161,10 +162,12 @@ export class OB11Constructor {
|
|||||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||||
let fileName = element.picElement.fileName
|
let fileName = element.picElement.fileName
|
||||||
const sourcePath = element.picElement.sourcePath
|
const sourcePath = element.picElement.sourcePath
|
||||||
if (element.picElement.picType === PicType.gif && !fileName.endsWith('.gif')) {
|
const isGif = element.picElement.picType === PicType.gif
|
||||||
|
if (isGif && !fileName.endsWith('.gif')) {
|
||||||
fileName += '.gif'
|
fileName += '.gif'
|
||||||
}
|
}
|
||||||
message_data['data']['file'] = fileName
|
message_data['data']['file'] = fileName
|
||||||
|
message_data['data']['subType'] = element.picElement.picSubType
|
||||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||||
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
||||||
|
|
||||||
@@ -188,9 +191,7 @@ export class OB11Constructor {
|
|||||||
element.picElement.sourcePath,
|
element.picElement.sourcePath,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
}).then()
|
||||||
.then()
|
|
||||||
// 不在自动下载图片
|
|
||||||
}
|
}
|
||||||
else if (element.videoElement || element.fileElement) {
|
else if (element.videoElement || element.fileElement) {
|
||||||
const videoOrFileElement = element.videoElement || element.fileElement
|
const videoOrFileElement = element.videoElement || element.fileElement
|
||||||
@@ -200,6 +201,13 @@ export class OB11Constructor {
|
|||||||
message_data['data']['path'] = videoOrFileElement.filePath
|
message_data['data']['path'] = videoOrFileElement.filePath
|
||||||
message_data['data']['file_id'] = videoOrFileElement.fileUuid
|
message_data['data']['file_id'] = videoOrFileElement.fileUuid
|
||||||
message_data['data']['file_size'] = videoOrFileElement.fileSize
|
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
|
dbUtil
|
||||||
.addFileCache(videoOrFileElement.fileUuid, {
|
.addFileCache(videoOrFileElement.fileUuid, {
|
||||||
msgId: msg.msgId,
|
msgId: msg.msgId,
|
||||||
@@ -290,7 +298,7 @@ export class OB11Constructor {
|
|||||||
if (message_data.type !== 'unknown' && message_data.data) {
|
if (message_data.type !== 'unknown' && message_data.data) {
|
||||||
const cqCode = encodeCQCode(message_data)
|
const cqCode = encodeCQCode(message_data)
|
||||||
if (messagePostFormat === 'string') {
|
if (messagePostFormat === 'string') {
|
||||||
;(resMsg.message as string) += cqCode
|
(resMsg.message as string) += cqCode
|
||||||
}
|
}
|
||||||
else (resMsg.message as OB11MessageData[]).push(message_data)
|
else (resMsg.message as OB11MessageData[]).push(message_data)
|
||||||
|
|
||||||
@@ -485,6 +493,16 @@ export class OB11Constructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
* */
|
* */
|
||||||
|
if (grayTipElement.jsonGrayTipElement.busiId == 1061) {
|
||||||
|
//判断业务类型
|
||||||
|
//Poke事件
|
||||||
|
let pokedetail: any[] = json.items;
|
||||||
|
//筛选item带有uid的元素
|
||||||
|
pokedetail = pokedetail.filter(item => item.uid);
|
||||||
|
if (pokedetail.length == 2) {
|
||||||
|
return new OB11GroupPokeEvent(parseInt(msg.peerUid), parseInt((uidMaps[pokedetail[0].uid])!), parseInt((uidMaps[pokedetail[1].uid])));
|
||||||
|
}
|
||||||
|
}
|
||||||
const memberUin = json.items[1].param[0]
|
const memberUin = json.items[1].param[0]
|
||||||
const title = json.items[3].txt
|
const title = json.items[3].txt
|
||||||
log('收到群成员新头衔消息', json)
|
log('收到群成员新头衔消息', json)
|
||||||
|
@@ -50,6 +50,7 @@ export function encodeCQCode(data: OB11MessageData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CQCodeEscape = (text: string) => {
|
const CQCodeEscape = (text: string) => {
|
||||||
|
text = text.toString()
|
||||||
return text.replace(/\&/g, '&').replace(/\[/g, '[').replace(/\]/g, ']').replace(/,/g, ',')
|
return text.replace(/\&/g, '&').replace(/\[/g, '[').replace(/\]/g, ']').replace(/,/g, ',')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,10 +21,10 @@ export class OB11FriendPokeEvent extends OB11PokeEvent {
|
|||||||
export class OB11GroupPokeEvent extends OB11PokeEvent {
|
export class OB11GroupPokeEvent extends OB11PokeEvent {
|
||||||
group_id: number
|
group_id: number
|
||||||
|
|
||||||
constructor(group_id: number, user_id: number = 0) {
|
constructor(group_id: number, user_id: number = 0, target_id: number = 0) {
|
||||||
super()
|
super()
|
||||||
this.group_id = group_id
|
this.group_id = group_id
|
||||||
this.target_id = user_id
|
this.target_id = target_id
|
||||||
this.user_id = user_id
|
this.user_id = user_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import { WebSocket as WebSocketClass } from 'ws'
|
|||||||
import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent'
|
import { OB11HeartbeatEvent } from '../../event/meta/OB11HeartbeatEvent'
|
||||||
import { log } from '../../../common/utils/log'
|
import { log } from '../../../common/utils/log'
|
||||||
import { getConfigUtil } from '../../../common/config'
|
import { getConfigUtil } from '../../../common/config'
|
||||||
|
import { version } from '../../../version'
|
||||||
|
|
||||||
export let rwsList: ReverseWebsocket[] = []
|
export let rwsList: ReverseWebsocket[] = []
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ export class ReverseWebsocket {
|
|||||||
'X-Self-ID': selfInfo.uin,
|
'X-Self-ID': selfInfo.uin,
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段
|
'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段
|
||||||
|
'User-Agent': `LLOneBot/${version}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
registerWsEventSender(this.websocket)
|
registerWsEventSender(this.websocket)
|
||||||
|
@@ -27,6 +27,7 @@ class OB11WebsocketServer extends WebsocketServerBase {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let handleResult = await action.websocketHandle(params, echo)
|
let handleResult = await action.websocketHandle(params, echo)
|
||||||
|
handleResult.echo = echo
|
||||||
wsReply(wsClient, handleResult)
|
wsReply(wsClient, handleResult)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo))
|
wsReply(wsClient, OB11Response.error(`api处理出错:${e.stack}`, 1200, echo))
|
||||||
|
@@ -62,6 +62,13 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
SettingButton('请稍候', 'llonebot-update-button', 'secondary'),
|
SettingButton('请稍候', 'llonebot-update-button', 'secondary'),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
SettingList([
|
||||||
|
SettingItem(
|
||||||
|
'是否启用 LLOneBot, 重启QQ后生效',
|
||||||
|
null,
|
||||||
|
SettingSwitch('enableLLOB', config.enableLLOB, { 'control-display-id': 'config-enableLLOB' }),
|
||||||
|
)]
|
||||||
|
),
|
||||||
SettingList([
|
SettingList([
|
||||||
SettingItem(
|
SettingItem(
|
||||||
'启用 HTTP 服务',
|
'启用 HTTP 服务',
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const version = '3.26.3'
|
export const version = '3.27.0'
|
||||||
|
Reference in New Issue
Block a user