mirror of
https://github.com/LLOneBot/LLOneBot.git
synced 2024-11-22 01:56:33 +00:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bd212c4bf3 | ||
![]() |
32c7f904db | ||
![]() |
2ef017282f | ||
![]() |
9672f67a23 | ||
![]() |
6e5cfd827c | ||
![]() |
5402bef4a9 | ||
![]() |
4194512cce | ||
![]() |
b3aad8b0d9 | ||
![]() |
1489c6df25 | ||
![]() |
2e225045e6 | ||
![]() |
11ed06148c | ||
![]() |
a3fc018186 | ||
![]() |
9692bf6ec6 | ||
![]() |
9b3916307a | ||
![]() |
fdf96b479c | ||
![]() |
25c7a6096d | ||
![]() |
627955e7fd | ||
![]() |
43e9b070a9 | ||
![]() |
78bb36a2bb | ||
![]() |
58e6e3cbda | ||
![]() |
1da086ce0a | ||
![]() |
e9d43a9449 | ||
![]() |
ce31052661 | ||
![]() |
3fd9b0a183 | ||
![]() |
7e1dee8e07 | ||
![]() |
f2854fdf00 | ||
![]() |
1fad95a55b | ||
![]() |
5342e1521c | ||
![]() |
3c532526df | ||
![]() |
05c6cae86f |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,4 +3,4 @@ package-lock.json
|
|||||||
dist/
|
dist/
|
||||||
out/
|
out/
|
||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import cp from 'vite-plugin-cp'
|
import cp from 'vite-plugin-cp'
|
||||||
import './scripts/gen-version'
|
import './scripts/gen-version'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
const external = [
|
const external = [
|
||||||
'silk-wasm',
|
'silk-wasm',
|
||||||
@@ -34,6 +35,7 @@ let config = {
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -43,9 +45,9 @@ let config = {
|
|||||||
...external.map(genCpModule),
|
...external.map(genCpModule),
|
||||||
{ src: './manifest.json', dest: 'dist' },
|
{ src: './manifest.json', dest: 'dist' },
|
||||||
{ src: './icon.jpg', dest: 'dist' },
|
{ src: './icon.jpg', dest: 'dist' },
|
||||||
{ src: './src/ntqqapi/external/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
{ src: './src/ntqqapi/native/crychic/crychic-win32-x64.node', dest: 'dist/main/' },
|
||||||
{ src: './src/ntqqapi/external/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
// { src: './src/ntqqapi/native/moehook/MoeHoo-win32-x64.node', dest: 'dist/main/' },
|
||||||
{ src: './src/ntqqapi/external/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
// { src: './src/ntqqapi/native/moehook/MoeHoo-linux-x64.node', dest: 'dist/main/' },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 4,
|
"manifest_version": 4,
|
||||||
"type": "extension",
|
"type": "extension",
|
||||||
"name": "LLOneBot v3.24.0",
|
"name": "LLOneBot v3.24.4",
|
||||||
"slug": "LLOneBot",
|
"slug": "LLOneBot",
|
||||||
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
"description": "使你的NTQQ支持OneBot11协议进行QQ机器人开发, 不支持商店在线更新",
|
||||||
"version": "3.24.0",
|
"version": "3.24.4",
|
||||||
"icon": "./icon.jpg",
|
"icon": "./icon.jpg",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -10,7 +10,8 @@
|
|||||||
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/",
|
"deploy-mac": "cp -r dist/* ~/Library/Containers/com.tencent.qq/Data/LiteLoaderQQNT/plugins/LLOneBot/",
|
||||||
"build-win": "npm run build && npm run deploy-win",
|
"build-win": "npm run build && npm run deploy-win",
|
||||||
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"",
|
"deploy-win": "cmd /c \"xcopy /C /S /Y dist\\* %USERPROFILE%\\documents\\LiteLoaderQQNT\\plugins\\LLOneBot\\\"",
|
||||||
"format": "prettier -cw ."
|
"format": "prettier -cw .",
|
||||||
|
"check": "tsc"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@@ -82,9 +82,12 @@ export class ConfigUtil {
|
|||||||
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')
|
fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkOldConfig(currentConfig: Config | OB11Config,
|
private checkOldConfig(
|
||||||
oldConfig: Config | OB11Config,
|
currentConfig: Config | OB11Config,
|
||||||
currentKey: string, oldKey: string) {
|
oldConfig: Config | OB11Config,
|
||||||
|
currentKey: string,
|
||||||
|
oldKey: string,
|
||||||
|
) {
|
||||||
// 迁移旧的配置到新配置,避免用户重新填写配置
|
// 迁移旧的配置到新配置,避免用户重新填写配置
|
||||||
const oldValue = oldConfig[oldKey]
|
const oldValue = oldConfig[oldKey]
|
||||||
if (oldValue) {
|
if (oldValue) {
|
||||||
|
@@ -4,6 +4,7 @@ import { NTQQGroupApi } from '../ntqqapi/api/group'
|
|||||||
import { log } from './utils/log'
|
import { log } from './utils/log'
|
||||||
import { isNumeric } from './utils/helper'
|
import { isNumeric } from './utils/helper'
|
||||||
import { NTQQFriendApi } from '../ntqqapi/api'
|
import { NTQQFriendApi } from '../ntqqapi/api'
|
||||||
|
import { WebApiGroupMember } from '@/ntqqapi/api/webapi'
|
||||||
|
|
||||||
export const selfInfo: SelfInfo = {
|
export const selfInfo: SelfInfo = {
|
||||||
uid: '',
|
uid: '',
|
||||||
@@ -11,6 +12,10 @@ export const selfInfo: SelfInfo = {
|
|||||||
nick: '',
|
nick: '',
|
||||||
online: true,
|
online: true,
|
||||||
}
|
}
|
||||||
|
export const WebGroupData = {
|
||||||
|
GroupData: new Map<string, Array<WebApiGroupMember>>(),
|
||||||
|
GroupTime: new Map<string, number>()
|
||||||
|
};
|
||||||
export let groups: Group[] = []
|
export let groups: Group[] = []
|
||||||
export let friends: Friend[] = []
|
export let friends: Friend[] = []
|
||||||
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
|
export let friendRequests: Map<number, FriendRequest> = new Map<number, FriendRequest>()
|
||||||
@@ -27,13 +32,13 @@ export async function getFriend(uinOrUid: string): Promise<Friend | undefined> {
|
|||||||
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
|
let friend = friends.find((friend) => friend[filterKey] === filterValue.toString())
|
||||||
if (!friend) {
|
if (!friend) {
|
||||||
try {
|
try {
|
||||||
const _friends = (await NTQQFriendApi.getFriends(true))
|
const _friends = await NTQQFriendApi.getFriends(true)
|
||||||
friend = _friends.find(friend => friend[filterKey] === filterValue.toString())
|
friend = _friends.find((friend) => friend[filterKey] === filterValue.toString())
|
||||||
if (friend){
|
if (friend) {
|
||||||
friends.push(friend)
|
friends.push(friend)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log("刷新好友列表失败", e.stack.toString())
|
log('刷新好友列表失败', e.stack.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return friend
|
return friend
|
||||||
@@ -48,8 +53,7 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
|
|||||||
if (group) {
|
if (group) {
|
||||||
groups.push(group)
|
groups.push(group)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
@@ -49,7 +49,7 @@ class DBUtil {
|
|||||||
setTimeout(initDB, 300)
|
setTimeout(initDB, 300)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
initDB()
|
setTimeout(initDB)
|
||||||
}).then()
|
}).then()
|
||||||
|
|
||||||
const expiredMilliSecond = 1000 * 60 * 60
|
const expiredMilliSecond = 1000 * 60 * 60
|
||||||
@@ -154,12 +154,12 @@ class DBUtil {
|
|||||||
this.updateMsg(msg).then()
|
this.updateMsg(msg).then()
|
||||||
return existMsg.msgShortId
|
return existMsg.msgShortId
|
||||||
}
|
}
|
||||||
this.addCache(msg)
|
|
||||||
|
|
||||||
const shortMsgId = await this.genMsgShortId()
|
const shortMsgId = await this.genMsgShortId()
|
||||||
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId
|
||||||
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq
|
||||||
msg.msgShortId = shortMsgId
|
msg.msgShortId = shortMsgId
|
||||||
|
this.addCache(msg)
|
||||||
// log("新增消息记录", msg.msgId)
|
// log("新增消息记录", msg.msgId)
|
||||||
this.db.put(shortIdKey, msg.msgId).then().catch()
|
this.db.put(shortIdKey, msg.msgId).then().catch()
|
||||||
this.db.put(longIdKey, JSON.stringify(msg)).then().catch()
|
this.db.put(longIdKey, JSON.stringify(msg)).then().catch()
|
||||||
|
@@ -12,7 +12,7 @@ export interface OB11Config {
|
|||||||
enableHttpHeart?: boolean
|
enableHttpHeart?: boolean
|
||||||
}
|
}
|
||||||
export interface CheckVersion {
|
export interface CheckVersion {
|
||||||
result: boolean,
|
result: boolean
|
||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
export interface Config {
|
export interface Config {
|
||||||
|
75
src/common/utils/QQBasicInfo.ts
Normal file
75
src/common/utils/QQBasicInfo.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import os from 'node:os'
|
||||||
|
import { systemPlatform } from './system'
|
||||||
|
|
||||||
|
export const exePath = process.execPath;
|
||||||
|
|
||||||
|
export const pkgInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'package.json');
|
||||||
|
let configVersionInfoPath;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (os.platform() !== 'linux') {
|
||||||
|
configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json');
|
||||||
|
} else {
|
||||||
|
const userPath = os.homedir();
|
||||||
|
const appDataPath = path.resolve(userPath, './.config/QQ');
|
||||||
|
configVersionInfoPath = path.resolve(appDataPath, './versions/config.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof configVersionInfoPath !== 'string') {
|
||||||
|
throw new Error('Something went wrong when load QQ info path');
|
||||||
|
}
|
||||||
|
|
||||||
|
export { configVersionInfoPath };
|
||||||
|
|
||||||
|
type QQPkgInfo = {
|
||||||
|
version: string;
|
||||||
|
buildVersion: string;
|
||||||
|
platform: string;
|
||||||
|
eleArch: string;
|
||||||
|
}
|
||||||
|
type QQVersionConfigInfo = {
|
||||||
|
baseVersion: string;
|
||||||
|
curVersion: string;
|
||||||
|
prevVersion: string;
|
||||||
|
onErrorVersions: Array<any>;
|
||||||
|
buildId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _qqVersionConfigInfo: QQVersionConfigInfo = {
|
||||||
|
'baseVersion': '9.9.9-23361',
|
||||||
|
'curVersion': '9.9.9-23361',
|
||||||
|
'prevVersion': '',
|
||||||
|
'onErrorVersions': [],
|
||||||
|
'buildId': '23361'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fs.existsSync(configVersionInfoPath)) {
|
||||||
|
try {
|
||||||
|
const _ =JSON.parse(fs.readFileSync(configVersionInfoPath).toString());
|
||||||
|
_qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Load QQ version config info failed, Use default version', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo;
|
||||||
|
|
||||||
|
export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath);
|
||||||
|
// platform_type: 3,
|
||||||
|
// app_type: 4,
|
||||||
|
// app_version: '9.9.9-23159',
|
||||||
|
// qua: 'V1_WIN_NQ_9.9.9_23159_GW_B',
|
||||||
|
// appid: '537213764',
|
||||||
|
// platVer: '10.0.26100',
|
||||||
|
// clientVer: '9.9.9-23159',
|
||||||
|
|
||||||
|
let _appid: string = '537213803'; // 默认为 Windows 平台的 appid
|
||||||
|
if (systemPlatform === 'linux') {
|
||||||
|
_appid = '537213827';
|
||||||
|
}
|
||||||
|
// todo: mac 平台的 appid
|
||||||
|
export const appid = _appid;
|
||||||
|
export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106'
|
@@ -109,7 +109,7 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fetchRes = await net.fetch(url, headers)
|
const fetchRes = await net.fetch(url, {headers})
|
||||||
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
|
if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`)
|
||||||
|
|
||||||
const blob = await fetchRes.blob()
|
const blob = await fetchRes.blob()
|
||||||
@@ -176,6 +176,7 @@ export async function uri2local(uri: string, fileName: string = null): Promise<U
|
|||||||
// res.ext = pathInfo.ext
|
// res.ext = pathInfo.ext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fileName = fileName.replace(/[/\\:*?"<>|]/g, '_')
|
||||||
res.fileName = fileName
|
res.fileName = fileName
|
||||||
filePath = path.join(TEMP_DIR, uuidv4() + fileName)
|
filePath = path.join(TEMP_DIR, uuidv4() + fileName)
|
||||||
fs.writeFileSync(filePath, buffer)
|
fs.writeFileSync(filePath, buffer)
|
||||||
|
@@ -65,3 +65,33 @@ export function wrapText(str: string, maxLength: number): string {
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 函数缓存装饰器,根据方法名、参数、自定义key生成缓存键,在一定时间内返回缓存结果
|
||||||
|
* @param ttl 超时时间,单位毫秒
|
||||||
|
* @param customKey 自定义缓存键前缀,可为空,防止方法名参数名一致时导致缓存键冲突
|
||||||
|
* @returns 处理后缓存或调用原方法的结果
|
||||||
|
*/
|
||||||
|
export function cacheFunc(ttl: number, customKey: string='') {
|
||||||
|
const cache = new Map<string, { expiry: number; value: any }>();
|
||||||
|
|
||||||
|
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
||||||
|
const originalMethod = descriptor.value;
|
||||||
|
const className = target.constructor.name; // 获取类名
|
||||||
|
const methodName = propertyKey; // 获取方法名
|
||||||
|
descriptor.value = async function (...args: any[]){
|
||||||
|
const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`;
|
||||||
|
const cached = cache.get(cacheKey);
|
||||||
|
if (cached && cached.expiry > Date.now()) {
|
||||||
|
return cached.value;
|
||||||
|
} else {
|
||||||
|
const result = await originalMethod.apply(this, args);
|
||||||
|
cache.set(cacheKey, { value: result, expiry: Date.now() + ttl });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptor;
|
||||||
|
};
|
||||||
|
}
|
@@ -5,7 +5,7 @@ export * from './file'
|
|||||||
export * from './helper'
|
export * from './helper'
|
||||||
export * from './log'
|
export * from './log'
|
||||||
export * from './qqlevel'
|
export * from './qqlevel'
|
||||||
export * from './qqpkg'
|
export * from './QQBasicInfo'
|
||||||
export * from './upgrade'
|
export * from './upgrade'
|
||||||
export const DATA_DIR = global.LiteLoader.plugins['LLOneBot'].path.data
|
export const DATA_DIR = global.LiteLoader.plugins['LLOneBot'].path.data
|
||||||
export const TEMP_DIR = path.join(DATA_DIR, 'temp')
|
export const TEMP_DIR = path.join(DATA_DIR, 'temp')
|
||||||
@@ -16,3 +16,4 @@ if (!fs.existsSync(TEMP_DIR)) {
|
|||||||
export { getVideoInfo } from './video'
|
export { getVideoInfo } from './video'
|
||||||
export { checkFfmpeg } from './video'
|
export { checkFfmpeg } from './video'
|
||||||
export { encodeSilk } from './audio'
|
export { encodeSilk } from './audio'
|
||||||
|
export { isQQ998 } from './QQBasicInfo'
|
@@ -1,12 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
|
|
||||||
type QQPkgInfo = {
|
|
||||||
version: string
|
|
||||||
buildVersion: string
|
|
||||||
platform: string
|
|
||||||
eleArch: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const qqPkgInfo: QQPkgInfo = require(path.join(process.resourcesPath, 'app/package.json'))
|
|
||||||
|
|
||||||
export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106'
|
|
87
src/common/utils/request.ts
Normal file
87
src/common/utils/request.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import https from 'node:https';
|
||||||
|
import http from 'node:http';
|
||||||
|
|
||||||
|
export class RequestUtil {
|
||||||
|
// 适用于获取服务器下发cookies时获取,仅GET
|
||||||
|
static async HttpsGetCookies(url: string): Promise<Map<string, string>> {
|
||||||
|
return new Promise<Map<string, string>>((resolve, reject) => {
|
||||||
|
const protocol = url.startsWith('https://') ? https : http;
|
||||||
|
protocol.get(url, (res) => {
|
||||||
|
const cookiesHeader = res.headers['set-cookie'];
|
||||||
|
if (!cookiesHeader) {
|
||||||
|
resolve(new Map<string, string>());
|
||||||
|
} else {
|
||||||
|
const cookiesMap = new Map<string, string>();
|
||||||
|
cookiesHeader.forEach((cookieStr) => {
|
||||||
|
cookieStr.split(';').forEach((cookiePart) => {
|
||||||
|
const trimmedPart = cookiePart.trim();
|
||||||
|
if (trimmedPart.includes('=')) {
|
||||||
|
const [key, value] = trimmedPart.split('=').map(part => part.trim());
|
||||||
|
cookiesMap.set(key, decodeURIComponent(value)); // 解码cookie值
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
resolve(cookiesMap);
|
||||||
|
}
|
||||||
|
}).on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求和回复都是JSON data传原始内容 自动编码json
|
||||||
|
static async HttpGetJson<T>(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}, isJsonRet: boolean = true, isArgJson: boolean = true): Promise<T> {
|
||||||
|
let option = new URL(url);
|
||||||
|
const protocol = url.startsWith('https://') ? https : http;
|
||||||
|
const options = {
|
||||||
|
hostname: option.hostname,
|
||||||
|
port: option.port,
|
||||||
|
path: option.href,
|
||||||
|
method: method,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = protocol.request(options, (res: any) => {
|
||||||
|
let responseBody = '';
|
||||||
|
res.on('data', (chunk: string | Buffer) => {
|
||||||
|
responseBody += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
if (isJsonRet) {
|
||||||
|
const responseJson = JSON.parse(responseBody);
|
||||||
|
resolve(responseJson as T);
|
||||||
|
} else {
|
||||||
|
resolve(responseBody as T);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||||||
|
}
|
||||||
|
} catch (parseError) {
|
||||||
|
reject(parseError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', (error: any) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
||||||
|
if (isArgJson) {
|
||||||
|
req.write(JSON.stringify(data));
|
||||||
|
} else {
|
||||||
|
req.write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求返回都是原始内容
|
||||||
|
static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: Record<string, string> = {}) {
|
||||||
|
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||||||
|
}
|
||||||
|
}
|
10
src/common/utils/system.ts
Normal file
10
src/common/utils/system.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
export const systemPlatform = os.platform();
|
||||||
|
export const cpuArch = os.arch();
|
||||||
|
export const systemVersion = os.release();
|
||||||
|
// export const hostname = os.hostname(); // win7不支持
|
||||||
|
const homeDir = os.homedir();
|
||||||
|
export const downloadsPath = path.join(homeDir, 'Downloads');
|
||||||
|
export const systemName = os.type();
|
@@ -5,7 +5,7 @@ import { copyFolder, httpDownload, log, PLUGIN_DIR, TEMP_DIR } from '.'
|
|||||||
import compressing from 'compressing'
|
import compressing from 'compressing'
|
||||||
|
|
||||||
const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
|
const downloadMirrorHosts = ['https://mirror.ghproxy.com/']
|
||||||
const checkVersionMirrorHosts = ['https://521github.com']
|
const checkVersionMirrorHosts = ['https://kkgithub.com']
|
||||||
|
|
||||||
export async function checkNewVersion() {
|
export async function checkNewVersion() {
|
||||||
const latestVersionText = await getRemoteVersion()
|
const latestVersionText = await getRemoteVersion()
|
||||||
|
@@ -48,13 +48,14 @@ import { dbUtil } from '../common/db'
|
|||||||
import { setConfig } from './setConfig'
|
import { setConfig } from './setConfig'
|
||||||
import { NTQQUserApi } from '../ntqqapi/api/user'
|
import { NTQQUserApi } from '../ntqqapi/api/user'
|
||||||
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
import { NTQQGroupApi } from '../ntqqapi/api/group'
|
||||||
import { crychic } from '../ntqqapi/external/crychic'
|
import { crychic } from '../ntqqapi/native/crychic'
|
||||||
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent'
|
import { OB11FriendPokeEvent, OB11GroupPokeEvent } from '../onebot11/event/notice/OB11PokeEvent'
|
||||||
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
|
import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade'
|
||||||
import { log } from '../common/utils/log'
|
import { log } from '../common/utils/log'
|
||||||
import { getConfigUtil } from '../common/config'
|
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'
|
||||||
|
|
||||||
let running = false
|
let running = false
|
||||||
|
|
||||||
@@ -224,39 +225,28 @@ function onLoad() {
|
|||||||
log('report message error: ', e.stack.toString())
|
log('report message error: ', e.stack.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
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) {
|
||||||
// log("message update", message)
|
log('message update', message.msgId, message)
|
||||||
if (message.recallTime != '0') {
|
if (message.recallTime != '0') {
|
||||||
//todo: 这个判断方法不太好,应该使用灰色消息元素来判断
|
if (recallMsgIds.includes(message.msgId)) {
|
||||||
// 撤回消息上报
|
continue
|
||||||
|
}
|
||||||
|
recallMsgIds.push(message.msgId)
|
||||||
const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
|
const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
|
||||||
if (!oriMessage) {
|
if (!oriMessage) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oriMessage.recallTime = message.recallTime
|
oriMessage.recallTime = message.recallTime
|
||||||
dbUtil.updateMsg(oriMessage).then()
|
dbUtil.updateMsg(oriMessage).then()
|
||||||
if (message.chatType == ChatType.friend) {
|
message.msgShortId = oriMessage.msgShortId
|
||||||
const friendRecallEvent = new OB11FriendRecallNoticeEvent(
|
OB11Constructor.RecallEvent(message).then((recallEvent) => {
|
||||||
parseInt(message.senderUin),
|
if (recallEvent) {
|
||||||
oriMessage.msgShortId,
|
log('post recall event', recallEvent)
|
||||||
)
|
postOB11Event(recallEvent)
|
||||||
postOB11Event(friendRecallEvent)
|
|
||||||
} else if (message.chatType == ChatType.group) {
|
|
||||||
let operatorId = message.senderUin
|
|
||||||
for (const element of message.elements) {
|
|
||||||
const operatorUid = element.grayTipElement?.revokeElement.operatorUid
|
|
||||||
const operator = await getGroupMember(message.peerUin, operatorUid)
|
|
||||||
operatorId = operator.uin
|
|
||||||
}
|
}
|
||||||
const groupRecallEvent = new OB11GroupRecallNoticeEvent(
|
})
|
||||||
parseInt(message.peerUin),
|
|
||||||
parseInt(message.senderUin),
|
|
||||||
parseInt(operatorId),
|
|
||||||
oriMessage.msgShortId,
|
|
||||||
)
|
|
||||||
postOB11Event(groupRecallEvent)
|
|
||||||
}
|
|
||||||
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
|
// 不让入库覆盖原来消息,不然就获取不到撤回的消息内容了
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -311,7 +301,11 @@ function onLoad() {
|
|||||||
// if (notify.user2.uid) {
|
// if (notify.user2.uid) {
|
||||||
// member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
|
// member2 = await getGroupMember(notify.group.groupCode, null, notify.user2.uid);
|
||||||
// }
|
// }
|
||||||
if ([GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type)) {
|
if (
|
||||||
|
[GroupNotifyTypes.ADMIN_SET, GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(
|
||||||
|
notify.type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid)
|
const member1 = await getGroupMember(notify.group.groupCode, notify.user1.uid)
|
||||||
log('有管理员变动通知')
|
log('有管理员变动通知')
|
||||||
refreshGroupMembers(notify.group.groupCode).then()
|
refreshGroupMembers(notify.group.groupCode).then()
|
||||||
@@ -321,7 +315,12 @@ function onLoad() {
|
|||||||
if (member1) {
|
if (member1) {
|
||||||
log('变动管理员获取成功')
|
log('变动管理员获取成功')
|
||||||
groupAdminNoticeEvent.user_id = parseInt(member1.uin)
|
groupAdminNoticeEvent.user_id = parseInt(member1.uin)
|
||||||
groupAdminNoticeEvent.sub_type = [GroupNotifyTypes.ADMIN_UNSET, GroupNotifyTypes.ADMIN_UNSET_OTHER].includes(notify.type) ? 'unset' : 'set'
|
groupAdminNoticeEvent.sub_type = [
|
||||||
|
GroupNotifyTypes.ADMIN_UNSET,
|
||||||
|
GroupNotifyTypes.ADMIN_UNSET_OTHER,
|
||||||
|
].includes(notify.type)
|
||||||
|
? 'unset'
|
||||||
|
: 'set'
|
||||||
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
|
// member1.role = notify.type == GroupNotifyTypes.ADMIN_SET ? GroupMemberRole.admin : GroupMemberRole.normal;
|
||||||
postOB11Event(groupAdminNoticeEvent, true)
|
postOB11Event(groupAdminNoticeEvent, true)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -7,26 +7,23 @@ import {
|
|||||||
ChatCacheList,
|
ChatCacheList,
|
||||||
ChatCacheListItemBasic,
|
ChatCacheListItemBasic,
|
||||||
ChatType,
|
ChatType,
|
||||||
ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, RawMessage,
|
ElementType,
|
||||||
|
IMAGE_HTTP_HOST,
|
||||||
|
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 https from 'https'
|
import { rkeyManager } from '@/ntqqapi/api/rkey'
|
||||||
import { sleep } from '../../common/utils'
|
|
||||||
import { hookApi } from '../external/moehook/hook'
|
|
||||||
|
|
||||||
let privateImageRKey = ''
|
|
||||||
let groupImageRKey = ''
|
|
||||||
let lastGetPrivateRKeyTime = 0
|
|
||||||
let lastGetGroupRKeyTime = 0
|
|
||||||
const rkeyExpireTime = 1000 * 60 * 30
|
|
||||||
|
|
||||||
export class NTQQFileApi {
|
export class NTQQFileApi {
|
||||||
static async getFileType(filePath: string) {
|
static async getFileType(filePath: string) {
|
||||||
return await callNTQQApi<{ ext: string }>({
|
return await callNTQQApi<{ ext: string }>({
|
||||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_TYPE, args: [filePath],
|
className: NTQQApiClass.FS_API,
|
||||||
|
methodName: NTQQApiMethod.FILE_TYPE,
|
||||||
|
args: [filePath],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +39,20 @@ export class NTQQFileApi {
|
|||||||
return await callNTQQApi<string>({
|
return await callNTQQApi<string>({
|
||||||
className: NTQQApiClass.FS_API,
|
className: NTQQApiClass.FS_API,
|
||||||
methodName: NTQQApiMethod.FILE_COPY,
|
methodName: NTQQApiMethod.FILE_COPY,
|
||||||
args: [{
|
args: [
|
||||||
fromPath: filePath,
|
{
|
||||||
toPath: destPath,
|
fromPath: filePath,
|
||||||
}],
|
toPath: destPath,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getFileSize(filePath: string) {
|
static async getFileSize(filePath: string) {
|
||||||
return await callNTQQApi<number>({
|
return await callNTQQApi<number>({
|
||||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_SIZE, args: [filePath],
|
className: NTQQApiClass.FS_API,
|
||||||
|
methodName: NTQQApiMethod.FILE_SIZE,
|
||||||
|
args: [filePath],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,18 +71,20 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
const mediaPath = await callNTQQApi<string>({
|
const mediaPath = await callNTQQApi<string>({
|
||||||
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
|
methodName: NTQQApiMethod.MEDIA_FILE_PATH,
|
||||||
args: [{
|
args: [
|
||||||
path_info: {
|
{
|
||||||
md5HexStr: md5,
|
path_info: {
|
||||||
fileName: fileName,
|
md5HexStr: md5,
|
||||||
elementType: elementType,
|
fileName: fileName,
|
||||||
elementSubType,
|
elementType: elementType,
|
||||||
thumbSize: 0,
|
elementSubType,
|
||||||
needCreate: true,
|
thumbSize: 0,
|
||||||
downloadType: 1,
|
needCreate: true,
|
||||||
file_uuid: '',
|
downloadType: 1,
|
||||||
|
file_uuid: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
})
|
})
|
||||||
log('media path', mediaPath)
|
log('media path', mediaPath)
|
||||||
await NTQQFileApi.copyFile(filePath, mediaPath)
|
await NTQQFileApi.copyFile(filePath, mediaPath)
|
||||||
@@ -95,7 +98,15 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, force: boolean = false) {
|
static async downloadMedia(
|
||||||
|
msgId: string,
|
||||||
|
chatType: ChatType,
|
||||||
|
peerUid: string,
|
||||||
|
elementId: string,
|
||||||
|
thumbPath: string,
|
||||||
|
sourcePath: string,
|
||||||
|
force: boolean = false,
|
||||||
|
) {
|
||||||
// 用于下载收到的消息中的图片等
|
// 用于下载收到的消息中的图片等
|
||||||
if (sourcePath && fs.existsSync(sourcePath)) {
|
if (sourcePath && fs.existsSync(sourcePath)) {
|
||||||
if (force) {
|
if (force) {
|
||||||
@@ -126,7 +137,7 @@ export class NTQQFileApi {
|
|||||||
methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
|
methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
|
||||||
args: apiParams,
|
args: apiParams,
|
||||||
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
|
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
|
||||||
cmdCB: (payload: { notifyInfo: { filePath: string, msgId: string } }) => {
|
cmdCB: (payload: { notifyInfo: { filePath: string; msgId: string } }) => {
|
||||||
log('media 下载完成判断', payload.notifyInfo.msgId, msgId)
|
log('media 下载完成判断', payload.notifyInfo.msgId, msgId)
|
||||||
return payload.notifyInfo.msgId == msgId
|
return payload.notifyInfo.msgId == msgId
|
||||||
},
|
},
|
||||||
@@ -135,21 +146,19 @@ export class NTQQFileApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getImageSize(filePath: string) {
|
static async getImageSize(filePath: string) {
|
||||||
return await callNTQQApi<{ width: number, height: number }>({
|
return await callNTQQApi<{ width: number; height: number }>({
|
||||||
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.IMAGE_SIZE, args: [filePath],
|
className: NTQQApiClass.FS_API,
|
||||||
|
methodName: NTQQApiMethod.IMAGE_SIZE,
|
||||||
|
args: [filePath],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getImageUrl(msg: RawMessage) {
|
static async getImageUrl(picElement: PicElement, chatType: ChatType) {
|
||||||
const isPrivateImage = msg.chatType !== ChatType.group
|
const isPrivateImage = chatType !== ChatType.group
|
||||||
const msgElement = msg.elements.find(e => !!e.picElement)
|
const url = picElement.originImageUrl // 没有域名
|
||||||
if (!msgElement) {
|
const md5HexStr = picElement.md5HexStr
|
||||||
return ''
|
const fileMd5 = picElement.md5HexStr
|
||||||
}
|
const fileUuid = picElement.fileUuid
|
||||||
const url = msgElement.picElement.originImageUrl // 没有域名
|
|
||||||
const md5HexStr = msgElement.picElement.md5HexStr
|
|
||||||
const fileMd5 = msgElement.picElement.md5HexStr
|
|
||||||
const fileUuid = msgElement.picElement.fileUuid
|
|
||||||
if (url) {
|
if (url) {
|
||||||
if (url.startsWith('/download')) {
|
if (url.startsWith('/download')) {
|
||||||
// console.log('rkey', rkey);
|
// console.log('rkey', rkey);
|
||||||
@@ -157,70 +166,9 @@ export class NTQQFileApi {
|
|||||||
return IMAGE_HTTP_HOST_NT + url
|
return IMAGE_HTTP_HOST_NT + url
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hookApi.isAvailable()) {
|
const rkeyData = await rkeyManager.getRkey();
|
||||||
log('hookApi is not available')
|
const existsRKey = isPrivateImage ? rkeyData.private_rkey : rkeyData.group_rkey;
|
||||||
return ''
|
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
|
||||||
}
|
|
||||||
|
|
||||||
const saveRKey = (rkey: string) => {
|
|
||||||
if (isPrivateImage) {
|
|
||||||
privateImageRKey = rkey
|
|
||||||
lastGetPrivateRKeyTime = Date.now()
|
|
||||||
} else {
|
|
||||||
groupImageRKey = rkey
|
|
||||||
lastGetGroupRKeyTime = Date.now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshRKey = async () => {
|
|
||||||
log('获取图片rkey...')
|
|
||||||
NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, msgElement.elementId, '', msgElement.picElement.sourcePath, false).then().catch(() => {
|
|
||||||
})
|
|
||||||
await sleep(1000)
|
|
||||||
const _rkey = hookApi.getRKey()
|
|
||||||
if (_rkey) {
|
|
||||||
const imageUrl = IMAGE_HTTP_HOST_NT + url + _rkey
|
|
||||||
// 验证_rkey是否有效
|
|
||||||
try {
|
|
||||||
await new Promise((res, rej) => {
|
|
||||||
https.get(imageUrl, response => {
|
|
||||||
if (response.statusCode !== 200) {
|
|
||||||
rej('图片rkey获取失败')
|
|
||||||
} else {
|
|
||||||
res(response)
|
|
||||||
}
|
|
||||||
}).on('error', e => {
|
|
||||||
rej(e)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
log('图片rkey获取成功', _rkey)
|
|
||||||
saveRKey(_rkey)
|
|
||||||
return _rkey
|
|
||||||
}catch (e) {
|
|
||||||
log('图片rkey有误', imageUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const existsRKey = isPrivateImage ? privateImageRKey : groupImageRKey
|
|
||||||
const lastGetRKeyTime = isPrivateImage ? lastGetPrivateRKeyTime : lastGetGroupRKeyTime
|
|
||||||
if ((Date.now() - lastGetRKeyTime > rkeyExpireTime)) {
|
|
||||||
// rkey过期
|
|
||||||
const newRKey = await refreshRKey()
|
|
||||||
if (newRKey) {
|
|
||||||
return IMAGE_HTTP_HOST_NT + url + `${newRKey}`
|
|
||||||
} else {
|
|
||||||
log('图片rkey获取失败', url)
|
|
||||||
if(existsRKey){
|
|
||||||
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 使用未过期的rkey
|
|
||||||
if (existsRKey) {
|
|
||||||
return IMAGE_HTTP_HOST_NT + url + `${existsRKey}`
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 老的图片url,不需要rkey
|
// 老的图片url,不需要rkey
|
||||||
return IMAGE_HTTP_HOST + url
|
return IMAGE_HTTP_HOST + url
|
||||||
@@ -229,47 +177,58 @@ export class NTQQFileApi {
|
|||||||
// 没有url,需要自己拼接
|
// 没有url,需要自己拼接
|
||||||
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
|
return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`
|
||||||
}
|
}
|
||||||
log('图片url获取失败', msg)
|
log('图片url获取失败', picElement)
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NTQQFileCacheApi {
|
export class NTQQFileCacheApi {
|
||||||
static async setCacheSilentScan(isSilent: boolean = true) {
|
static async setCacheSilentScan(isSilent: boolean = true) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
|
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
|
||||||
args: [{
|
args: [
|
||||||
isSilent,
|
{
|
||||||
}, null],
|
isSilent,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static getCacheSessionPathList() {
|
static getCacheSessionPathList() {
|
||||||
return callNTQQApi<{
|
return callNTQQApi<
|
||||||
key: string,
|
{
|
||||||
value: string
|
key: string
|
||||||
}[]>({
|
value: string
|
||||||
|
}[]
|
||||||
|
>({
|
||||||
className: NTQQApiClass.OS_API,
|
className: NTQQApiClass.OS_API,
|
||||||
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
|
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
|
||||||
return callNTQQApi<any>({ // TODO: 目前还不知道真正的返回值是什么
|
return callNTQQApi<any>({
|
||||||
|
// TODO: 目前还不知道真正的返回值是什么
|
||||||
methodName: NTQQApiMethod.CACHE_CLEAR,
|
methodName: NTQQApiMethod.CACHE_CLEAR,
|
||||||
args: [{
|
args: [
|
||||||
keys: cacheKeys,
|
{
|
||||||
}, null],
|
keys: cacheKeys,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static addCacheScannedPaths(pathMap: object = {}) {
|
static addCacheScannedPaths(pathMap: object = {}) {
|
||||||
return callNTQQApi<GeneralCallResult>({
|
return callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
|
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
|
||||||
args: [{
|
args: [
|
||||||
pathMap: { ...pathMap },
|
{
|
||||||
}, null],
|
pathMap: { ...pathMap },
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,14 +262,18 @@ export class NTQQFileCacheApi {
|
|||||||
return new Promise<ChatCacheList>((res, rej) => {
|
return new Promise<ChatCacheList>((res, rej) => {
|
||||||
callNTQQApi<ChatCacheList>({
|
callNTQQApi<ChatCacheList>({
|
||||||
methodName: NTQQApiMethod.CACHE_CHAT_GET,
|
methodName: NTQQApiMethod.CACHE_CHAT_GET,
|
||||||
args: [{
|
args: [
|
||||||
chatType: type,
|
{
|
||||||
pageSize,
|
chatType: type,
|
||||||
order: 1,
|
pageSize,
|
||||||
pageIndex,
|
order: 1,
|
||||||
}, null],
|
pageIndex,
|
||||||
}).then(list => res(list))
|
},
|
||||||
.catch(e => rej(e))
|
null,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.then((list) => res(list))
|
||||||
|
.catch((e) => rej(e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,24 +282,29 @@ export class NTQQFileCacheApi {
|
|||||||
|
|
||||||
return callNTQQApi<CacheFileList>({
|
return callNTQQApi<CacheFileList>({
|
||||||
methodName: NTQQApiMethod.CACHE_FILE_GET,
|
methodName: NTQQApiMethod.CACHE_FILE_GET,
|
||||||
args: [{
|
args: [
|
||||||
fileType: fileType,
|
{
|
||||||
restart: true,
|
fileType: fileType,
|
||||||
pageSize: pageSize,
|
restart: true,
|
||||||
order: 1,
|
pageSize: pageSize,
|
||||||
lastRecord: _lastRecord,
|
order: 1,
|
||||||
}, null],
|
lastRecord: _lastRecord,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,
|
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,
|
||||||
args: [{
|
args: [
|
||||||
chats,
|
{
|
||||||
fileKeys,
|
chats,
|
||||||
}, null],
|
fileKeys,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -74,7 +74,11 @@ export class NTQQGroupApi {
|
|||||||
}
|
}
|
||||||
static async getGroupIgnoreNotifies() {
|
static async getGroupIgnoreNotifies() {
|
||||||
await NTQQGroupApi.getGroupNotifies()
|
await NTQQGroupApi.getGroupNotifies()
|
||||||
return await NTQQWindowApi.openWindow(NTQQWindows.GroupNotifyFilterWindow, [], ReceiveCmdS.GROUP_NOTIFY)
|
return await NTQQWindowApi.openWindow<GeneralCallResult & GroupNotifies>(
|
||||||
|
NTQQWindows.GroupNotifyFilterWindow,
|
||||||
|
[],
|
||||||
|
ReceiveCmdS.GROUP_NOTIFY,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) {
|
static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) {
|
||||||
const notify: GroupNotify = await dbUtil.getGroupNotify(seq)
|
const notify: GroupNotify = await dbUtil.getGroupNotify(seq)
|
||||||
|
@@ -20,6 +20,7 @@ export class NTQQMsgApi {
|
|||||||
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
// nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览
|
||||||
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
// nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid
|
||||||
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
// 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType
|
||||||
|
emojiId = emojiId.toString()
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
methodName: NTQQApiMethod.EMOJI_LIKE,
|
methodName: NTQQApiMethod.EMOJI_LIKE,
|
||||||
args: [
|
args: [
|
||||||
|
59
src/ntqqapi/api/rkey.ts
Normal file
59
src/ntqqapi/api/rkey.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
//远端rkey获取
|
||||||
|
|
||||||
|
import { log } from '@/common/utils'
|
||||||
|
|
||||||
|
interface ServerRkeyData{
|
||||||
|
group_rkey: string;
|
||||||
|
private_rkey: string;
|
||||||
|
expired_time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RkeyManager {
|
||||||
|
serverUrl: string = '';
|
||||||
|
private rkeyData: ServerRkeyData = {
|
||||||
|
group_rkey: '',
|
||||||
|
private_rkey: '',
|
||||||
|
expired_time: 0
|
||||||
|
};
|
||||||
|
constructor(serverUrl: string) {
|
||||||
|
this.serverUrl = serverUrl;
|
||||||
|
}
|
||||||
|
async getRkey(){
|
||||||
|
if (this.isExpired()) {
|
||||||
|
try {
|
||||||
|
await this.refreshRkey();
|
||||||
|
} catch (e) {
|
||||||
|
log('获取rkey失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.rkeyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
isExpired(): boolean {
|
||||||
|
const now = new Date().getTime() / 1000;
|
||||||
|
// console.log(`now: ${now}, expired_time: ${this.rkeyData.expired_time}`);
|
||||||
|
return now > this.rkeyData.expired_time;
|
||||||
|
}
|
||||||
|
async refreshRkey(): Promise<any> {
|
||||||
|
//刷新rkey
|
||||||
|
this.rkeyData = await this.fetchServerRkey();
|
||||||
|
}
|
||||||
|
async fetchServerRkey(){
|
||||||
|
return new Promise<ServerRkeyData>((resolve, reject) => {
|
||||||
|
fetch(this.serverUrl)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
return reject(response.statusText); // 请求失败,返回错误信息
|
||||||
|
}
|
||||||
|
return response.json(); // 解析 JSON 格式的响应体
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
resolve(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey');
|
@@ -3,10 +3,19 @@ import { Group, SelfInfo, User } from '../types'
|
|||||||
import { ReceiveCmdS } from '../hook'
|
import { ReceiveCmdS } from '../hook'
|
||||||
import { selfInfo, uidMaps } from '../../common/data'
|
import { selfInfo, uidMaps } from '../../common/data'
|
||||||
import { NTQQWindowApi, NTQQWindows } from './window'
|
import { NTQQWindowApi, NTQQWindows } from './window'
|
||||||
import { isQQ998, log, sleep } from '../../common/utils'
|
import { cacheFunc, isQQ998, log, sleep } from '../../common/utils'
|
||||||
|
import { wrapperApi } from '@/ntqqapi/native/wrapper'
|
||||||
|
import * as https from 'https'
|
||||||
|
|
||||||
let userInfoCache: Record<string, User> = {} // uid: User
|
let userInfoCache: Record<string, User> = {} // uid: User
|
||||||
|
|
||||||
|
export interface ClientKeyData extends GeneralCallResult {
|
||||||
|
url: string;
|
||||||
|
keyIndex: string;
|
||||||
|
clientKey: string;
|
||||||
|
expireTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class NTQQUserApi {
|
export class NTQQUserApi {
|
||||||
static async setQQAvatar(filePath: string) {
|
static async setQQAvatar(filePath: string) {
|
||||||
return await callNTQQApi<GeneralCallResult>({
|
return await callNTQQApi<GeneralCallResult>({
|
||||||
@@ -28,6 +37,7 @@ export class NTQQUserApi {
|
|||||||
timeoutSecond: 2,
|
timeoutSecond: 2,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserInfo(uid: string) {
|
static async getUserInfo(uid: string) {
|
||||||
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
const result = await callNTQQApi<{ profiles: Map<string, User> }>({
|
||||||
methodName: NTQQApiMethod.USER_INFO,
|
methodName: NTQQApiMethod.USER_INFO,
|
||||||
@@ -36,6 +46,7 @@ export class NTQQUserApi {
|
|||||||
})
|
})
|
||||||
return result.profiles.get(uid)
|
return result.profiles.get(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUserDetailInfo(uid: string, getLevel = false) {
|
static async getUserDetailInfo(uid: string, getLevel = false) {
|
||||||
// this.getUserInfo(uid);
|
// this.getUserInfo(uid);
|
||||||
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO
|
||||||
@@ -84,64 +95,46 @@ export class NTQQUserApi {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
static async getSkey(groupName: string, groupCode: string): Promise<{ data: string }> {
|
|
||||||
return await NTQQWindowApi.openWindow<{ data: string }>(
|
static async getSkey(): Promise<string> {
|
||||||
NTQQWindows.GroupHomeWorkWindow,
|
const clientKeyData = await this.getClientKey()
|
||||||
[
|
if (clientKeyData.result !== 0) {
|
||||||
{
|
throw new Error('获取clientKey失败')
|
||||||
groupName,
|
}
|
||||||
groupCode,
|
const url = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + selfInfo.uin
|
||||||
source: 'funcbar',
|
+ '&clientkey=' + clientKeyData.clientKey
|
||||||
},
|
+ '&u1=https%3A%2F%2Fh5.qzone.qq.com%2Fqqnt%2Fqzoneinpcqq%2Ffriend%3Frefresh%3D0%26clientuin%3D0%26darkMode%3D0&keyindex=' + clientKeyData.keyIndex
|
||||||
],
|
|
||||||
ReceiveCmdS.SKEY_UPDATE,
|
return new Promise((resolve, reject) => {
|
||||||
1,
|
const req = https.get(url, (res) => {
|
||||||
)
|
const rawCookies = res.headers['set-cookie']
|
||||||
// return await callNTQQApi<string>({
|
const cookies = {}
|
||||||
// className: NTQQApiClass.GROUP_HOME_WORK,
|
rawCookies.forEach(cookie => {
|
||||||
// methodName: NTQQApiMethod.UPDATE_SKEY,
|
// 使用正则表达式匹配 cookie 名称和值
|
||||||
// args: [
|
const regex = /([^=;]+)=([^;]*)/
|
||||||
// {
|
const match = regex.exec(cookie)
|
||||||
// domain: "qun.qq.com"
|
if (match) {
|
||||||
// }
|
cookies[match[1].trim()] = match[2].trim()
|
||||||
// ]
|
}
|
||||||
// })
|
})
|
||||||
// return await callNTQQApi<GeneralCallResult>({
|
resolve(cookies['skey'])
|
||||||
// methodName: NTQQApiMethod.GET_SKEY,
|
})
|
||||||
// args: [
|
req.on('error', e => {
|
||||||
// {
|
reject(e)
|
||||||
// "domains": [
|
});
|
||||||
// "qzone.qq.com",
|
req.end();
|
||||||
// "qlive.qq.com",
|
});
|
||||||
// "qun.qq.com",
|
|
||||||
// "gamecenter.qq.com",
|
|
||||||
// "vip.qq.com",
|
|
||||||
// "qianbao.qq.com",
|
|
||||||
// "qidian.qq.com"
|
|
||||||
// ],
|
|
||||||
// "isForNewPCQQ": false
|
|
||||||
// },
|
|
||||||
// null
|
|
||||||
// ]
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getCookie(group: Group) {
|
@cacheFunc(60 * 30 * 1000)
|
||||||
let cookies = await this.getCookieWithoutSkey()
|
static async getCookies(domain: string) {
|
||||||
let skey = ''
|
const skey = await this.getSkey();
|
||||||
for (let i = 0; i < 2; i++) {
|
const pskey= (await this.getPSkey([domain])).get(domain);
|
||||||
skey = (await this.getSkey(group.groupName, group.groupCode)).data
|
if (!pskey || !skey) {
|
||||||
skey = skey.trim()
|
throw new Error('获取Cookies失败')
|
||||||
if (skey) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
await sleep(1000)
|
|
||||||
}
|
|
||||||
if (!skey) {
|
|
||||||
throw new Error('获取skey失败')
|
|
||||||
}
|
}
|
||||||
const bkn = NTQQUserApi.genBkn(skey)
|
const bkn = NTQQUserApi.genBkn(skey)
|
||||||
cookies = cookies.replace('skey=;', `skey=${skey};`)
|
const cookies = `p_skey=${pskey}; skey=${skey}; p_uin=o${selfInfo.uin}; uin=o${selfInfo.uin}`;
|
||||||
return { cookies, bkn }
|
return { cookies, bkn }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,4 +149,17 @@ export class NTQQUserApi {
|
|||||||
|
|
||||||
return (hash & 0x7fffffff).toString()
|
return (hash & 0x7fffffff).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getPSkey(domains: string[]): Promise<Map<string, string>> {
|
||||||
|
const res = await wrapperApi.NodeIQQNTWrapperSession.getTipOffService().getPskey(domains, true)
|
||||||
|
if (res.result !== 0) {
|
||||||
|
throw new Error(`获取Pskey失败: ${res.errMsg}`)
|
||||||
|
}
|
||||||
|
return res.domainPskeyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getClientKey(): Promise<ClientKeyData> {
|
||||||
|
return await wrapperApi.NodeIQQNTWrapperSession.getTicketService().forceFetchClientKey('')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,76 +1,366 @@
|
|||||||
import { groups } from '../../common/data'
|
import { WebGroupData, groups, selfInfo } from '@/common/data';
|
||||||
import { log } from '../../common/utils'
|
import { log } from '@/common/utils/log';
|
||||||
import { NTQQUserApi } from './user'
|
import { NTQQUserApi } from './user';
|
||||||
|
import { RequestUtil } from '@/common/utils/request';
|
||||||
export class WebApi {
|
export enum WebHonorType {
|
||||||
private static bkn: string
|
ALL = 'all',
|
||||||
private static skey: string
|
TALKACTIVE = 'talkative',
|
||||||
private static pskey: string
|
PERFROMER = 'performer',
|
||||||
private static cookie: string
|
LEGEND = 'legend',
|
||||||
private defaultHeaders: Record<string, string> = {
|
STORONGE_NEWBI = 'strong_newbie',
|
||||||
'User-Agent': 'QQ/8.9.28.635 CFNetwork/1312 Darwin/21.0.0',
|
EMOTION = 'emotion'
|
||||||
|
}
|
||||||
|
export interface WebApiGroupMember {
|
||||||
|
uin: number
|
||||||
|
role: number
|
||||||
|
g: number
|
||||||
|
join_time: number
|
||||||
|
last_speak_time: number
|
||||||
|
lv: {
|
||||||
|
point: number
|
||||||
|
level: number
|
||||||
}
|
}
|
||||||
|
card: string
|
||||||
constructor() {}
|
tags: string
|
||||||
|
flag: number
|
||||||
public async addGroupDigest(groupCode: string, msgSeq: string) {
|
nick: string
|
||||||
const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`
|
qage: number
|
||||||
const res = await this.request(url)
|
rm: number
|
||||||
return await res.json()
|
}
|
||||||
|
interface WebApiGroupMemberRet {
|
||||||
|
ec: number
|
||||||
|
errcode: number
|
||||||
|
em: string
|
||||||
|
cache: number
|
||||||
|
adm_num: number
|
||||||
|
levelname: any
|
||||||
|
mems: WebApiGroupMember[]
|
||||||
|
count: number
|
||||||
|
svr_time: number
|
||||||
|
max_count: number
|
||||||
|
search_count: number
|
||||||
|
extmode: number
|
||||||
|
}
|
||||||
|
export interface WebApiGroupNoticeFeed {
|
||||||
|
u: number//发送者
|
||||||
|
fid: string//fid
|
||||||
|
pubt: number//时间
|
||||||
|
msg: {
|
||||||
|
text: string
|
||||||
|
text_face: string
|
||||||
|
title: string,
|
||||||
|
pics?: {
|
||||||
|
id: string,
|
||||||
|
w: string,
|
||||||
|
h: string
|
||||||
|
}[]
|
||||||
}
|
}
|
||||||
|
type: number
|
||||||
public async getGroupDigest(groupCode: string) {
|
fn: number
|
||||||
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`
|
cn: number
|
||||||
const res = await this.request(url)
|
vn: number
|
||||||
log(res.headers)
|
settings: {
|
||||||
return await res.json()
|
is_show_edit_card: number
|
||||||
|
remind_ts: number
|
||||||
|
tip_window_type: number
|
||||||
|
confirm_required: number
|
||||||
}
|
}
|
||||||
|
read_num: number
|
||||||
private genBkn(sKey: string) {
|
is_read: number
|
||||||
return NTQQUserApi.genBkn(sKey)
|
is_all_confirm: number
|
||||||
|
}
|
||||||
|
export interface WebApiGroupNoticeRet {
|
||||||
|
ec: number
|
||||||
|
em: string
|
||||||
|
ltsm: number
|
||||||
|
srv_code: number
|
||||||
|
read_only: number
|
||||||
|
role: number
|
||||||
|
feeds: WebApiGroupNoticeFeed[]
|
||||||
|
group: {
|
||||||
|
group_id: number
|
||||||
|
class_ext: number
|
||||||
}
|
}
|
||||||
private async init() {
|
sta: number,
|
||||||
if (!WebApi.bkn) {
|
gln: number
|
||||||
const group = groups[0]
|
tst: number,
|
||||||
WebApi.skey = (await NTQQUserApi.getSkey(group.groupName, group.groupCode)).data
|
ui: any
|
||||||
WebApi.bkn = this.genBkn(WebApi.skey)
|
server_time: number
|
||||||
let cookie = await NTQQUserApi.getCookieWithoutSkey()
|
svrt: number
|
||||||
const pskeyRegex = /p_skey=([^;]+)/
|
ad: number
|
||||||
const match = cookie.match(pskeyRegex)
|
}
|
||||||
const pskeyValue = match ? match[1] : null
|
interface GroupEssenceMsg {
|
||||||
WebApi.pskey = pskeyValue
|
group_code: string
|
||||||
if (cookie.indexOf('skey=;') !== -1) {
|
msg_seq: number
|
||||||
cookie = cookie.replace('skey=;', `skey=${WebApi.skey};`)
|
msg_random: number
|
||||||
}
|
sender_uin: string
|
||||||
WebApi.cookie = cookie
|
sender_nick: string
|
||||||
// for(const kv of WebApi.cookie.split(";")){
|
sender_time: number
|
||||||
// const [key, value] = kv.split("=");
|
add_digest_uin: string
|
||||||
// }
|
add_digest_nick: string
|
||||||
// log("set cookie", key, value)
|
add_digest_time: number
|
||||||
// await session.defaultSession.cookies.set({
|
msg_content: any[]
|
||||||
// url: 'https://qun.qq.com', // 你要请求的域名
|
can_be_removed: true
|
||||||
// name: key.trim(),
|
}
|
||||||
// value: value.trim(),
|
export interface GroupEssenceMsgRet {
|
||||||
// expirationDate: Date.now() / 1000 + 300000, // Cookie 过期时间,例如设置为当前时间之后的300秒
|
retcode: number
|
||||||
// });
|
retmsg: string
|
||||||
// }
|
data: {
|
||||||
}
|
msg_list: GroupEssenceMsg[]
|
||||||
}
|
is_end: boolean
|
||||||
|
group_role: number
|
||||||
private async request(url: string, method: 'GET' | 'POST' = 'GET', headers: Record<string, string> = {}) {
|
config_page_url: string
|
||||||
await this.init()
|
}
|
||||||
url += '&bkn=' + WebApi.bkn
|
}
|
||||||
let _headers: Record<string, string> = {
|
export class WebApi {
|
||||||
...this.defaultHeaders,
|
static async getGroupEssenceMsg(GroupCode: string, page_start: string): Promise<GroupEssenceMsgRet> {
|
||||||
...headers,
|
const {cookies: CookieValue, bkn: Bkn} = (await NTQQUserApi.getCookies('qun.qq.com'))
|
||||||
Cookie: WebApi.cookie,
|
const url = 'https://qun.qq.com/cgi-bin/group_digest/digest_list?bkn=' + Bkn + '&group_code=' + GroupCode + '&page_start=' + page_start + '&page_limit=20';
|
||||||
credentials: 'include',
|
let ret;
|
||||||
}
|
try {
|
||||||
log('request', url, _headers)
|
ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(url, 'GET', '', { 'Cookie': CookieValue });
|
||||||
const options = {
|
} catch {
|
||||||
method: method,
|
return undefined;
|
||||||
headers: _headers,
|
}
|
||||||
}
|
//console.log(url, CookieValue);
|
||||||
return fetch(url, options)
|
if (ret.retcode !== 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static async getGroupMembers(GroupCode: string, cached: boolean = true): Promise<WebApiGroupMember[]> {
|
||||||
|
log('webapi 获取群成员', GroupCode);
|
||||||
|
let MemberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
|
||||||
|
try {
|
||||||
|
let cachedData = WebGroupData.GroupData.get(GroupCode);
|
||||||
|
let cachedTime = WebGroupData.GroupTime.get(GroupCode);
|
||||||
|
|
||||||
|
if (!cachedTime || Date.now() - cachedTime > 1800 * 1000 || !cached) {
|
||||||
|
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com'];
|
||||||
|
const _Skey = await NTQQUserApi.getSkey();
|
||||||
|
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + selfInfo.uin;
|
||||||
|
if (!_Skey || !_Pskey) {
|
||||||
|
return MemberData;
|
||||||
|
}
|
||||||
|
const Bkn = WebApi.genBkn(_Skey);
|
||||||
|
const retList: Promise<WebApiGroupMemberRet>[] = [];
|
||||||
|
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>('https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?st=0&end=40&sort=1&gc=' + GroupCode + '&bkn=' + Bkn, 'POST', '', { 'Cookie': CookieValue });
|
||||||
|
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
|
||||||
|
return [];
|
||||||
|
} else {
|
||||||
|
for (const key in fastRet.mems) {
|
||||||
|
MemberData.push(fastRet.mems[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//初始化获取PageNum
|
||||||
|
const PageNum = Math.ceil(fastRet.count / 40);
|
||||||
|
//遍历批量请求
|
||||||
|
for (let i = 2; i <= PageNum; i++) {
|
||||||
|
const ret: Promise<WebApiGroupMemberRet> = RequestUtil.HttpGetJson<WebApiGroupMemberRet>('https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?st=' + (i - 1) * 40 + '&end=' + i * 40 + '&sort=1&gc=' + GroupCode + '&bkn=' + Bkn, 'POST', '', { 'Cookie': CookieValue });
|
||||||
|
retList.push(ret);
|
||||||
|
}
|
||||||
|
//批量等待
|
||||||
|
for (let i = 1; i <= PageNum; i++) {
|
||||||
|
const ret = await (retList[i]);
|
||||||
|
if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const key in ret.mems) {
|
||||||
|
MemberData.push(ret.mems[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WebGroupData.GroupData.set(GroupCode, MemberData);
|
||||||
|
WebGroupData.GroupTime.set(GroupCode, Date.now());
|
||||||
|
} else {
|
||||||
|
MemberData = cachedData as Array<WebApiGroupMember>;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return MemberData;
|
||||||
|
}
|
||||||
|
return MemberData;
|
||||||
|
}
|
||||||
|
// public static async addGroupDigest(groupCode: string, msgSeq: string) {
|
||||||
|
// const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`;
|
||||||
|
// const res = await this.request(url);
|
||||||
|
// return await res.json();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public async getGroupDigest(groupCode: string) {
|
||||||
|
// const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`;
|
||||||
|
// const res = await this.request(url);
|
||||||
|
// return await res.json();
|
||||||
|
// }
|
||||||
|
static async setGroupNotice(GroupCode: string, Content: string = '') {
|
||||||
|
//https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=${bkn}
|
||||||
|
//qid=${群号}&bkn=${bkn}&text=${内容}&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}
|
||||||
|
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com'];
|
||||||
|
const _Skey = await NTQQUserApi.getSkey();
|
||||||
|
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + selfInfo.uin;
|
||||||
|
let ret: any = undefined;
|
||||||
|
//console.log(CookieValue);
|
||||||
|
if (!_Skey || !_Pskey) {
|
||||||
|
//获取Cookies失败
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const Bkn = WebApi.genBkn(_Skey);
|
||||||
|
const data = 'qid=' + GroupCode + '&bkn=' + Bkn + '&text=' + Content + '&pinned=0&type=1&settings={"is_show_edit_card":1,"tip_window_type":1,"confirm_required":1}';
|
||||||
|
const url = 'https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=' + Bkn;
|
||||||
|
try {
|
||||||
|
ret = await RequestUtil.HttpGetJson<any>(url, 'GET', '', { 'Cookie': CookieValue });
|
||||||
|
return ret;
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
static async getGrouptNotice(GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
|
||||||
|
const _Pskey = (await NTQQUserApi.getPSkey(['qun.qq.com']))['qun.qq.com'];
|
||||||
|
const _Skey = await NTQQUserApi.getSkey();
|
||||||
|
const CookieValue = 'p_skey=' + _Pskey + '; skey=' + _Skey + '; p_uin=o' + selfInfo.uin;
|
||||||
|
let ret: WebApiGroupNoticeRet | undefined = undefined;
|
||||||
|
//console.log(CookieValue);
|
||||||
|
if (!_Skey || !_Pskey) {
|
||||||
|
//获取Cookies失败
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const Bkn = WebApi.genBkn(_Skey);
|
||||||
|
const url = 'https://web.qun.qq.com/cgi-bin/announce/get_t_list?bkn=' + Bkn + '&qid=' + GroupCode + '&ft=23&ni=1&n=1&i=1&log_read=1&platform=1&s=-1&n=20';
|
||||||
|
try {
|
||||||
|
ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(url, 'GET', '', { 'Cookie': CookieValue });
|
||||||
|
if (ret?.ec !== 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
static genBkn(sKey: string) {
|
||||||
|
sKey = sKey || '';
|
||||||
|
let hash = 5381;
|
||||||
|
|
||||||
|
for (let i = 0; i < sKey.length; i++) {
|
||||||
|
const code = sKey.charCodeAt(i);
|
||||||
|
hash = hash + (hash << 5) + code;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (hash & 0x7FFFFFFF).toString();
|
||||||
|
}
|
||||||
|
//实现未缓存 考虑2h缓存
|
||||||
|
static async getGroupHonorInfo(groupCode: string, getType: WebHonorType) {
|
||||||
|
async function getDataInternal(Internal_groupCode: string, Internal_type: number) {
|
||||||
|
let url = 'https://qun.qq.com/interactive/honorlist?gc=' + Internal_groupCode + '&type=' + Internal_type.toString();
|
||||||
|
let res = '';
|
||||||
|
let resJson;
|
||||||
|
try {
|
||||||
|
res = await RequestUtil.HttpGetText(url, 'GET', '', { 'Cookie': CookieValue });
|
||||||
|
const match = res.match(/window\.__INITIAL_STATE__=(.*?);/);
|
||||||
|
if (match) {
|
||||||
|
resJson = JSON.parse(match[1].trim());
|
||||||
|
}
|
||||||
|
if (Internal_type === 1) {
|
||||||
|
return resJson?.talkativeList;
|
||||||
|
} else {
|
||||||
|
return resJson?.actorList;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('获取当前群荣耀失败', url, e);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let HonorInfo: any = { group_id: groupCode };
|
||||||
|
const CookieValue = (await NTQQUserApi.getCookies('qun.qq.com')).cookies;
|
||||||
|
|
||||||
|
if (getType === WebHonorType.TALKACTIVE || getType === WebHonorType.ALL) {
|
||||||
|
try {
|
||||||
|
let RetInternal = await getDataInternal(groupCode, 1);
|
||||||
|
if (!RetInternal) {
|
||||||
|
throw new Error('获取龙王信息失败');
|
||||||
|
}
|
||||||
|
HonorInfo.current_talkative = {
|
||||||
|
user_id: RetInternal[0]?.uin,
|
||||||
|
avatar: RetInternal[0]?.avatar,
|
||||||
|
nickname: RetInternal[0]?.name,
|
||||||
|
day_count: 0,
|
||||||
|
description: RetInternal[0]?.desc
|
||||||
|
}
|
||||||
|
HonorInfo.talkative_list = [];
|
||||||
|
for (const talkative_ele of RetInternal) {
|
||||||
|
HonorInfo.talkative_list.push({
|
||||||
|
user_id: talkative_ele?.uin,
|
||||||
|
avatar: talkative_ele?.avatar,
|
||||||
|
description: talkative_ele?.desc,
|
||||||
|
day_count: 0,
|
||||||
|
nickname: talkative_ele?.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||||
|
try {
|
||||||
|
let RetInternal = await getDataInternal(groupCode, 2);
|
||||||
|
if (!RetInternal) {
|
||||||
|
throw new Error('获取群聊之火失败');
|
||||||
|
}
|
||||||
|
HonorInfo.performer_list = [];
|
||||||
|
for (const performer_ele of RetInternal) {
|
||||||
|
HonorInfo.performer_list.push({
|
||||||
|
user_id: performer_ele?.uin,
|
||||||
|
nickname: performer_ele?.name,
|
||||||
|
avatar: performer_ele?.avatar,
|
||||||
|
description: performer_ele?.desc
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getType === WebHonorType.PERFROMER || getType === WebHonorType.ALL) {
|
||||||
|
try {
|
||||||
|
let RetInternal = await getDataInternal(groupCode, 3);
|
||||||
|
if (!RetInternal) {
|
||||||
|
throw new Error('获取群聊炽焰失败');
|
||||||
|
}
|
||||||
|
HonorInfo.legend_list = [];
|
||||||
|
for (const legend_ele of RetInternal) {
|
||||||
|
HonorInfo.legend_list.push({
|
||||||
|
user_id: legend_ele?.uin,
|
||||||
|
nickname: legend_ele?.name,
|
||||||
|
avatar: legend_ele?.avatar,
|
||||||
|
desc: legend_ele?.description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('获取群聊炽焰失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||||
|
try {
|
||||||
|
let RetInternal = await getDataInternal(groupCode, 6);
|
||||||
|
if (!RetInternal) {
|
||||||
|
throw new Error('获取快乐源泉失败');
|
||||||
|
}
|
||||||
|
HonorInfo.emotion_list = [];
|
||||||
|
for (const emotion_ele of RetInternal) {
|
||||||
|
HonorInfo.emotion_list.push({
|
||||||
|
user_id: emotion_ele?.uin,
|
||||||
|
nickname: emotion_ele?.name,
|
||||||
|
avatar: emotion_ele?.avatar,
|
||||||
|
desc: emotion_ele?.description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('获取快乐源泉失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//冒尖小春笋好像已经被tx扬了
|
||||||
|
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||||
|
HonorInfo.strong_newbie_list = [];
|
||||||
|
}
|
||||||
|
return HonorInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ import {
|
|||||||
AtType,
|
AtType,
|
||||||
ElementType,
|
ElementType,
|
||||||
FaceIndex,
|
FaceIndex,
|
||||||
FaceType,
|
|
||||||
PicType,
|
PicType,
|
||||||
SendArkElement,
|
SendArkElement,
|
||||||
SendFaceElement,
|
SendFaceElement,
|
||||||
@@ -22,8 +21,9 @@ import { log } from '../common/utils/log'
|
|||||||
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
|
import { defaultVideoThumb, getVideoInfo } from '../common/utils/video'
|
||||||
import { encodeSilk } from '../common/utils/audio'
|
import { encodeSilk } from '../common/utils/audio'
|
||||||
import { isNull } from '../common/utils'
|
import { isNull } from '../common/utils'
|
||||||
|
import faceConfig from './face_config.json';
|
||||||
|
|
||||||
export const mFaceCache = new Map<string, string>(); // emojiId -> faceName
|
export const mFaceCache = new Map<string, string>() // emojiId -> faceName
|
||||||
|
|
||||||
export class SendMsgElementConstructor {
|
export class SendMsgElementConstructor {
|
||||||
static poke(groupCode: string, uin: string) {
|
static poke(groupCode: string, uin: string) {
|
||||||
@@ -119,15 +119,15 @@ export class SendMsgElementConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
static async video(filePath: string, fileName: string = '', diyThumbPath: string = ''): Promise<SendVideoElement> {
|
||||||
try{
|
try {
|
||||||
await fs.stat(filePath)
|
await fs.stat(filePath)
|
||||||
}catch (e) {
|
} catch (e) {
|
||||||
throw `文件${filePath}异常,不存在`
|
throw `文件${filePath}异常,不存在`
|
||||||
}
|
}
|
||||||
log("复制视频到QQ目录", filePath)
|
log('复制视频到QQ目录', filePath)
|
||||||
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO)
|
let { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO)
|
||||||
|
|
||||||
log("复制视频到QQ目录完成", path)
|
log('复制视频到QQ目录完成', path)
|
||||||
if (fileSize === 0) {
|
if (fileSize === 0) {
|
||||||
throw '文件异常,大小为0'
|
throw '文件异常,大小为0'
|
||||||
}
|
}
|
||||||
@@ -265,13 +265,30 @@ export class SendMsgElementConstructor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static face(faceId: number): SendFaceElement {
|
static face(faceId: number): SendFaceElement {
|
||||||
|
// 从face_config.json中获取表情名称
|
||||||
|
const sysFaces = faceConfig.sysface
|
||||||
|
const emojiFaces = faceConfig.emoji
|
||||||
|
const face = sysFaces.find((face) => face.QSid === faceId.toString())
|
||||||
faceId = parseInt(faceId.toString())
|
faceId = parseInt(faceId.toString())
|
||||||
|
// let faceType = parseInt(faceId.toString().substring(0, 1));
|
||||||
|
let faceType = 1
|
||||||
|
if (faceId >= 222){
|
||||||
|
faceType = 2
|
||||||
|
}
|
||||||
|
if (face.AniStickerType){
|
||||||
|
faceType = 3;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
elementType: ElementType.FACE,
|
elementType: ElementType.FACE,
|
||||||
elementId: '',
|
elementId: '',
|
||||||
faceElement: {
|
faceElement: {
|
||||||
faceIndex: faceId,
|
faceIndex: faceId,
|
||||||
faceType: faceId < 222 ? FaceType.normal : FaceType.normal2,
|
faceType,
|
||||||
|
faceText: face.QDes,
|
||||||
|
stickerId: face.AniStickerId,
|
||||||
|
stickerType: face.AniStickerType,
|
||||||
|
packId: face.AniStickerPackId,
|
||||||
|
sourceType: 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +315,7 @@ export class SendMsgElementConstructor {
|
|||||||
elementId: '',
|
elementId: '',
|
||||||
faceElement: {
|
faceElement: {
|
||||||
faceIndex: FaceIndex.dice,
|
faceIndex: FaceIndex.dice,
|
||||||
faceType: FaceType.dice,
|
faceType: 3,
|
||||||
faceText: '[骰子]',
|
faceText: '[骰子]',
|
||||||
packId: '1',
|
packId: '1',
|
||||||
stickerId: '33',
|
stickerId: '33',
|
||||||
|
54
src/ntqqapi/external/crychic/index.ts
vendored
54
src/ntqqapi/external/crychic/index.ts
vendored
@@ -1,54 +0,0 @@
|
|||||||
import {log} from "../../../common/utils";
|
|
||||||
import {NTQQApi} from "../../ntcall";
|
|
||||||
import {cpModule} from "../cpmodule";
|
|
||||||
|
|
||||||
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 {
|
|
||||||
cpModule('crychic');
|
|
||||||
this.crychic = require("./crychic.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()
|
|
BIN
src/ntqqapi/external/moehook/MoeHoo-linux-x64.node
vendored
BIN
src/ntqqapi/external/moehook/MoeHoo-linux-x64.node
vendored
Binary file not shown.
BIN
src/ntqqapi/external/moehook/MoeHoo-win32-x64.node
vendored
BIN
src/ntqqapi/external/moehook/MoeHoo-win32-x64.node
vendored
Binary file not shown.
3665
src/ntqqapi/face_config.json
Normal file
3665
src/ntqqapi/face_config.json
Normal file
File diff suppressed because it is too large
Load Diff
58
src/ntqqapi/native/crychic/index.ts
Normal file
58
src/ntqqapi/native/crychic/index.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { log } from '../../../common/utils'
|
||||||
|
import { NTQQApi } from '../../ntcall'
|
||||||
|
import { cpModule } from '../cpmodule'
|
||||||
|
|
||||||
|
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 {
|
||||||
|
cpModule('crychic')
|
||||||
|
this.crychic = require('./crychic.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,11 +1,9 @@
|
|||||||
import * as os from "os";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import {cpModule} from "../cpmodule";
|
import {cpModule} from "../cpmodule";
|
||||||
|
import { qqPkgInfo } from '@/common/utils/QQBasicInfo'
|
||||||
|
|
||||||
interface MoeHook {
|
interface MoeHook {
|
||||||
GetRkey: () => string, // Return '&rkey=xxx'
|
GetRkey: () => string, // Return '&rkey=xxx'
|
||||||
HookRkey: () => string
|
HookRkey: (version: string) => string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +14,8 @@ class HookApi {
|
|||||||
cpModule('MoeHoo');
|
cpModule('MoeHoo');
|
||||||
try {
|
try {
|
||||||
this.moeHook = require('./MoeHoo.node');
|
this.moeHook = require('./MoeHoo.node');
|
||||||
console.log("hook rkey地址", this.moeHook!.HookRkey());
|
console.log("hook rkey qq version", this.moeHook!.HookRkey(qqPkgInfo.version));
|
||||||
|
console.log("hook rkey地址", this.moeHook!.HookRkey(qqPkgInfo.version));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('加载 moehoo 失败', e);
|
console.log('加载 moehoo 失败', e);
|
||||||
}
|
}
|
||||||
@@ -31,4 +30,4 @@ class HookApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hookApi = new HookApi();
|
// export const hookApi = new HookApi();
|
19
src/ntqqapi/native/wrapper.ts
Normal file
19
src/ntqqapi/native/wrapper.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
let Process = require('process')
|
||||||
|
let os = require('os')
|
||||||
|
Process.dlopenOrig = Process.dlopen
|
||||||
|
|
||||||
|
export const wrapperApi: any = {}
|
||||||
|
|
||||||
|
Process.dlopen = function(module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
|
||||||
|
let dlopenRet = this.dlopenOrig(module, filename, flags)
|
||||||
|
for (let export_name in module.exports) {
|
||||||
|
module.exports[export_name] = new Proxy(module.exports[export_name], {
|
||||||
|
construct: (target, args, _newTarget) => {
|
||||||
|
let ret = new target(...args)
|
||||||
|
if (export_name === 'NodeIQQNTWrapperSession') wrapperApi.NodeIQQNTWrapperSession = ret
|
||||||
|
return ret
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return dlopenRet
|
||||||
|
}
|
@@ -4,7 +4,6 @@ import { hookApiCallbacks, ReceiveCmd, ReceiveCmdS, registerReceiveHook, removeR
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { log } from '../common/utils/log'
|
import { log } from '../common/utils/log'
|
||||||
import { NTQQWindow, NTQQWindowApi, NTQQWindows } from './api/window'
|
import { NTQQWindow, NTQQWindowApi, NTQQWindows } from './api/window'
|
||||||
import { WebApi } from './api/webapi'
|
|
||||||
import { HOOK_LOG } from '../common/config'
|
import { HOOK_LOG } from '../common/config'
|
||||||
|
|
||||||
export enum NTQQApiClass {
|
export enum NTQQApiClass {
|
||||||
@@ -21,6 +20,7 @@ export enum NTQQApiClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum NTQQApiMethod {
|
export enum NTQQApiMethod {
|
||||||
|
TEST = 'NodeIKernelTipOffService/getPskey',
|
||||||
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
RECENT_CONTACT = 'nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact',
|
||||||
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
ACTIVE_CHAT_PREVIEW = 'nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回最新预览消息
|
||||||
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
ACTIVE_CHAT_HISTORY = 'nodeIKernelMsgService/getMsgsIncludeSelfAndAddActiveChat', // 激活聊天窗口,有时候必须这样才能收到消息, 并返回历史消息
|
||||||
@@ -81,7 +81,7 @@ export enum NTQQApiMethod {
|
|||||||
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
OPEN_EXTRA_WINDOW = 'openExternalWindow',
|
||||||
|
|
||||||
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader',
|
||||||
GET_SKEY = 'nodeIKernelTipOffService/getPskey',
|
GET_PSKEY = 'nodeIKernelTipOffService/getPskey',
|
||||||
UPDATE_SKEY = 'updatePskey',
|
UPDATE_SKEY = 'updatePskey',
|
||||||
|
|
||||||
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的
|
||||||
|
@@ -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 {
|
||||||
|
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
|
||||||
sourcePath: string // 图片本地路径
|
sourcePath: string // 图片本地路径
|
||||||
@@ -201,6 +202,7 @@ export interface PicElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum GrayTipElementSubType {
|
export enum GrayTipElementSubType {
|
||||||
|
RECALL = 1,
|
||||||
INVITE_NEW_MEMBER = 12,
|
INVITE_NEW_MEMBER = 12,
|
||||||
MEMBER_NEW_TITLE = 17,
|
MEMBER_NEW_TITLE = 17,
|
||||||
}
|
}
|
||||||
@@ -213,6 +215,8 @@ export interface GrayTipElement {
|
|||||||
operatorNick: string
|
operatorNick: string
|
||||||
operatorRemark: string
|
operatorRemark: string
|
||||||
operatorMemRemark?: string
|
operatorMemRemark?: string
|
||||||
|
origMsgSenderUid?: string
|
||||||
|
isSelfOperate?: boolean
|
||||||
wording: string // 自定义的撤回提示语
|
wording: string // 自定义的撤回提示语
|
||||||
}
|
}
|
||||||
aioOpGrayTipElement: TipAioOpGrayTipElement
|
aioOpGrayTipElement: TipAioOpGrayTipElement
|
||||||
@@ -226,11 +230,6 @@ export interface GrayTipElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FaceType {
|
|
||||||
normal = 1, // 小黄脸
|
|
||||||
normal2 = 2, // 新小黄脸, 从faceIndex 222开始?
|
|
||||||
dice = 3, // 骰子
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum FaceIndex {
|
export enum FaceIndex {
|
||||||
dice = 358,
|
dice = 358,
|
||||||
@@ -239,7 +238,7 @@ export enum FaceIndex {
|
|||||||
|
|
||||||
export interface FaceElement {
|
export interface FaceElement {
|
||||||
faceIndex: number
|
faceIndex: number
|
||||||
faceType: FaceType
|
faceType: number
|
||||||
faceText?: string
|
faceText?: string
|
||||||
packId?: string
|
packId?: string
|
||||||
stickerId?: string
|
stickerId?: string
|
||||||
|
@@ -5,8 +5,8 @@ export enum GroupNotifyTypes {
|
|||||||
ADMIN_SET = 8,
|
ADMIN_SET = 8,
|
||||||
KICK_MEMBER = 9,
|
KICK_MEMBER = 9,
|
||||||
MEMBER_EXIT = 11, // 主动退出
|
MEMBER_EXIT = 11, // 主动退出
|
||||||
ADMIN_UNSET = 12, // 我被取消管理员
|
ADMIN_UNSET = 12, // 我被取消管理员
|
||||||
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
ADMIN_UNSET_OTHER = 13, // 其他人取消管理员
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupNotifies {
|
export interface GroupNotifies {
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import BaseAction from '../BaseAction'
|
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 { log, sleep, uri2local } from '@/common/utils'
|
||||||
import { NTQQFileApi } from '../../../ntqqapi/api/file'
|
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'
|
||||||
import { FileCache } from '../../../common/types'
|
import { FileCache } from '@/common/types'
|
||||||
|
|
||||||
export interface GetFilePayload {
|
export interface GetFilePayload {
|
||||||
file: string // 文件名或者fileUuid
|
file: string // 文件名或者fileUuid
|
||||||
|
24
src/onebot11/action/group/GetGroupEssence.ts
Normal file
24
src/onebot11/action/group/GetGroupEssence.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { GroupEssenceMsgRet, WebApi } from "@/ntqqapi/api";
|
||||||
|
import BaseAction from "../BaseAction";
|
||||||
|
import { ActionName } from "../types";
|
||||||
|
|
||||||
|
interface PayloadType {
|
||||||
|
group_id: number;
|
||||||
|
pages?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetGroupEssence extends BaseAction<PayloadType, GroupEssenceMsgRet> {
|
||||||
|
actionName = ActionName.GoCQHTTP_GetEssenceMsg;
|
||||||
|
|
||||||
|
protected async _handle(payload: PayloadType) {
|
||||||
|
throw '此 api 暂不支持'
|
||||||
|
const ret = await WebApi.getGroupEssenceMsg(payload.group_id.toString(), payload.pages?.toString() || '0');
|
||||||
|
if (!ret) {
|
||||||
|
throw new Error('获取失败');
|
||||||
|
}
|
||||||
|
// ret.map((item) => {
|
||||||
|
//
|
||||||
|
// })
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
22
src/onebot11/action/group/GetGroupHonorInfo.ts
Normal file
22
src/onebot11/action/group/GetGroupHonorInfo.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { WebApi, WebHonorType } from "@/ntqqapi/api";
|
||||||
|
import { ActionName } from "../types";
|
||||||
|
import BaseAction from "../BaseAction";
|
||||||
|
|
||||||
|
interface Payload {
|
||||||
|
group_id: number,
|
||||||
|
type?: WebHonorType
|
||||||
|
}
|
||||||
|
export class GetGroupHonorInfo extends BaseAction<Payload, Array<any>> {
|
||||||
|
actionName = ActionName.GetGroupHonorInfo;
|
||||||
|
|
||||||
|
protected async _handle(payload: Payload) {
|
||||||
|
// console.log(await NTQQUserApi.getRobotUinRange());
|
||||||
|
if (!payload.group_id) {
|
||||||
|
throw '缺少参数group_id';
|
||||||
|
}
|
||||||
|
if (!payload.type) {
|
||||||
|
payload.type = WebHonorType.ALL;
|
||||||
|
}
|
||||||
|
return await WebApi.getGroupHonorInfo(payload.group_id.toString(), payload.type);
|
||||||
|
}
|
||||||
|
}
|
@@ -14,13 +14,10 @@ class GetGroupList extends BaseAction<Payload, OB11Group[]> {
|
|||||||
actionName = ActionName.GetGroupList
|
actionName = ActionName.GetGroupList
|
||||||
|
|
||||||
protected async _handle(payload: Payload) {
|
protected async _handle(payload: Payload) {
|
||||||
if (
|
if (groups.length === 0 || payload?.no_cache === true || payload?.no_cache === 'true') {
|
||||||
groups.length === 0
|
|
||||||
|| payload?.no_cache === true || payload?.no_cache === 'true'
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const groups = await NTQQGroupApi.getGroups(true)
|
const groups = await NTQQGroupApi.getGroups(true)
|
||||||
log("强制刷新群列表, 数量:", groups.length)
|
log('强制刷新群列表, 数量:', groups.length)
|
||||||
return OB11Constructor.groups(groups)
|
return OB11Constructor.groups(groups)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@ import { NTQQGroupApi } from '../../../ntqqapi/api/group'
|
|||||||
import { log } from '../../../common/utils'
|
import { log } from '../../../common/utils'
|
||||||
|
|
||||||
export interface PayloadType {
|
export interface PayloadType {
|
||||||
group_id: number,
|
group_id: number
|
||||||
no_cache: boolean | string
|
no_cache: boolean | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,6 +47,8 @@ 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, ForwardSingleGroupMsg } from './msg/ForwardSingleMsg'
|
||||||
|
import { GetGroupEssence } from './group/GetGroupEssence'
|
||||||
|
import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
|
||||||
|
|
||||||
export const actionHandlers = [
|
export const actionHandlers = [
|
||||||
new GetFile(),
|
new GetFile(),
|
||||||
@@ -89,6 +91,8 @@ export const actionHandlers = [
|
|||||||
new ForwardFriendSingleMsg(),
|
new ForwardFriendSingleMsg(),
|
||||||
new ForwardSingleGroupMsg(),
|
new ForwardSingleGroupMsg(),
|
||||||
//以下为go-cqhttp api
|
//以下为go-cqhttp api
|
||||||
|
new GetGroupEssence(),
|
||||||
|
new GetGroupHonorInfo(),
|
||||||
new GoCQHTTPSendForwardMsg(),
|
new GoCQHTTPSendForwardMsg(),
|
||||||
new GoCQHTTPSendGroupForwardMsg(),
|
new GoCQHTTPSendGroupForwardMsg(),
|
||||||
new GoCQHTTPSendPrivateForwardMsg(),
|
new GoCQHTTPSendPrivateForwardMsg(),
|
||||||
|
@@ -14,11 +14,13 @@ import { friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo } f
|
|||||||
import {
|
import {
|
||||||
OB11MessageCustomMusic,
|
OB11MessageCustomMusic,
|
||||||
OB11MessageData,
|
OB11MessageData,
|
||||||
OB11MessageDataType, OB11MessageFile,
|
OB11MessageDataType,
|
||||||
|
OB11MessageFile,
|
||||||
OB11MessageJson,
|
OB11MessageJson,
|
||||||
OB11MessageMixType,
|
OB11MessageMixType,
|
||||||
OB11MessageMusic,
|
OB11MessageMusic,
|
||||||
OB11MessageNode, OB11MessageVideo,
|
OB11MessageNode,
|
||||||
|
OB11MessageVideo,
|
||||||
OB11PostSendMsg,
|
OB11PostSendMsg,
|
||||||
} from '../../types'
|
} from '../../types'
|
||||||
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg'
|
import { NTQQMsgApi, Peer } from '../../../ntqqapi/api/msg'
|
||||||
@@ -32,7 +34,7 @@ import { ALLOW_SEND_TEMP_MSG, getConfigUtil } from '../../../common/config'
|
|||||||
import { log } from '../../../common/utils/log'
|
import { log } from '../../../common/utils/log'
|
||||||
import { sleep } from '../../../common/utils/helper'
|
import { sleep } from '../../../common/utils/helper'
|
||||||
import { uri2local } from '../../../common/utils'
|
import { uri2local } from '../../../common/utils'
|
||||||
import { crychic } from '../../../ntqqapi/external/crychic'
|
import { crychic } from '../../../ntqqapi/native/crychic'
|
||||||
import { NTQQGroupApi } from '../../../ntqqapi/api'
|
import { NTQQGroupApi } from '../../../ntqqapi/api'
|
||||||
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
|
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '../../../common/utils/sign'
|
||||||
|
|
||||||
@@ -177,11 +179,18 @@ export async function createSendElements(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case OB11MessageDataType.mface: {
|
case OB11MessageDataType.mface:
|
||||||
sendElements.push(
|
{
|
||||||
SendMsgElementConstructor.mface(sendMsg.data.emoji_package_id, sendMsg.data.emoji_id, sendMsg.data.key, sendMsg.data.summary),
|
sendElements.push(
|
||||||
)
|
SendMsgElementConstructor.mface(
|
||||||
}break;
|
sendMsg.data.emoji_package_id,
|
||||||
|
sendMsg.data.emoji_id,
|
||||||
|
sendMsg.data.key,
|
||||||
|
sendMsg.data.summary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
case OB11MessageDataType.image:
|
case OB11MessageDataType.image:
|
||||||
case OB11MessageDataType.file:
|
case OB11MessageDataType.file:
|
||||||
case OB11MessageDataType.video:
|
case OB11MessageDataType.video:
|
||||||
@@ -303,14 +312,14 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
|
|
||||||
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
protected async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
||||||
const messages = convertMessage2List(payload.message)
|
const messages = convertMessage2List(payload.message)
|
||||||
const fmNum = this.getSpecialMsgNum(payload, OB11MessageDataType.node)
|
const fmNum = this.getSpecialMsgNum(messages, OB11MessageDataType.node)
|
||||||
if (fmNum && fmNum != messages.length) {
|
if (fmNum && fmNum != messages.length) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const musicNum = this.getSpecialMsgNum(payload, OB11MessageDataType.music)
|
const musicNum = this.getSpecialMsgNum(messages, OB11MessageDataType.music)
|
||||||
if (musicNum && messages.length > 1) {
|
if (musicNum && messages.length > 1) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
@@ -358,7 +367,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
if (friend) {
|
if (friend) {
|
||||||
// peer.name = friend.nickName
|
// peer.name = friend.nickName
|
||||||
peer.peerUid = friend.uid
|
peer.peerUid = friend.uid
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
peer.chatType = ChatType.temp
|
peer.chatType = ChatType.temp
|
||||||
const tempUserUid = getUidByUin(payload.user_id.toString())
|
const tempUserUid = getUidByUin(payload.user_id.toString())
|
||||||
if (!tempUserUid) {
|
if (!tempUserUid) {
|
||||||
@@ -371,25 +381,28 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
if (payload?.group_id && payload.message_type === 'group') {
|
if (payload?.group_id && payload.message_type === 'group') {
|
||||||
await genGroupPeer()
|
await genGroupPeer()
|
||||||
} else if (payload?.user_id) {
|
}
|
||||||
|
else if (payload?.user_id) {
|
||||||
genFriendPeer()
|
genFriendPeer()
|
||||||
} else if (payload.group_id) {
|
}
|
||||||
|
else if (payload.group_id) {
|
||||||
await genGroupPeer()
|
await genGroupPeer()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
throw '发送消息参数错误, 请指定group_id或user_id'
|
throw '发送消息参数错误, 请指定group_id或user_id'
|
||||||
}
|
}
|
||||||
const messages = convertMessage2List(
|
const messages = convertMessage2List(
|
||||||
payload.message,
|
payload.message,
|
||||||
payload.auto_escape === true || payload.auto_escape === 'true',
|
payload.auto_escape === true || payload.auto_escape === 'true',
|
||||||
)
|
)
|
||||||
if (this.getSpecialMsgNum(payload, OB11MessageDataType.node)) {
|
if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) {
|
||||||
try {
|
try {
|
||||||
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
|
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
|
||||||
return { message_id: returnMsg.msgShortId }
|
return { message_id: returnMsg.msgShortId }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw '发送转发消息失败 ' + e.toString()
|
throw '发送转发消息失败 ' + e.toString()
|
||||||
}
|
}
|
||||||
} else if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) {
|
} else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
|
||||||
const music = messages[0] as OB11MessageMusic
|
const music = messages[0] as OB11MessageMusic
|
||||||
if (music) {
|
if (music) {
|
||||||
const { musicSignUrl } = getConfigUtil().getConfig()
|
const { musicSignUrl } = getConfigUtil().getConfig()
|
||||||
@@ -402,24 +415,23 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
const postData: MusicSignPostData = { ...music.data }
|
const postData: MusicSignPostData = { ...music.data }
|
||||||
if (type === 'custom' && music.data.content) {
|
if (type === 'custom' && music.data.content) {
|
||||||
|
|
||||||
;(postData as CustomMusicSignPostData).singer = music.data.content
|
;(postData as CustomMusicSignPostData).singer = music.data.content
|
||||||
delete (postData as OB11MessageCustomMusic['data']).content
|
delete (postData as OB11MessageCustomMusic['data']).content
|
||||||
}
|
}
|
||||||
if (type === 'custom'){
|
if (type === 'custom') {
|
||||||
const customMusicData = music.data as CustomMusicSignPostData
|
const customMusicData = music.data as CustomMusicSignPostData
|
||||||
if (!customMusicData.url){
|
if (!customMusicData.url) {
|
||||||
throw ('自定义音卡缺少参数url');
|
throw '自定义音卡缺少参数url'
|
||||||
}
|
}
|
||||||
if (!customMusicData.audio){
|
if (!customMusicData.audio) {
|
||||||
throw('自定义音卡缺少参数audio');
|
throw '自定义音卡缺少参数audio'
|
||||||
}
|
}
|
||||||
if (!customMusicData.title){
|
if (!customMusicData.title) {
|
||||||
throw('自定义音卡缺少参数title');
|
throw '自定义音卡缺少参数title'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'qq' || type === '163') {
|
if (type === 'qq' || type === '163') {
|
||||||
const idMusicData = music.data as IdMusicSignPostData;
|
const idMusicData = music.data as IdMusicSignPostData
|
||||||
if (!idMusicData.id) {
|
if (!idMusicData.id) {
|
||||||
throw '音乐卡片缺少id参数'
|
throw '音乐卡片缺少id参数'
|
||||||
}
|
}
|
||||||
@@ -427,6 +439,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
let jsonContent: string
|
let jsonContent: string
|
||||||
try {
|
try {
|
||||||
jsonContent = await new MusicSign(musicSignUrl).sign(postData)
|
jsonContent = await new MusicSign(musicSignUrl).sign(postData)
|
||||||
|
if (!jsonContent){
|
||||||
|
throw '音乐消息生成失败,提交内容有误或者签名服务器签名失败'
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw `签名音乐消息失败:${e}`
|
throw `签名音乐消息失败:${e}`
|
||||||
}
|
}
|
||||||
@@ -448,9 +463,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
return { message_id: returnMsg.msgShortId }
|
return { message_id: returnMsg.msgShortId }
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType): number {
|
private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number {
|
||||||
if (Array.isArray(payload.message)) {
|
if (Array.isArray(message)) {
|
||||||
return payload.message.filter((msg) => msg.type == msgType).length
|
return message.filter((msg) => msg.type == msgType).length
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -502,7 +517,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId))
|
let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId))
|
||||||
if (!needClone) {
|
if (!needClone) {
|
||||||
nodeMsgIds.push(nodeMsg.msgId)
|
nodeMsgIds.push(nodeMsg.msgId)
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (nodeMsg.peerUid !== selfInfo.uid) {
|
if (nodeMsg.peerUid !== selfInfo.uid) {
|
||||||
const cloneMsg = await this.cloneMsg(nodeMsg)
|
const cloneMsg = await this.cloneMsg(nodeMsg)
|
||||||
if (cloneMsg) {
|
if (cloneMsg) {
|
||||||
@@ -510,7 +526,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
// 自定义的消息
|
// 自定义的消息
|
||||||
// 提取消息段,发给自己生成消息id
|
// 提取消息段,发给自己生成消息id
|
||||||
try {
|
try {
|
||||||
@@ -532,7 +549,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
}
|
}
|
||||||
sendElementsSplit[splitIndex] = [ele]
|
sendElementsSplit[splitIndex] = [ele]
|
||||||
splitIndex++
|
splitIndex++
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
sendElementsSplit[splitIndex].push(ele)
|
sendElementsSplit[splitIndex].push(ele)
|
||||||
}
|
}
|
||||||
log(sendElementsSplit)
|
log(sendElementsSplit)
|
||||||
@@ -561,7 +579,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
nodeMsgArray.push(nodeMsg)
|
nodeMsgArray.push(nodeMsg)
|
||||||
if (!srcPeer) {
|
if (!srcPeer) {
|
||||||
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
|
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid }
|
||||||
} else if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
}
|
||||||
|
else if (srcPeer.peerUid !== nodeMsg.peerUid) {
|
||||||
needSendSelf = true
|
needSendSelf = true
|
||||||
srcPeer = selfPeer
|
srcPeer = selfPeer
|
||||||
}
|
}
|
||||||
|
@@ -66,4 +66,6 @@ export enum ActionName {
|
|||||||
GoCQHTTP_DownloadFile = 'download_file',
|
GoCQHTTP_DownloadFile = 'download_file',
|
||||||
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
GoCQHTTP_GetGroupMsgHistory = 'get_group_msg_history',
|
||||||
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
GoCQHTTP_GetForwardMsg = 'get_forward_msg',
|
||||||
|
GoCQHTTP_GetEssenceMsg = "get_essence_msg_list",
|
||||||
|
GetGroupHonorInfo = "get_group_honor_info",
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,15 @@ import { NTQQUserApi } from '../../../ntqqapi/api'
|
|||||||
import { groups } from '../../../common/data'
|
import { groups } from '../../../common/data'
|
||||||
import { ActionName } from '../types'
|
import { ActionName } from '../types'
|
||||||
|
|
||||||
export class GetCookies extends BaseAction<null, { cookies: string; bkn: string }> {
|
interface Payload {
|
||||||
|
domain: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetCookies extends BaseAction<Payload, { cookies: string; bkn: string }> {
|
||||||
actionName = ActionName.GetCookies
|
actionName = ActionName.GetCookies
|
||||||
|
|
||||||
protected async _handle() {
|
protected async _handle(payload: Payload) {
|
||||||
return NTQQUserApi.getCookie(groups[0])
|
const domain = payload.domain || 'qun.qq.com'
|
||||||
|
return NTQQUserApi.getCookies(domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import { ActionName } from '../types'
|
|||||||
import { NTQQFriendApi } from '../../../ntqqapi/api'
|
import { NTQQFriendApi } from '../../../ntqqapi/api'
|
||||||
import { log } from '../../../common/utils'
|
import { log } from '../../../common/utils'
|
||||||
|
|
||||||
interface Payload{
|
interface Payload {
|
||||||
no_cache: boolean | string
|
no_cache: boolean | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import fastXmlParser, { XMLParser } from 'fast-xml-parser'
|
import fastXmlParser from 'fast-xml-parser'
|
||||||
import {
|
import {
|
||||||
OB11Group,
|
OB11Group,
|
||||||
OB11GroupMember,
|
OB11GroupMember,
|
||||||
@@ -16,8 +16,7 @@ import {
|
|||||||
GrayTipElementSubType,
|
GrayTipElementSubType,
|
||||||
Group,
|
Group,
|
||||||
GroupMember,
|
GroupMember,
|
||||||
IMAGE_HTTP_HOST,
|
PicType,
|
||||||
IMAGE_HTTP_HOST_NT,
|
|
||||||
RawMessage,
|
RawMessage,
|
||||||
SelfInfo,
|
SelfInfo,
|
||||||
Sex,
|
Sex,
|
||||||
@@ -25,7 +24,7 @@ import {
|
|||||||
User,
|
User,
|
||||||
VideoElement,
|
VideoElement,
|
||||||
} from '../ntqqapi/types'
|
} from '../ntqqapi/types'
|
||||||
import { deleteGroup, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap } from '../common/data'
|
import { deleteGroup, getFriend, getGroupMember, selfInfo, tempGroupCodeMap } 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'
|
||||||
@@ -46,6 +45,8 @@ import { NTQQGroupApi } from '../ntqqapi/api'
|
|||||||
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
|
import { OB11GroupMsgEmojiLikeEvent } from './event/notice/OB11MsgEmojiLikeEvent'
|
||||||
import { mFaceCache } from '../ntqqapi/constructor'
|
import { mFaceCache } from '../ntqqapi/constructor'
|
||||||
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
import { OB11FriendAddNoticeEvent } from './event/notice/OB11FriendAddNoticeEvent'
|
||||||
|
import { OB11FriendRecallNoticeEvent } from './event/notice/OB11FriendRecallNoticeEvent'
|
||||||
|
import { OB11GroupRecallNoticeEvent } from './event/notice/OB11GroupRecallNoticeEvent'
|
||||||
|
|
||||||
let lastRKeyUpdateTime = 0
|
let lastRKeyUpdateTime = 0
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ export class OB11Constructor {
|
|||||||
time: parseInt(msg.msgTime) || Date.now(),
|
time: parseInt(msg.msgTime) || Date.now(),
|
||||||
message_id: msg.msgShortId,
|
message_id: msg.msgShortId,
|
||||||
real_id: msg.msgShortId,
|
real_id: msg.msgShortId,
|
||||||
|
message_seq: msg.msgShortId,
|
||||||
message_type: msg.chatType == ChatType.group ? 'group' : 'private',
|
message_type: msg.chatType == ChatType.group ? 'group' : 'private',
|
||||||
sender: {
|
sender: {
|
||||||
user_id: parseInt(msg.senderUin),
|
user_id: parseInt(msg.senderUin),
|
||||||
@@ -84,13 +86,15 @@ export class OB11Constructor {
|
|||||||
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role)
|
resMsg.sender.role = OB11Constructor.groupMemberRole(member.role)
|
||||||
resMsg.sender.nickname = member.nick
|
resMsg.sender.nickname = member.nick
|
||||||
}
|
}
|
||||||
} else if (msg.chatType == ChatType.friend) {
|
}
|
||||||
|
else if (msg.chatType == ChatType.friend) {
|
||||||
resMsg.sub_type = 'friend'
|
resMsg.sub_type = 'friend'
|
||||||
const friend = await getFriend(msg.senderUin)
|
const friend = await getFriend(msg.senderUin)
|
||||||
if (friend) {
|
if (friend) {
|
||||||
resMsg.sender.nickname = friend.nick
|
resMsg.sender.nickname = friend.nick
|
||||||
}
|
}
|
||||||
} else if (msg.chatType == ChatType.temp) {
|
}
|
||||||
|
else if (msg.chatType == ChatType.temp) {
|
||||||
resMsg.sub_type = 'group'
|
resMsg.sub_type = 'group'
|
||||||
const tempGroupCode = tempGroupCodeMap[msg.peerUin]
|
const tempGroupCode = tempGroupCodeMap[msg.peerUin]
|
||||||
if (tempGroupCode) {
|
if (tempGroupCode) {
|
||||||
@@ -108,7 +112,8 @@ export class OB11Constructor {
|
|||||||
if (element.textElement.atType == AtType.atAll) {
|
if (element.textElement.atType == AtType.atAll) {
|
||||||
// message_data["data"]["mention"] = "all"
|
// message_data["data"]["mention"] = "all"
|
||||||
message_data['data']['qq'] = 'all'
|
message_data['data']['qq'] = 'all'
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
let atUid = element.textElement.atNtUid
|
let atUid = element.textElement.atNtUid
|
||||||
let atQQ = element.textElement.atUid
|
let atQQ = element.textElement.atUid
|
||||||
if (!atQQ || atQQ === '0') {
|
if (!atQQ || atQQ === '0') {
|
||||||
@@ -122,14 +127,16 @@ export class OB11Constructor {
|
|||||||
message_data['data']['qq'] = atQQ
|
message_data['data']['qq'] = atQQ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (element.textElement) {
|
}
|
||||||
|
else if (element.textElement) {
|
||||||
message_data['type'] = 'text'
|
message_data['type'] = 'text'
|
||||||
let text = element.textElement.content
|
let text = element.textElement.content
|
||||||
if (!text.trim()) {
|
if (!text.trim()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
message_data['data']['text'] = text
|
message_data['data']['text'] = text
|
||||||
} else if (element.replyElement) {
|
}
|
||||||
|
else if (element.replyElement) {
|
||||||
message_data['type'] = 'reply'
|
message_data['type'] = 'reply'
|
||||||
// log("收到回复消息", element.replyElement.replayMsgSeq)
|
// log("收到回复消息", element.replyElement.replayMsgSeq)
|
||||||
try {
|
try {
|
||||||
@@ -137,26 +144,33 @@ export class OB11Constructor {
|
|||||||
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
|
// log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
|
||||||
if (replyMsg) {
|
if (replyMsg) {
|
||||||
message_data['data']['id'] = replyMsg.msgShortId.toString()
|
message_data['data']['id'] = replyMsg.msgShortId.toString()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq)
|
log('获取不到引用的消息', e.stack, element.replyElement.replayMsgSeq)
|
||||||
}
|
}
|
||||||
} else if (element.picElement) {
|
}
|
||||||
|
else if (element.picElement) {
|
||||||
message_data['type'] = 'image'
|
message_data['type'] = 'image'
|
||||||
// message_data["data"]["file"] = element.picElement.sourcePath
|
// message_data["data"]["file"] = element.picElement.sourcePath
|
||||||
message_data['data']['file'] = element.picElement.fileName
|
let fileName = element.picElement.fileName;
|
||||||
|
const sourcePath = element.picElement.sourcePath;
|
||||||
|
if (element.picElement.picType === PicType.gif && !fileName.endsWith('.gif')){
|
||||||
|
fileName += ".gif";
|
||||||
|
}
|
||||||
|
message_data['data']['file'] = fileName
|
||||||
// message_data["data"]["path"] = element.picElement.sourcePath
|
// message_data["data"]["path"] = element.picElement.sourcePath
|
||||||
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
// let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
|
||||||
|
|
||||||
message_data['data']['url'] = await NTQQFileApi.getImageUrl(msg)
|
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement, msg.chatType);
|
||||||
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
// message_data["data"]["file_id"] = element.picElement.fileUuid
|
||||||
message_data['data']['file_size'] = element.picElement.fileSize
|
message_data['data']['file_size'] = element.picElement.fileSize
|
||||||
dbUtil
|
dbUtil
|
||||||
.addFileCache(element.picElement.fileName, {
|
.addFileCache(fileName, {
|
||||||
fileName: element.picElement.fileName,
|
fileName,
|
||||||
filePath: element.picElement.sourcePath,
|
filePath: sourcePath,
|
||||||
fileSize: element.picElement.fileSize.toString(),
|
fileSize: element.picElement.fileSize.toString(),
|
||||||
url: message_data['data']['url'],
|
url: message_data['data']['url'],
|
||||||
downloadFunc: async () => {
|
downloadFunc: async () => {
|
||||||
@@ -172,7 +186,8 @@ export class OB11Constructor {
|
|||||||
})
|
})
|
||||||
.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
|
||||||
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
|
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
|
||||||
message_data['type'] = ob11MessageDataType
|
message_data['type'] = ob11MessageDataType
|
||||||
@@ -201,7 +216,8 @@ export class OB11Constructor {
|
|||||||
})
|
})
|
||||||
.then()
|
.then()
|
||||||
// 怎么拿到url呢
|
// 怎么拿到url呢
|
||||||
} else if (element.pttElement) {
|
}
|
||||||
|
else if (element.pttElement) {
|
||||||
message_data['type'] = OB11MessageDataType.voice
|
message_data['type'] = OB11MessageDataType.voice
|
||||||
message_data['data']['file'] = element.pttElement.fileName
|
message_data['data']['file'] = element.pttElement.fileName
|
||||||
message_data['data']['path'] = element.pttElement.filePath
|
message_data['data']['path'] = element.pttElement.filePath
|
||||||
@@ -221,22 +237,27 @@ export class OB11Constructor {
|
|||||||
// }).catch(err => {
|
// }).catch(err => {
|
||||||
// console.log("语音转文字失败", err);
|
// console.log("语音转文字失败", err);
|
||||||
// })
|
// })
|
||||||
} else if (element.arkElement) {
|
}
|
||||||
|
else if (element.arkElement) {
|
||||||
message_data['type'] = OB11MessageDataType.json
|
message_data['type'] = OB11MessageDataType.json
|
||||||
message_data['data']['data'] = element.arkElement.bytesData
|
message_data['data']['data'] = element.arkElement.bytesData
|
||||||
} else if (element.faceElement) {
|
}
|
||||||
|
else if (element.faceElement) {
|
||||||
const faceId = element.faceElement.faceIndex
|
const faceId = element.faceElement.faceIndex
|
||||||
if (faceId === FaceIndex.dice) {
|
if (faceId === FaceIndex.dice) {
|
||||||
message_data['type'] = OB11MessageDataType.dice
|
message_data['type'] = OB11MessageDataType.dice
|
||||||
message_data['data']['result'] = element.faceElement.resultId
|
message_data['data']['result'] = element.faceElement.resultId
|
||||||
} else if (faceId === FaceIndex.RPS) {
|
}
|
||||||
|
else if (faceId === FaceIndex.RPS) {
|
||||||
message_data['type'] = OB11MessageDataType.RPS
|
message_data['type'] = OB11MessageDataType.RPS
|
||||||
message_data['data']['result'] = element.faceElement.resultId
|
message_data['data']['result'] = element.faceElement.resultId
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
message_data['type'] = OB11MessageDataType.face
|
message_data['type'] = OB11MessageDataType.face
|
||||||
message_data['data']['id'] = element.faceElement.faceIndex.toString()
|
message_data['data']['id'] = element.faceElement.faceIndex.toString()
|
||||||
}
|
}
|
||||||
} else if (element.marketFaceElement) {
|
}
|
||||||
|
else if (element.marketFaceElement) {
|
||||||
message_data['type'] = OB11MessageDataType.mface
|
message_data['type'] = OB11MessageDataType.mface
|
||||||
message_data['data']['summary'] = element.marketFaceElement.faceName
|
message_data['data']['summary'] = element.marketFaceElement.faceName
|
||||||
const md5 = element.marketFaceElement.emojiId
|
const md5 = element.marketFaceElement.emojiId
|
||||||
@@ -249,11 +270,13 @@ export class OB11Constructor {
|
|||||||
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId
|
message_data['data']['emoji_id'] = element.marketFaceElement.emojiId
|
||||||
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId)
|
message_data['data']['emoji_package_id'] = String(element.marketFaceElement.emojiPackageId)
|
||||||
message_data['data']['key'] = element.marketFaceElement.key
|
message_data['data']['key'] = element.marketFaceElement.key
|
||||||
mFaceCache.set(md5, element.marketFaceElement.faceName);
|
mFaceCache.set(md5, element.marketFaceElement.faceName)
|
||||||
} else if (element.markdownElement) {
|
}
|
||||||
|
else if (element.markdownElement) {
|
||||||
message_data['type'] = OB11MessageDataType.markdown
|
message_data['type'] = OB11MessageDataType.markdown
|
||||||
message_data['data']['data'] = element.markdownElement.content
|
message_data['data']['data'] = element.markdownElement.content
|
||||||
} else if (element.multiForwardMsgElement) {
|
}
|
||||||
|
else if (element.multiForwardMsgElement) {
|
||||||
message_data['type'] = OB11MessageDataType.forward
|
message_data['type'] = OB11MessageDataType.forward
|
||||||
message_data['data']['id'] = msg.msgId
|
message_data['data']['id'] = msg.msgId
|
||||||
}
|
}
|
||||||
@@ -261,7 +284,8 @@ export class OB11Constructor {
|
|||||||
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)
|
||||||
|
|
||||||
resMsg.raw_message += cqCode
|
resMsg.raw_message += cqCode
|
||||||
}
|
}
|
||||||
@@ -310,7 +334,8 @@ export class OB11Constructor {
|
|||||||
// log("构造群增加事件", event)
|
// log("构造群增加事件", event)
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
} else if (groupElement.type === TipGroupElementType.ban) {
|
}
|
||||||
|
else if (groupElement.type === TipGroupElementType.ban) {
|
||||||
log('收到群群员禁言提示', groupElement)
|
log('收到群群员禁言提示', groupElement)
|
||||||
const memberUid = groupElement.shutUp.member.uid
|
const memberUid = groupElement.shutUp.member.uid
|
||||||
const adminUid = groupElement.shutUp.admin.uid
|
const adminUid = groupElement.shutUp.admin.uid
|
||||||
@@ -321,7 +346,8 @@ export class OB11Constructor {
|
|||||||
memberUin =
|
memberUin =
|
||||||
(await getGroupMember(msg.peerUid, memberUid))?.uin ||
|
(await getGroupMember(msg.peerUid, memberUid))?.uin ||
|
||||||
(await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
|
(await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
memberUin = '0' // 0表示全员禁言
|
memberUin = '0' // 0表示全员禁言
|
||||||
if (duration > 0) {
|
if (duration > 0) {
|
||||||
duration = -1
|
duration = -1
|
||||||
@@ -338,7 +364,8 @@ export class OB11Constructor {
|
|||||||
sub_type,
|
sub_type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (groupElement.type == TipGroupElementType.kicked) {
|
}
|
||||||
|
else if (groupElement.type == TipGroupElementType.kicked) {
|
||||||
log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
|
log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
|
||||||
deleteGroup(msg.peerUid)
|
deleteGroup(msg.peerUid)
|
||||||
NTQQGroupApi.quitGroup(msg.peerUid).then()
|
NTQQGroupApi.quitGroup(msg.peerUid).then()
|
||||||
@@ -358,7 +385,8 @@ export class OB11Constructor {
|
|||||||
return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave')
|
return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, 'leave')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (element.fileElement) {
|
}
|
||||||
|
else if (element.fileElement) {
|
||||||
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
|
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
|
||||||
id: element.fileElement.fileUuid,
|
id: element.fileElement.fileUuid,
|
||||||
name: element.fileElement.fileName,
|
name: element.fileElement.fileName,
|
||||||
@@ -423,7 +451,8 @@ export class OB11Constructor {
|
|||||||
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite')
|
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), 'invite')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
|
}
|
||||||
|
else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
|
||||||
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
|
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
@@ -463,14 +492,41 @@ export class OB11Constructor {
|
|||||||
|
|
||||||
static async FriendAddEvent(msg: RawMessage): Promise<OB11FriendAddNoticeEvent | undefined> {
|
static async FriendAddEvent(msg: RawMessage): Promise<OB11FriendAddNoticeEvent | undefined> {
|
||||||
if (msg.chatType !== ChatType.friend) {
|
if (msg.chatType !== ChatType.friend) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
if (msg.msgType === 5 && msg.subMsgType === 12) {
|
if (msg.msgType === 5 && msg.subMsgType === 12) {
|
||||||
const event = new OB11FriendAddNoticeEvent(parseInt(msg.peerUin));
|
const event = new OB11FriendAddNoticeEvent(parseInt(msg.peerUin))
|
||||||
return event;
|
return event
|
||||||
}
|
}
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async RecallEvent(
|
||||||
|
msg: RawMessage,
|
||||||
|
): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> {
|
||||||
|
let msgElement = msg.elements.find(
|
||||||
|
(element) => element.grayTipElement?.subElementType === GrayTipElementSubType.RECALL,
|
||||||
|
)
|
||||||
|
if (!msgElement) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const isGroup = msg.chatType === ChatType.group
|
||||||
|
const revokeElement = msgElement.grayTipElement.revokeElement
|
||||||
|
if (isGroup) {
|
||||||
|
const operator = await getGroupMember(msg.peerUid, revokeElement.operatorUid)
|
||||||
|
const sender = await getGroupMember(msg.peerUid, revokeElement.origMsgSenderUid)
|
||||||
|
return new OB11GroupRecallNoticeEvent(
|
||||||
|
parseInt(msg.peerUid),
|
||||||
|
parseInt(sender.uin),
|
||||||
|
parseInt(operator.uin),
|
||||||
|
msg.msgShortId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new OB11FriendRecallNoticeEvent(parseInt(msg.senderUin), msg.msgShortId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static friend(friend: User): OB11User {
|
static friend(friend: User): OB11User {
|
||||||
return {
|
return {
|
||||||
user_id: parseInt(friend.uin),
|
user_id: parseInt(friend.uin),
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
|
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent'
|
||||||
|
|
||||||
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
|
export class OB11FriendAddNoticeEvent extends OB11BaseNoticeEvent {
|
||||||
notice_type = 'friend_add';
|
notice_type = 'friend_add'
|
||||||
user_id: number;
|
user_id: number
|
||||||
|
|
||||||
public constructor(userId: number) {
|
public constructor(userId: number) {
|
||||||
super();
|
super()
|
||||||
this.user_id = userId;
|
this.user_id = userId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
import { Response } from 'express'
|
import { Response } from 'express'
|
||||||
import { OB11Response } from '../action/OB11Response'
|
import { OB11Response } from '../action/OB11Response'
|
||||||
import { HttpServerBase } from '../../common/server/http'
|
import { HttpServerBase } from '@/common/server/http'
|
||||||
import { actionHandlers, actionMap } from '../action'
|
import { actionHandlers, actionMap } from '../action'
|
||||||
import { getConfigUtil } from '../../common/config'
|
import { getConfigUtil } from '@/common/config'
|
||||||
import { postOB11Event } from './postOB11Event'
|
import { postOB11Event } from './postOB11Event'
|
||||||
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
|
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent'
|
||||||
import { selfInfo } from '../../common/data'
|
import { selfInfo } from '@/common/data'
|
||||||
|
|
||||||
class OB11HTTPServer extends HttpServerBase {
|
class OB11HTTPServer extends HttpServerBase {
|
||||||
name = 'OneBot V11 server'
|
name = 'LLOneBot server'
|
||||||
|
|
||||||
handleFailed(res: Response, payload: any, e: any) {
|
handleFailed(res: Response, payload: any, e: any) {
|
||||||
res.send(OB11Response.error(e.stack.toString(), 200))
|
res.send(OB11Response.error(e.stack.toString(), 200))
|
||||||
|
@@ -75,6 +75,7 @@ export interface OB11Message {
|
|||||||
self_id?: number
|
self_id?: number
|
||||||
time: number
|
time: number
|
||||||
message_id: number
|
message_id: number
|
||||||
|
message_seq: number // go-cqhttp字段,实际上是message_id
|
||||||
real_id: number
|
real_id: number
|
||||||
user_id: number
|
user_id: number
|
||||||
group_id?: number
|
group_id?: number
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const version = '3.24.0'
|
export const version = '3.24.4'
|
||||||
|
@@ -1,12 +1,18 @@
|
|||||||
import http from 'https'
|
import http from 'https'
|
||||||
|
|
||||||
function checkUrl(imageUrl) {
|
function checkUrl(imageUrl) {
|
||||||
http.get(imageUrl, response => {
|
http
|
||||||
console.log(response.statusCode)
|
.get(imageUrl, (response) => {
|
||||||
}).on('error', e => {
|
console.log(response.statusCode)
|
||||||
console.log(e)
|
})
|
||||||
})
|
.on('error', (e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUrl('https://gchat.qpic.cn/download?appid=1407&fileid=CgoxMzMyNTI0MjIxEhRrdaUgQP5MjweWa4uR8pviUDaGQhjcxQUg_wooiYTj39fphQNQgL2jAQ&spec=0&rkey=CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64')
|
checkUrl(
|
||||||
checkUrl('https://multimedia.nt.qq.com.cn/download?appid=1407&fileid=CgoxMzMyNTI0MjIxEhRrdaUgQP5MjweWa4uR8pviUDaGQhjcxQUg_wooiYTj39fphQNQgL2jAQ&spec=0&rkey=CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64')
|
'https://gchat.qpic.cn/download?appid=1407&fileid=CgoxMzMyNTI0MjIxEhRrdaUgQP5MjweWa4uR8pviUDaGQhjcxQUg_wooiYTj39fphQNQgL2jAQ&spec=0&rkey=CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64',
|
||||||
|
)
|
||||||
|
checkUrl(
|
||||||
|
'https://multimedia.nt.qq.com.cn/download?appid=1407&fileid=CgoxMzMyNTI0MjIxEhRrdaUgQP5MjweWa4uR8pviUDaGQhjcxQUg_wooiYTj39fphQNQgL2jAQ&spec=0&rkey=CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64',
|
||||||
|
)
|
||||||
|
@@ -7,8 +7,21 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"moduleResolution": "node"
|
"experimentalDecorators": true,
|
||||||
// "sourceMap": true
|
"resolveJsonModule": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"@/common/*": [
|
||||||
|
"./src/common/*"
|
||||||
|
],
|
||||||
|
"@/onebot11/*": [
|
||||||
|
"./src/onebot11"
|
||||||
|
],
|
||||||
|
"@/ntqqapi/*": [
|
||||||
|
"./src/ntqqapi/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"noEmit": true
|
||||||
},
|
},
|
||||||
"include": ["src/*", "src/**/*", "scripts/*"],
|
"include": ["src/*", "src/**/*", "scripts/*"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
Reference in New Issue
Block a user