mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
914136b750 | ||
![]() |
f9a60795f5 | ||
![]() |
19640927c7 | ||
![]() |
22faac7e36 | ||
![]() |
30d260ab32 | ||
![]() |
115120d066 | ||
![]() |
1327844736 | ||
![]() |
29904f3cb7 | ||
![]() |
50395594b7 | ||
![]() |
9360af88b3 | ||
![]() |
376370336c | ||
![]() |
70df6e3302 | ||
![]() |
0a1fc2dc12 | ||
![]() |
9857f6e437 | ||
![]() |
56d6ebe916 | ||
![]() |
81134ea2d4 | ||
![]() |
a9f3e7fc54 | ||
![]() |
eb84e2f8c9 |
@@ -1,21 +1,21 @@
|
|||||||
# EditorConfig is awesome: https://EditorConfig.org
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
# top-most EditorConfig file
|
# top-most EditorConfig file
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
# Unix-style newlines with a newline ending every file
|
# Unix-style newlines with a newline ending every file
|
||||||
[*]
|
[*]
|
||||||
end_of_line = lf|crlf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
# Matches multiple files with brace expansion notation
|
# Matches multiple files with brace expansion notation
|
||||||
# Set default charset
|
# Set default charset
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
# 2 space indentation
|
# 2 space indentation
|
||||||
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
[*.{cjs,mjs,js,jsx,ts,tsx,css,scss,sass,html,json}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
# Unfortunately, EditorConfig doesn't support space configuration inside import braces directly.
|
||||||
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
# You'll need to rely on your linter/formatter like ESLint or Prettier for that.
|
||||||
|
18
docs/changelogs/CHANGELOG.v1.6.5.md
Normal file
18
docs/changelogs/CHANGELOG.v1.6.5.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# v1.6.5
|
||||||
|
|
||||||
|
QQ Version: Windows 9.9.12-26000 / Linux 3.2.9-26000
|
||||||
|
## 使用前警告
|
||||||
|
1. 在最近版本由于QQ本体大幅变动,为了保证NapCat可用性,NapCat近期启动与安装方式将将大幅变动,请关注文档和社群获取。
|
||||||
|
2. 在Core上完全执行开源,请不要用于违法用途,如此可能造成NapCat完全停止更新。
|
||||||
|
3. 针对原启动方式的围堵,NapCat研发了多种方式,除此其余理论与扩展的分析和思路将部分展示于Docs,以便各位参与开发与维护NapCat。
|
||||||
|
## 其余·备注
|
||||||
|
启动方式: WayBoot.03 (Electron Main进程为Node 直接注入代码 同理项目: LiteLoader)
|
||||||
|
|
||||||
|
## 修复与优化
|
||||||
|
1. 优化了WrapperNative载入代码
|
||||||
|
2. 优化缓存
|
||||||
|
|
||||||
|
## 新增与调整
|
||||||
|
没有哦
|
||||||
|
|
||||||
|
新增的 API 详细见[API文档](https://napneko.github.io/zh-CN/develop/extends_api)
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.6.4",
|
"version": "1.6.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch:dev": "vite --mode development",
|
"watch:dev": "vite --mode development",
|
||||||
"watch:prod": "vite --mode production",
|
"watch:prod": "vite --mode production",
|
||||||
|
28
script/NapCat.164.bat
Normal file
28
script/NapCat.164.bat
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001
|
||||||
|
:: 检查是否有管理员权限
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo 请求管理员权限...
|
||||||
|
powershell -Command "Start-Process '%~f0' -Verb runAs"
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
:: 如果有管理员权限,继续执行
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
:loop_read
|
||||||
|
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QQ" /v "UninstallString"') do (
|
||||||
|
set "RetString=%%b"
|
||||||
|
goto :napcat_boot
|
||||||
|
)
|
||||||
|
|
||||||
|
:napcat_boot
|
||||||
|
for %%a in ("!RetString!") do (
|
||||||
|
set "pathWithoutUninstall=%%~dpa"
|
||||||
|
)
|
||||||
|
|
||||||
|
set "QQPath=!pathWithoutUninstall!QQ.exe"
|
||||||
|
|
||||||
|
echo !QQPath!
|
||||||
|
"!QQPath!" --enable-logging %*
|
||||||
|
|
||||||
|
pause
|
@@ -1,4 +1,3 @@
|
|||||||
import { NodeIKernelMsgListener } from '@/core';
|
|
||||||
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
import { NodeIQQNTWrapperSession } from '@/core/wrapper';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { logError, logDebug } from '@/common/utils/log';
|
import { logError, logDebug } from "@/common/utils/log";
|
||||||
|
|
||||||
type group_id = number;
|
type group_id = number;
|
||||||
type user_id = number;
|
type user_id = number;
|
||||||
@@ -31,7 +31,7 @@ class LRU<T> {
|
|||||||
private tail: cacheNode<T> | null = null;
|
private tail: cacheNode<T> | null = null;
|
||||||
private onFuncs: ((node: cacheNode<T>) => void)[] = [];
|
private onFuncs: ((node: cacheNode<T>) => void)[] = [];
|
||||||
|
|
||||||
constructor(maxAge: number = 2e4, maxSize: number = 5e3) {
|
constructor(maxAge: number = 6e4, maxSize: number = 5e3) {
|
||||||
this.maxAge = maxAge;
|
this.maxAge = maxAge;
|
||||||
this.maxSize = maxSize;
|
this.maxSize = maxSize;
|
||||||
this.cache = Object.create(null);
|
this.cache = Object.create(null);
|
||||||
@@ -44,7 +44,7 @@ class LRU<T> {
|
|||||||
// 移除LRU节点
|
// 移除LRU节点
|
||||||
private removeLRUNode(node: cacheNode<T>) {
|
private removeLRUNode(node: cacheNode<T>) {
|
||||||
logDebug(
|
logDebug(
|
||||||
'removeLRUNode',
|
"removeLRUNode",
|
||||||
node.groupId,
|
node.groupId,
|
||||||
node.userId,
|
node.userId,
|
||||||
node.value,
|
node.value,
|
||||||
@@ -140,6 +140,26 @@ class LRU<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public get(groupId: group_id): { userId: user_id; value: T }[];
|
||||||
|
public get(groupId: group_id, userId: user_id): null | { userId: user_id; value: T };
|
||||||
|
public get(groupId: group_id, userId?: user_id): any {
|
||||||
|
const groupObject = this.cache[groupId];
|
||||||
|
if(!groupObject) return userId === undefined ? [] : null;
|
||||||
|
|
||||||
|
if (userId === undefined) {
|
||||||
|
return Object.entries(groupObject).map(([userId, { value }]) => ({
|
||||||
|
userId: Number(userId),
|
||||||
|
value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupObject[userId]) {
|
||||||
|
return { userId, value: groupObject[userId].value };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LRU;
|
export default LRU;
|
||||||
|
@@ -408,10 +408,12 @@ class DBUtil extends DBUtilBase {
|
|||||||
logDebug('读取发言时间', groupId);
|
logDebug('读取发言时间', groupId);
|
||||||
return new Promise<IRember[]>((resolve, reject) => {
|
return new Promise<IRember[]>((resolve, reject) => {
|
||||||
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
|
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
|
||||||
|
const cache = this.LURCache.get(groupId).map(e=>({user_id:e.userId, last_sent_time:e.value}));
|
||||||
if (err) {
|
if (err) {
|
||||||
logError('查询发言时间失败', groupId);
|
logError('查询发言时间失败', groupId);
|
||||||
return resolve([]);
|
return resolve(cache.map(e=>({...e, join_time:0})));
|
||||||
}
|
}
|
||||||
|
Object.assign(rows, cache)
|
||||||
logDebug('查询发言时间成功', groupId, rows);
|
logDebug('查询发言时间成功', groupId, rows);
|
||||||
resolve(rows);
|
resolve(rows);
|
||||||
});
|
});
|
||||||
|
@@ -160,7 +160,12 @@ export async function httpDownload(options: string | HttpDownloadOptions): Promi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const fetchRes = await fetch(url, { headers });
|
const fetchRes = await fetch(url, { headers }).catch((err) => {
|
||||||
|
if (err.cause) {
|
||||||
|
throw err.cause;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
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();
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities';
|
import { ModifyProfileParams, SelfInfo, User, UserDetailInfoByUin } from '@/core/entities';
|
||||||
import { selfInfo } from '@/core/data';
|
import { friends, selfInfo } from '@/core/data';
|
||||||
import { CacheClassFuncAsync } from '@/common/utils/helper';
|
import { CacheClassFuncAsync } from '@/common/utils/helper';
|
||||||
import { GeneralCallResult, napCatCore } from '@/core';
|
import { GeneralCallResult, napCatCore, NTQQFriendApi } from '@/core';
|
||||||
import { ProfileListener } from '@/core/listeners';
|
import { ProfileListener } from '@/core/listeners';
|
||||||
import { rejects } from 'assert';
|
import { rejects } from 'assert';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
@@ -170,7 +170,30 @@ export class NTQQUserApi {
|
|||||||
5000,
|
5000,
|
||||||
[Uin]
|
[Uin]
|
||||||
);
|
);
|
||||||
return ret.uidInfo.get(Uin);
|
let uid = ret.uidInfo.get(Uin); //通过QQ默认方式转换
|
||||||
|
if (!uid) {
|
||||||
|
Array.from(friends.values()).forEach((t) => {
|
||||||
|
if (t.uin == Uin) {
|
||||||
|
//logDebug('getUidByUin', t.uid, t.uin, Uin);
|
||||||
|
uid = t.uid;
|
||||||
|
}
|
||||||
|
//console.log(t.uid, t.uin, Uin);
|
||||||
|
});
|
||||||
|
//uid = Array.from(friends.values()).find((t) => { t.uin == Uin })?.uid; // 从NC维护的QQ Buddy缓存 转换
|
||||||
|
}
|
||||||
|
if (!uid) {
|
||||||
|
uid = (await NTQQFriendApi.getFriends(false)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 缓存转换 方法一
|
||||||
|
}
|
||||||
|
if (!uid) {
|
||||||
|
uid = (await NTQQFriendApi.getFriends(true)).find((t) => { t.uin == Uin })?.uid; //从QQ Native 非缓存转换 方法二
|
||||||
|
}
|
||||||
|
if (!uid) {
|
||||||
|
let unveifyUid = (await NTQQUserApi.getUserDetailInfoByUin(Uin)).info.uid;//从QQ Native 特殊转换 方法三
|
||||||
|
if (unveifyUid.indexOf("*") == -1) {
|
||||||
|
uid = unveifyUid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uid;
|
||||||
}
|
}
|
||||||
static async getUinByUid(Uid: string | undefined) {
|
static async getUinByUid(Uid: string | undefined) {
|
||||||
if (!Uid) {
|
if (!Uid) {
|
||||||
|
@@ -171,7 +171,7 @@ export interface NodeIQQNTWrapperSession {
|
|||||||
getTicketService(): NodeIKernelTicketService;
|
getTicketService(): NodeIKernelTicketService;
|
||||||
|
|
||||||
getTipOffService(): NodeIKernelTipOffService;
|
getTipOffService(): NodeIKernelTipOffService;
|
||||||
|
|
||||||
getNodeMiscService(): NodeIKernelNodeMiscService;
|
getNodeMiscService(): NodeIKernelNodeMiscService;
|
||||||
|
|
||||||
getRichMediaService(): NodeIKernelRichMediaService;
|
getRichMediaService(): NodeIKernelRichMediaService;
|
||||||
@@ -286,11 +286,7 @@ let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/
|
|||||||
if (!fs.existsSync(wrapperNodePath)) {
|
if (!fs.existsSync(wrapperNodePath)) {
|
||||||
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${qqVersionConfigInfo.curVersion}/wrapper.node`);
|
wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${qqVersionConfigInfo.curVersion}/wrapper.node`);
|
||||||
}
|
}
|
||||||
let WrapperLoader = path.join(__dirname, "WrapperLoader.cjs");
|
const nativemodule: any = { exports: {} };
|
||||||
//此处待优化
|
process.dlopen(nativemodule, wrapperNodePath);
|
||||||
fs.writeFileSync(WrapperLoader, `
|
const QQWrapper: WrapperNodeApi = nativemodule.exports;
|
||||||
module.exports = require("${wrapperNodePath.replace(/\\/g, "\\\\")}");
|
|
||||||
exports = module.exports;
|
|
||||||
`)
|
|
||||||
const QQWrapper: WrapperNodeApi = (await import("file://" + WrapperLoader)).default;
|
|
||||||
export default QQWrapper;
|
export default QQWrapper;
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
//统一到处入口
|
|
||||||
export * from './main';
|
|
@@ -1,29 +0,0 @@
|
|||||||
enum NapCatCorePlatform {
|
|
||||||
Node = 'Node',//命令行模式加载
|
|
||||||
LiteLoader = 'LiteLoader',//LL插件模式加载
|
|
||||||
}
|
|
||||||
class NewNapCatCore {
|
|
||||||
platform: NapCatCorePlatform; // 平台
|
|
||||||
constructor(platform: NapCatCorePlatform) {
|
|
||||||
this.platform = platform;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class NapCatCoreManger {
|
|
||||||
static core: NewNapCatCore | undefined = undefined;
|
|
||||||
static defaultPlatform: NapCatCorePlatform = NapCatCorePlatform.Node;
|
|
||||||
static SetDefaultCore(platform: NapCatCorePlatform) {
|
|
||||||
if (this.core !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.defaultPlatform = platform;
|
|
||||||
}
|
|
||||||
static GetPlatform(): NapCatCorePlatform {
|
|
||||||
return NapCatCoreManger.defaultPlatform;
|
|
||||||
}
|
|
||||||
static GetInstance(): NewNapCatCore {
|
|
||||||
if (this.core === undefined) {
|
|
||||||
this.core = new NewNapCatCore(NapCatCoreManger.defaultPlatform);
|
|
||||||
}
|
|
||||||
return this.core;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
//拦截proxy到会话
|
|
@@ -1 +0,0 @@
|
|||||||
//初始化跟之前一样
|
|
@@ -1 +0,0 @@
|
|||||||
//proxy封装工具类
|
|
@@ -102,6 +102,7 @@ async function createContext(payload: OB11PostSendMsg, contextMode: ContextMode)
|
|||||||
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
|
if ((contextMode === ContextMode.Private || contextMode === ContextMode.Normal) && payload.user_id) {
|
||||||
const Uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
|
const Uid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
|
||||||
const isBuddy = await NTQQFriendApi.isBuddy(Uid!);
|
const isBuddy = await NTQQFriendApi.isBuddy(Uid!);
|
||||||
|
//console.log("[调试代码] UIN:", payload.user_id, " UID:", Uid, " IsBuddy:", isBuddy);
|
||||||
return {
|
return {
|
||||||
peer: {
|
peer: {
|
||||||
chatType: isBuddy ? ChatType.friend : ChatType.temp,
|
chatType: isBuddy ? ChatType.friend : ChatType.temp,
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const version = '1.6.4';
|
export const version = '1.6.5';
|
||||||
|
@@ -29,7 +29,7 @@ async function onSettingWindowCreated(view: Element) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
undefined,
|
undefined,
|
||||||
SettingButton('V1.6.4', 'napcat-update-button', 'secondary')
|
SettingButton('V1.6.5', 'napcat-update-button', 'secondary')
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
@@ -167,7 +167,7 @@ async function onSettingWindowCreated(view) {
|
|||||||
SettingItem(
|
SettingItem(
|
||||||
'<span id="napcat-update-title">Napcat</span>',
|
'<span id="napcat-update-title">Napcat</span>',
|
||||||
void 0,
|
void 0,
|
||||||
SettingButton("V1.6.4", "napcat-update-button", "secondary")
|
SettingButton("V1.6.5", "napcat-update-button", "secondary")
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
SettingList([
|
SettingList([
|
||||||
|
Reference in New Issue
Block a user