Compare commits

...

81 Commits

Author SHA1 Message Date
手瓜一十雪
e3814403e4 fix: #682 2025-01-07 19:28:46 +08:00
手瓜一十雪
3d16d52dd8 fix: #696 fallback 2025-01-07 18:51:04 +08:00
Mlikiowa
1ae47fffb4 release: v4.2.68 2025-01-06 13:08:43 +00:00
手瓜一十雪
4e7096b9e2 feat: offset win 31219 2025-01-06 21:08:18 +08:00
Mlikiowa
8cc9b7f6a7 release: v4.2.67 2025-01-05 02:50:38 +00:00
手瓜一十雪
fb45c1020e revert: 再也不用umami了 2025-01-05 10:49:49 +08:00
Mlikiowa
e9db4ae8f4 release: v4.2.66 2025-01-04 11:52:38 +00:00
手瓜一十雪
c46ec32bd6 fix: 简化代码 2025-01-04 13:12:04 +08:00
手瓜一十雪
c58a26ed99 fix 2025-01-04 13:06:37 +08:00
手瓜一十雪
a66f5e4971 fix 2025-01-04 13:03:29 +08:00
pk5ls20
574c8c6089 fix: SendGroupAiRecord
- smtx
2025-01-04 12:48:53 +08:00
手瓜一十雪
67afd95910 feat: getUserDetailInfoV2 2025-01-03 21:38:57 +08:00
手瓜一十雪
f7d0cb0be7 fix 2025-01-03 21:07:26 +08:00
手瓜一十雪
be9b68a0b1 feat: CancelableTask&Fallback 2025-01-03 20:46:51 +08:00
Mlikiowa
4637414af2 release: v4.2.65 2025-01-03 05:14:05 +00:00
手瓜一十雪
4bd92a72bd fix: ua agent 2025-01-03 13:09:43 +08:00
手瓜一十雪
a3be26f3e4 fix: error 2025-01-03 12:44:55 +08:00
Mlikiowa
675c906cbf release: v4.2.64 2024-12-31 14:42:17 +00:00
手瓜一十雪
6be6023236 Merge pull request #679 from wu-yafeng/main
fix #678
2024-12-31 22:41:34 +08:00
WuYafeng
42cee0d018 在没有写入权限时记录warn日志 2024-12-31 22:38:01 +08:00
WuYafeng
041f725748 Update config.ts 2024-12-31 22:25:05 +08:00
WuYafeng
0594d61631 Update config.ts 2024-12-31 22:20:34 +08:00
Mlikiowa
15cae6b765 release: v4.2.63 2024-12-31 13:52:37 +00:00
pk5ls20
b984116c35 fix: #677 2024-12-31 21:34:45 +08:00
Mlikiowa
13bda6e3f4 release: v4.2.62 2024-12-31 08:35:55 +00:00
手瓜一十雪
c0d18549d1 fix 2024-12-31 15:09:08 +08:00
手瓜一十雪
3caff72fce fix: cache trace 2024-12-31 13:10:58 +08:00
Mlikiowa
1313e9c3f4 release: v4.2.61 2024-12-30 15:59:33 +00:00
手瓜一十雪
0848d5a39e feat: clouflare 2024-12-30 23:59:09 +08:00
手瓜一十雪
7660646059 fix 2024-12-30 22:16:09 +08:00
Mlikiowa
bcd90fc744 release: v4.2.60 2024-12-30 12:48:11 +00:00
手瓜一十雪
638fc22d62 fix:error 2024-12-30 20:47:50 +08:00
手瓜一十雪
c87d365b88 fix 2024-12-30 20:42:23 +08:00
Mlikiowa
aee9602f25 release: v4.2.59 2024-12-30 12:29:33 +00:00
手瓜一十雪
976fbd0220 fix: 修复精确统计 2024-12-30 20:27:28 +08:00
手瓜一十雪
afd955d06f fix: error 2024-12-30 20:14:51 +08:00
手瓜一十雪
4d548da66b fix: console-log 2024-12-30 19:56:06 +08:00
手瓜一十雪
41b70f53d1 fix: LoginErrorCode 2024-12-30 19:29:14 +08:00
Wesley F. Young
a47a618bcd make author NapNeko in manifest.json
毕竟我已经脱离一线开发挺久了(
2024-12-30 18:40:57 +08:00
Mlikiowa
62170a30af release: v4.2.58 2024-12-30 09:56:02 +00:00
手瓜一十雪
780c5ac23c fix 2024-12-30 17:55:40 +08:00
手瓜一十雪
9fba519a5a fix 2024-12-30 17:55:13 +08:00
Mlikiowa
3cd0e7d26b release: v4.2.57 2024-12-30 09:54:40 +00:00
手瓜一十雪
a8fd6af994 Merge pull request #676 from NapNeko/dependabot/npm_and_yarn/commander-13.0.0
chore(deps-dev): bump commander from 12.1.0 to 13.0.0
2024-12-30 17:54:03 +08:00
手瓜一十雪
4000b89644 fix 2024-12-30 17:53:05 +08:00
Mlikiowa
9c00bbc0b7 release: v4.2.56 2024-12-30 08:55:36 +00:00
手瓜一十雪
a2989d3b38 fix:workname 2024-12-30 16:54:40 +08:00
Mlikiowa
fc731b60d5 release: v4.2.54 2024-12-30 08:47:57 +00:00
手瓜一十雪
193980dd4a fix: trace 2024-12-30 16:47:33 +08:00
dependabot[bot]
35427b0768 chore(deps-dev): bump commander from 12.1.0 to 13.0.0
Bumps [commander](https://github.com/tj/commander.js) from 12.1.0 to 13.0.0.
- [Release notes](https://github.com/tj/commander.js/releases)
- [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tj/commander.js/compare/v12.1.0...v13.0.0)

---
updated-dependencies:
- dependency-name: commander
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-30 08:36:27 +00:00
Mlikiowa
73ea130e40 release: v4.2.53 2024-12-30 08:34:23 +00:00
手瓜一十雪
5667e6aaee fix 2024-12-30 16:34:01 +08:00
Mlikiowa
fbd626131d release: v4.2.52 2024-12-30 08:17:53 +00:00
手瓜一十雪
7b82444338 fix 2024-12-30 16:17:29 +08:00
Mlikiowa
8108b9f565 release: v4.2.51 2024-12-30 08:15:48 +00:00
手瓜一十雪
c6ddd00cd9 Merge pull request #675 from NapNeko/test
fix: 重写远程遥测
2024-12-30 16:14:48 +08:00
手瓜一十雪
20c0c00fa0 fixfix 2024-12-30 16:14:32 +08:00
手瓜一十雪
1f90364ba6 fix: 重写远程遥测 2024-12-30 16:10:38 +08:00
手瓜一十雪
49ea4d31a5 fix 2024-12-30 13:23:33 +08:00
Mlikiowa
dc35f1456a release: v4.2.50 2024-12-30 04:33:08 +00:00
手瓜一十雪
0ebeb90804 fix 2024-12-30 12:31:43 +08:00
手瓜一十雪
3ef5436c98 Merge pull request #673 from NapNeko/fixseq
fix: #670
2024-12-30 12:28:29 +08:00
手瓜一十雪
de7996d789 refactor: 进群入群 2024-12-30 12:27:42 +08:00
手瓜一十雪
ac52d9bae2 fix: 算了能用就行 2024-12-30 11:59:16 +08:00
手瓜一十雪
cb02df3b76 Merge pull request #672 from clansty/feat/download_file-file-proto
feat: download_file 支持 file://
2024-12-29 22:45:27 +08:00
手瓜一十雪
5fc5a6f1a6 fix: #670 2024-12-29 22:44:25 +08:00
手瓜一十雪
726a0d0394 fix 2024-12-29 22:30:33 +08:00
Clansty
6edf5345a3 chore: remove unused import 2024-12-29 22:27:41 +08:00
Clansty
242bbfdb14 chore: uriToLocalFile 2024-12-29 22:03:04 +08:00
Clansty
89e7712676 feat: download_file 支持 file:// 2024-12-29 21:41:44 +08:00
手瓜一十雪
9525786929 fix 2024-12-29 16:20:41 +08:00
Mlikiowa
72088e41a8 release: v4.2.46 2024-12-29 07:47:38 +00:00
手瓜一十雪
a3ed9ff2ef Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-12-29 15:47:18 +08:00
手瓜一十雪
ff16dc73ec fix: typo 2024-12-29 15:47:16 +08:00
Mlikiowa
2da4ef5f0f release: v4.2.45 2024-12-29 07:46:08 +00:00
手瓜一十雪
eaf481799d feat: 追踪退出 2024-12-29 15:44:37 +08:00
手瓜一十雪
1f72863aba fix: 追踪登录失败 2024-12-29 15:25:51 +08:00
Mlikiowa
6b353fd8d8 release: v4.2.44 2024-12-29 06:45:43 +00:00
手瓜一十雪
56cde4ad79 fix: #669 2024-12-29 14:44:47 +08:00
手瓜一十雪
3b86d3c632 feat: umami 统计追踪 2024-12-29 14:39:17 +08:00
Mlikiowa
4ac7a25afb release: v4.2.43 2024-12-29 04:04:55 +00:00
35 changed files with 634 additions and 143 deletions

View File

@@ -4,16 +4,12 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.2.42",
"version": "4.2.68",
"icon": "./logo.png",
"authors": [
{
"name": "MliKiowa",
"link": "https://github.com/MliKiowa"
},
{
"name": "Young",
"link": "https://github.com/Wesley-Young"
"name": "NapNeko",
"link": "https://github.com/NapNeko"
}
],
"repository": {

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.2.42",
"version": "4.2.68",
"scripts": {
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
@@ -37,7 +37,7 @@
"@typescript-eslint/parser": "^8.3.0",
"ajv": "^8.13.0",
"async-mutex": "^0.5.0",
"commander": "^12.1.0",
"commander": "^13.0.0",
"cors": "^2.8.5",
"eslint": "^9.14.0",
"eslint-import-resolver-typescript": "^3.6.1",

107
src/common/cancel-task.ts Normal file
View File

@@ -0,0 +1,107 @@
export type TaskExecutor<T> = (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void, onCancel: (callback: () => void) => void) => void;
export class CancelableTask<T> {
private promise: Promise<T>;
private cancelCallback: (() => void) | null = null;
private isCanceled = false;
private cancelListeners: Array<() => void> = [];
constructor(executor: TaskExecutor<T>) {
this.promise = new Promise<T>((resolve, reject) => {
const onCancel = (callback: () => void) => {
this.cancelCallback = callback;
};
executor(
(value) => {
if (!this.isCanceled) {
resolve(value);
}
},
(reason) => {
if (!this.isCanceled) {
reject(reason);
}
},
onCancel
);
});
}
public cancel() {
if (this.cancelCallback) {
this.cancelCallback();
}
this.isCanceled = true;
this.cancelListeners.forEach(listener => listener());
}
public isTaskCanceled(): boolean {
return this.isCanceled;
}
public onCancel(listener: () => void) {
this.cancelListeners.push(listener);
}
public then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2> {
return this.promise.then(onfulfilled, onrejected);
}
public catch<TResult = never>(
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
): Promise<T | TResult> {
return this.promise.catch(onrejected);
}
public finally(onfinally?: (() => void) | undefined | null): Promise<T> {
return this.promise.finally(onfinally);
}
[Symbol.asyncIterator]() {
return {
next: () => this.promise.then(value => ({ value, done: true })),
};
}
}
async function demoAwait() {
const executor: TaskExecutor<number> = (resolve, reject, onCancel) => {
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Task is running... Count: ${count}`);
if (count === 5) {
clearInterval(intervalId);
resolve(count);
}
}, 1000);
onCancel(() => {
clearInterval(intervalId);
console.log('Task has been canceled.');
reject(new Error('Task was canceled'));
});
};
const task = new CancelableTask(executor);
task.onCancel(() => {
console.log('Cancel listener triggered.');
});
setTimeout(() => {
task.cancel(); // 取消任务
}, 6000);
try {
const result = await task;
console.log(`Task completed with result: ${result}`);
} catch (error) {
console.error('Task failed:', error);
}
}

22
src/common/decorator.ts Normal file
View File

@@ -0,0 +1,22 @@
// decoratorAsyncMethod(this,function,wrapper)
async function decoratorMethod<T, R>(
target: T,
method: () => Promise<R>,
wrapper: (result: R) => Promise<any>,
executeImmediately: boolean = true
): Promise<any> {
const execute = async () => {
try {
const result = await method.call(target);
return wrapper(result);
} catch (error) {
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
}
};
if (executeImmediately) {
return execute();
} else {
return execute;
}
}

43
src/common/fall-back.ts Normal file
View File

@@ -0,0 +1,43 @@
type Handler<T> = () => T | Promise<T>;
type Checker<T> = (result: T) => T | Promise<T>;
export class Fallback<T> {
private handlers: Handler<T>[] = [];
private checker: Checker<T>;
constructor(checker?: Checker<T>) {
this.checker = checker || (async (result: T) => result);
}
add(handler: Handler<T>): this {
this.handlers.push(handler);
return this;
}
// 执行处理程序链
async run(): Promise<T> {
const errors: Error[] = [];
for (const handler of this.handlers) {
try {
const result = await handler();
let data = await this.checker(result);
if (data) {
return data;
}
} catch (error) {
console.log(error);
errors.push(error instanceof Error ? error : new Error(String(error)));
}
}
throw new AggregateError(errors, 'All handlers failed');
}
}
export class FallbackUtil {
static boolchecker<T>(value: T, condition: boolean): T {
if (condition) {
return value;
} else {
throw new Error('Condition is false, throwing error');
}
}
}

View File

@@ -175,10 +175,9 @@ export async function checkUriType(Uri: string) {
return { Uri: Uri, Type: FileUriType.Unknown };
}
export async function uriToLocalFile(dir: string, uri: string): Promise<Uri2LocalRes> {
export async function uriToLocalFile(dir: string, uri: string, filename: string = randomUUID(), headers?: Record<string, string>): Promise<Uri2LocalRes> {
const { Uri: HandledUri, Type: UriType } = await checkUriType(uri);
const filename = randomUUID();
const filePath = path.join(dir, filename);
switch (UriType) {
@@ -191,7 +190,7 @@ export async function uriToLocalFile(dir: string, uri: string): Promise<Uri2Loca
}
case FileUriType.Remote: {
const buffer = await httpDownload(HandledUri);
const buffer = await httpDownload({ url: HandledUri, headers: headers });
fs.writeFileSync(filePath, buffer, { flag: 'wx' });
return { success: true, errMsg: '', fileName: filename, path: filePath };
}
@@ -206,4 +205,4 @@ export async function uriToLocalFile(dir: string, uri: string): Promise<Uri2Loca
default:
return { success: false, errMsg: `识别URL失败, uri= ${uri}`, fileName: '', path: '' };
}
}
}

165
src/common/umami.ts Normal file
View File

@@ -0,0 +1,165 @@
import https from 'node:https';
import { napCatVersion } from './version';
import os from 'node:os';
export class UmamiTraceCore {
napcatVersion = napCatVersion;
qqversion = '1.0.0';
guid = 'default-user';
heartbeatInterval: NodeJS.Timeout | null = null;
website: string = '596cbbb2-1740-4373-a807-cf3d0637bfa7';
referrer: string = 'https://trace.napneko.icu/';
hostname: string = 'trace.napneko.icu';
ua: string = '';
workname: string = 'default';
bootTime = Date.now();
cache: string = '';
platform = process.platform;
init(qqversion: string, guid: string, workname: string) {
this.qqversion = qqversion;
this.workname = workname;
const UaList = {
linux: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36',
win32: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.2128.93 Safari/537.36',
darwin: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36',
};
try {
if (this.platform === 'win32') {
const ntVersion = os.release();
UaList.win32 = `Mozilla/5.0 (Windows NT ${ntVersion}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.2128.93 Safari/537.36`;
} else if (this.platform === 'darwin') {
const macVersion = os.release();
UaList.darwin = `Mozilla/5.0 (Macintosh; Intel Mac OS X ${macVersion}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36`;
}
} catch (error) {
this.ua = UaList.win32;
}
this.ua = UaList[this.platform as keyof typeof UaList] || UaList.win32;
this.identifyUser(guid);
this.startHeartbeat();
}
identifyUser(guid: string) {
this.guid = guid;
const data = {
napcat_version: this.napcatVersion,
qq_version: this.qqversion,
napcat_working: this.workname,
device_guid: this.guid,
device_platform: this.platform,
device_arch: os.arch(),
boot_time: new Date(this.bootTime + 8 * 60 * 60 * 1000).toISOString().replace('T', ' ').substring(0, 19),
sys_time: new Date(Date.now() - os.uptime() * 1000 + 8 * 60 * 60 * 1000).toISOString().replace('T', ' ').substring(0, 19),
};
this.sendEvent(
{
website: this.website,
hostname: this.hostname,
referrer: this.referrer,
title: 'NapCat ' + this.napcatVersion,
url: `/${this.qqversion}/${this.napcatVersion}/${this.workname}/identify`,
},
data,
'identify'
);
}
sendEvent(event: string | object, data?: object, type = 'event') {
const env = process.env;
const language = env.LANG || env.LANGUAGE || env.LC_ALL || env.LC_MESSAGES;
const payload = {
...(typeof event === 'string' ? { event } : event),
hostname: this.hostname,
referrer: this.referrer,
website: this.website,
language: language || 'en-US',
screen: '1920x1080',
data: {
...data,
},
};
this.sendRequest(payload, type);
}
sendTrace(eventName: string, data: string = '') {
const payload = {
website: this.website,
hostname: this.hostname,
title: 'NapCat ' + this.napcatVersion,
url: `/${this.qqversion}/${this.napcatVersion}/${this.workname}/${eventName}` + (data ? `/${data}` : ''),
referrer: this.referrer,
};
this.sendRequest(payload);
}
sendRequest(payload: object, type = 'event') {
const options = {
hostname: '104.19.42.72', // 固定 IP 或者从 hostUrl 获取
port: 443,
path: '/api/send',
method: 'POST',
headers: {
"Host": "umami.napneko.icu",
"Content-Type": "application/json",
"User-Agent": this.ua,
...(this.cache ? { 'x-umami-cache': this.filterInvalidChars(this.cache) } : {})
}
};
try {
const request = https.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => {
responseData += chunk;
});
res.on('end', () => {
if (!this.cache) {
this.cache = responseData;
console.log('Umami cache:', this.cache);
}
});
res.on('error', (error) => {
});
});
request.on('error', (error) => {
});
request.write(JSON.stringify({ type, payload }));
request.end();
} catch (error) {
}
}
filterInvalidChars(value: string): string {
return value.replace(/[^\x00-\x7F]/g, '');
}
startHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
}
this.heartbeatInterval = setInterval(() => {
this.sendEvent({
name: 'heartbeat',
title: 'NapCat ' + this.napcatVersion,
url: `/${this.qqversion}/${this.napcatVersion}/${this.workname}/heartbeat`,
});
}, 5 * 60 * 1000);
}
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
}
}
export const UmamiTrace = new UmamiTraceCore();

View File

@@ -1 +1 @@
export const napCatVersion = '4.2.42';
export const napCatVersion = '4.2.68';

View File

@@ -18,6 +18,7 @@ export class NTQQGroupApi {
context: InstanceContext;
core: NapCatCore;
groupMemberCache: Map<string, Map<string, GroupMember>> = new Map<string, Map<string, GroupMember>>();
groupMemberCacheEvent: Map<string, boolean> = new Map<string, boolean>();
essenceLRU = new LimitedHashTable<number, string>(1000);
constructor(context: InstanceContext, core: NapCatCore) {
@@ -44,7 +45,7 @@ export class NTQQGroupApi {
async initCache() {
for (const group of await this.getGroups(true)) {
this.refreshGroupMemberCache(group.groupCode).then().catch();
this.refreshGroupMemberCache(group.groupCode, false).then().catch(e => this.context.logger.logError(e));
}
}
@@ -126,14 +127,26 @@ export class NTQQGroupApi {
return this.context.session.getGroupService().getAllMemberList(groupCode, forced);
}
async refreshGroupMemberCache(groupCode: string) {
try {
const members = await this.getGroupMemberAll(groupCode, true);
this.groupMemberCache.set(groupCode, members.result.infos);
} catch (e) {
this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`);
async refreshGroupMemberCache(groupCode: string, isWait = true) {
this.groupMemberCacheEvent.set(groupCode, true);
const updateCache = async () => {
try {
const members = await this.getGroupMemberAll(groupCode, true);
this.groupMemberCache.set(groupCode, members.result.infos);
} catch (e) {
this.context.logger.logError(`刷新群成员缓存失败, 群号: ${groupCode}, 错误: ${e}`);
} finally {
this.groupMemberCacheEvent.set(groupCode, false);
}
};
if (isWait) {
await updateCache();
} else {
updateCache();
}
return this.groupMemberCache;
return this.groupMemberCache.get(groupCode);
}
async getGroupMember(groupCode: string | number, memberUinOrUid: string | number) {
@@ -143,7 +156,7 @@ export class NTQQGroupApi {
// 获取群成员缓存
let members = this.groupMemberCache.get(groupCodeStr);
if (!members) {
members = (await this.refreshGroupMemberCache(groupCodeStr)).get(groupCodeStr);
members = (await this.refreshGroupMemberCache(groupCodeStr, true));
}
const getMember = () => {
@@ -157,7 +170,7 @@ export class NTQQGroupApi {
let member = getMember();
// 如果缓存中不存在该成员,尝试刷新缓存
if (!member) {
members = (await this.refreshGroupMemberCache(groupCodeStr)).get(groupCodeStr);
members = (await this.refreshGroupMemberCache(groupCodeStr, true));
member = getMember();
}
return member;

View File

@@ -2,8 +2,7 @@ import { ModifyProfileParams, User, UserDetailSource } from '@/core/types';
import { RequestUtil } from '@/common/request';
import { InstanceContext, NapCatCore, ProfileBizType } from '..';
import { solveAsyncProblem } from '@/common/helper';
import { promisify } from 'node:util';
import { LRUCache } from '@/common/lru-cache';
import { Fallback, FallbackUtil } from '@/common/fall-back';
export class NTQQUserApi {
context: InstanceContext;
@@ -108,6 +107,19 @@ export class NTQQUserApi {
return retUser;
}
async getUserDetailInfoV2(uid: string): Promise<User> {
const fallback = new Fallback<User>((user) => FallbackUtil.boolchecker(user, user !== undefined && user.uin !== '0'))
.add(() => this.fetchUserDetailInfo(uid, UserDetailSource.KDB))
.add(() => this.fetchUserDetailInfo(uid, UserDetailSource.KSERVER));
const retUser = await fallback.run().then(async (user) => {
if (user && user.uin === '0') {
user.uin = await this.core.apis.UserApi.getUidByUinV2(uid) ?? '0';
}
return user;
});
return retUser;
}
async modifySelfProfile(param: ModifyProfileParams) {
return this.context.session.getProfileService().modifyDesktopMiniProfile(param);
}
@@ -169,46 +181,34 @@ export class NTQQUserApi {
return skey;
}
async getUidByUinV2(Uin: string) {
if (!Uin) {
async getUidByUinV2(uin: string) {
if (!uin) {
return '';
}
const services = [
() => this.context.session.getUixConvertService().getUid([Uin]).then((data) => data.uidInfo.get(Uin)).catch(() => undefined),
() => promisify<string, string[], Map<string, string>>
(this.context.session.getProfileService().getUidByUin)('FriendsServiceImpl', [Uin]).then((data) => data.get(Uin)).catch(() => undefined),
() => this.context.session.getGroupService().getUidByUins([Uin]).then((data) => data.uids.get(Uin)).catch(() => undefined),
() => this.getUserDetailInfoByUin(Uin).then((data) => data.detail.uid).catch(() => undefined),
];
let uid: string | undefined = undefined;
for (const service of services) {
uid = await service();
if (uid && uid.indexOf('*') == -1 && uid !== '') {
break;
}
}
const fallback =
new Fallback<string | undefined>((uid) => FallbackUtil.boolchecker(uid, uid !== undefined && uid.indexOf('*') === -1 && uid !== ''))
.add(() => this.context.session.getUixConvertService().getUid([uin]).then((data) => data.uidInfo.get(uin)))
.add(() => this.context.session.getProfileService().getUidByUin('FriendsServiceImpl', [uin]).get(uin))
.add(() => this.context.session.getGroupService().getUidByUins([uin]).then((data) => data.uids.get(uin)))
.add(() => this.getUserDetailInfoByUin(uin).then((data) => data.detail.uid));
const uid = await fallback.run().catch(() => '');
return uid ?? '';
}
async getUinByUidV2(Uid: string) {
if (!Uid) {
async getUinByUidV2(uid: string) {
if (!uid) {
return '0';
}
const services = [
() => this.context.session.getUixConvertService().getUin([Uid]).then((data) => data.uinInfo.get(Uid)).catch(() => undefined),
() => this.context.session.getGroupService().getUinByUids([Uid]).then((data) => data.uins.get(Uid)).catch(() => undefined),
() => promisify<string, string[], Map<string, string>>
(this.context.session.getProfileService().getUinByUid)('FriendsServiceImpl', [Uid]).then((data) => data.get(Uid)).catch(() => undefined),
() => this.core.apis.FriendApi.getBuddyIdMap(true).then((data) => data.getKey(Uid)).catch(() => undefined),
() => this.getUserDetailInfo(Uid).then((data) => data.uin).catch(() => undefined),
];
let uin: string | undefined = undefined;
for (const service of services) {
uin = await service();
if (uin && uin !== '0' && uin !== '') {
break;
}
}
const fallback = new Fallback<string | undefined>((uin) => FallbackUtil.boolchecker(uin, uin !== undefined && uin !== '0' && uin !== ''))
.add(() => this.context.session.getUixConvertService().getUin([uid]).then((data) => data.uinInfo.get(uid)))
.add(() => this.context.session.getProfileService().getUinByUid('FriendsServiceImpl', [uid]).get(uid))
.add(() => this.context.session.getGroupService().getUinByUids([uid]).then((data) => data.uins.get(uid)))
.add(() => this.getUserDetailInfo(uid).then((data) => data.uin));
const uin = await fallback.run().catch(() => '0');
return uin ?? '0';
}

View File

@@ -8,7 +8,7 @@ import {
WebHonorType,
} from '@/core';
import { NapCatCore } from '..';
import { createReadStream, readFileSync, statSync } from 'node:fs';
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { basename } from 'node:path';

View File

@@ -158,5 +158,9 @@
"6.9.63-30899": {
"appid": 537263820,
"qua": "V1_MAC_NQ_6.9.63_30899_GW_B"
},
"9.9.17-31219": {
"appid": 537266450,
"qua": "V1_WIN_NQ_9.9.17_31219_GW_B"
}
}

View File

@@ -202,5 +202,9 @@
"6.9.63-30899-arm64": {
"send": "41DCBD8",
"recv": "41DF3F0"
},
"9.9.17-31219-x64": {
"send": "39C1350",
"recv": "39C5784"
}
}

View File

@@ -152,6 +152,7 @@ export class NapCatCore {
// Renamed from 'InitDataListener'
async initNapCatCoreListeners() {
const msgListener = new NodeIKernelMsgListener();
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
// 下线通知
this.context.logger.logError('[KickedOffLine] [' + Info.tipsTitle + '] ' + Info.tipsDesc);

View File

@@ -21,7 +21,7 @@ export class PacketOperationContext {
}
async GroupPoke(groupUin: number, uin: number) {
const req = trans.SendPoke.build(groupUin, uin);
const req = trans.SendPoke.build(uin, groupUin);
await this.context.client.sendOidbPacket(req);
}

View File

@@ -56,6 +56,7 @@ export interface GrayTipElement {
aioOpGrayTipElement: TipAioOpGrayTipElement;
groupElement: TipGroupElement;
xmlElement: {
busiId: string;
content: string;
templId: string;
};

View File

@@ -9,4 +9,5 @@ export * from './sign';
export * from './element';
export * from './constant';
export * from './graytip';
export * from './emoji';
export * from './emoji';
export * from './service';

35
src/core/types/service.ts Normal file
View File

@@ -0,0 +1,35 @@
export enum LoginErrorCode {
KLOGINERRORACCOUNTNOTUIN = 140022018,
KLOGINERRORACCOUNTORPASSWORDERROR = 140022013,
KLOGINERRORBLACKACCOUNT = 150022021,
KLOGINERRORDEFAULT = 140022000,
KLOGINERROREXPIRETICKET = 140022014,
KLOGINERRORFROZEN = 140022005,
KLOGINERRORILLAGETICKET = 140022016,
KLOGINERRORINVAILDCOOKIE = 140022012,
KLOGINERRORINVALIDPARAMETER = 140022001,
KLOGINERRORKICKEDTICKET = 140022015,
KLOGINERRORMUTIPLEPASSWORDINCORRECT = 150022029,
KLOGINERRORNEEDUPDATE = 140022004,
KLOGINERRORNEEDVERIFYREALNAME = 140022019,
KLOGINERRORNEWDEVICE = 140022010,
KLOGINERRORNICEACCOUNTEXPIRED = 150022020,
KLOGINERRORNICEACCOUNTPARENTCHILDEXPIRED = 150022025,
KLOGINERRORPASSWORD = 2,
KLOGINERRORPROOFWATER = 140022008,
KLOGINERRORPROTECT = 140022006,
KLOGINERRORREFUSEPASSOWRDLOGIN = 140022009,
KLOGINERRORREMINDCANAELLATEDSTATUS = 150022028,
KLOGINERRORSCAN = 1,
KLOGINERRORSCCESS = 0,
KLOGINERRORSECBEAT = 140022017,
KLOGINERRORSMSINVALID = 150022026,
KLOGINERRORSTRICK = 140022007,
KLOGINERRORSYSTEMFAILED = 140022002,
KLOGINERRORTGTGTEXCHAGEA1FORBID = 150022027,
KLOGINERRORTIMEOUTRETRY = 140022003,
KLOGINERRORTOOMANYTIMESTODAY = 150022023,
KLOGINERRORTOOOFTEN = 150022022,
KLOGINERRORUNREGISTERED = 150022024,
KLOGINERRORUNUSUALDEVICE = 140022011,
}

View File

@@ -27,6 +27,7 @@ export async function NCoreInitFramework(
process.on('uncaughtException', (err) => {
console.log('[NapCat] [Error] Unhandled Exception:', err.message);
});
process.on('unhandledRejection', (reason, promise) => {
console.log('[NapCat] [Error] unhandledRejection:', reason);
});

View File

@@ -83,5 +83,5 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
}
}
abstract _handle(payload: PayloadType, adaptername: string): PromiseLike<ReturnDataType>;
abstract _handle(payload: PayloadType, adaptername: string): Promise<ReturnDataType>;
}

View File

@@ -14,22 +14,23 @@ class OCRImageBase extends OneBotAction<Payload, any> {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const { path, success } = (await uriToLocalFile(this.core.NapCatTempPath, payload.image));
const { path, success } = await uriToLocalFile(this.core.NapCatTempPath, payload.image);
if (!success) {
throw new Error(`OCR ${payload.image}失败,image字段可能格式不正确`);
throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`);
}
if (path) {
await checkFileExist(path, 5000); // 避免崩溃
const ret = await this.core.apis.SystemApi.ocrImage(path);
fs.unlink(path, () => { });
if (!ret) {
throw new Error(`OCR ${payload.image}失败`);
try {
await checkFileExist(path, 5000); // 避免崩溃
const ret = await this.core.apis.SystemApi.ocrImage(path);
if (!ret) {
throw new Error(`OCR ${payload.image}失败`);
}
return ret.result;
} finally {
fs.unlink(path, () => { });
}
return ret.result;
}
fs.unlink(path, () => { });
throw new Error(`OCR ${payload.image}失败,文件可能不存在`);
throw new Error(`OCR ${payload.image}失败, 文件可能不存在`);
}
}
@@ -39,4 +40,4 @@ export class OCRImage extends OCRImageBase {
export class IOCRImage extends OCRImageBase {
actionName = ActionName.IOCRImage;
}
}

View File

@@ -17,7 +17,7 @@ class SetGroupSignBase extends GetPacketStatusDepends<Payload, any> {
}
export class SetGroupSign extends SetGroupSignBase {
actionName = ActionName.SendGroupSign;
actionName = ActionName.SetGroupSign;
}
export class SendGroupSign extends SetGroupSignBase {

View File

@@ -2,7 +2,7 @@ import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import fs from 'fs';
import { join as joinPath } from 'node:path';
import { calculateFileMD5, httpDownload } from '@/common/file';
import { calculateFileMD5, uriToLocalFile } from '@/common/file';
import { randomUUID } from 'crypto';
import { Static, Type } from '@sinclair/typebox';
@@ -26,17 +26,20 @@ export default class GoCQHTTPDownloadFile extends OneBotAction<Payload, FileResp
async _handle(payload: Payload): Promise<FileResponse> {
const isRandomName = !payload.name;
const name = payload.name || randomUUID();
const filePath = joinPath(this.core.NapCatTempPath, name);
let result: Awaited<ReturnType<typeof uriToLocalFile>>;
if (payload.base64) {
fs.writeFileSync(filePath, payload.base64, 'base64');
result = await uriToLocalFile(this.core.NapCatTempPath, `base64://${payload.base64}`, name);
} else if (payload.url) {
const headers = this.getHeaders(payload.headers);
const buffer = await httpDownload({ url: payload.url, headers: headers });
fs.writeFileSync(filePath, Buffer.from(buffer), 'binary');
result = await uriToLocalFile(this.core.NapCatTempPath, payload.url, name, headers);
} else {
throw new Error('不存在任何文件, 无法下载');
}
if (!result.success) {
throw new Error(result.errMsg);
}
const filePath = result.path;
if (fs.existsSync(filePath)) {
if (isRandomName) {

View File

@@ -13,12 +13,13 @@ type Payload = Static<typeof SchemaData>;
export default class GoCQHTTPGetStrangerInfo extends OneBotAction<Payload, OB11User> {
actionName = ActionName.GoCQHTTP_GetStrangerInfo;
payloadSchema = SchemaData;
async _handle(payload: Payload): Promise<OB11User> {
const user_id = payload.user_id.toString();
const extendData = await this.core.apis.UserApi.getUserDetailInfoByUin(user_id);
let uid = (await this.core.apis.UserApi.getUidByUinV2(user_id));
if (!uid) uid = extendData.detail.uid;
console.log(uid);
const info = (await this.core.apis.UserApi.getUserDetailInfo(uid));
return {
...extendData.detail.simpleInfo.coreInfo,

View File

@@ -26,20 +26,35 @@ class GetGroupMemberInfo extends OneBotAction<Payload, OB11GroupMember> {
return uid;
}
async _handle(payload: Payload) {
const isNocache = this.parseBoolean(payload.no_cache ?? true);
const uid = await this.getUid(payload.user_id);
const groupMember = this.core.apis.GroupApi.groupMemberCache.get(payload.group_id.toString())?.get(uid);
let [member, info] = await Promise.all([
private async getGroupMemberInfo(payload: Payload, uid: string, isNocache: boolean) {
const groupMemberCache = this.core.apis.GroupApi.groupMemberCache.get(payload.group_id.toString());
let groupMember = groupMemberCache?.get(uid);
const [member, info] = await Promise.all([
this.core.apis.GroupApi.getGroupMemberEx(payload.group_id.toString(), uid, isNocache),
this.core.apis.UserApi.getUserDetailInfo(uid),
]);
if (!member || !groupMember) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`);
if (info) {
member = { ...groupMember, ...member, ...info };
} else {
if (!member) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`);
if (!groupMember && this.core.apis.GroupApi.groupMemberCacheEvent.get(payload.group_id.toString())) {
groupMember = (await this.core.apis.GroupApi.refreshGroupMemberCache(payload.group_id.toString(), true))?.get(uid);
}
if (!groupMember) throw new Error(`群(${payload.group_id})成员${payload.user_id}不存在`);
return info ? { ...groupMember, ...member, ...info } : member;
}
async _handle(payload: Payload) {
const isNocache = this.parseBoolean(payload.no_cache ?? true);
const uid = await this.getUid(payload.user_id);
const member = await this.getGroupMemberInfo(payload, uid, isNocache);
if (!member) {
this.core.context.logger.logDebug(`获取群成员详细信息失败, 只能返回基础信息`);
}
return OB11Construct.groupMember(payload.group_id.toString(), member);
}
}

View File

@@ -3,6 +3,7 @@ import { OB11Construct } from '@/onebot/helper/data';
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { GroupMember } from '@/core';
const SchemaData = Type.Object({
group_id: Type.Union([Type.Number(), Type.String()]),
@@ -17,25 +18,32 @@ export class GetGroupMemberList extends OneBotAction<Payload, OB11GroupMember[]>
async _handle(payload: Payload) {
const groupIdStr = payload.group_id.toString();
const noCache = payload.no_cache ? this.stringToBoolean(payload.no_cache) : false;
const noCache = this.parseBoolean(payload.no_cache ?? false);
const groupMembers = await this.getGroupMembers(groupIdStr, noCache);
const _groupMembers = await Promise.all(
Array.from(groupMembers.values()).map(item =>
OB11Construct.groupMember(groupIdStr, item)
)
);
return Array.from(new Map(_groupMembers.map(member => [member.user_id, member])).values());
}
private parseBoolean(value: boolean | string): boolean {
return typeof value === 'string' ? value === 'true' : value;
}
private async getGroupMembers(groupIdStr: string, noCache: boolean): Promise<Map<string, GroupMember>> {
const memberCache = this.core.apis.GroupApi.groupMemberCache;
let groupMembers = memberCache.get(groupIdStr);
if (noCache || !groupMembers) {
this.core.apis.GroupApi.refreshGroupMemberCache(groupIdStr).then().catch();
//下次刷新
groupMembers = memberCache.get(groupIdStr);
const data = this.core.apis.GroupApi.refreshGroupMemberCache(groupIdStr, true).then().catch();
groupMembers = memberCache.get(groupIdStr) || (await data);
if (!groupMembers) {
throw new Error(`Failed to get group member list for group ${groupIdStr}`);
}
}
const memberPromises = Array.from(groupMembers.values()).map(item =>
OB11Construct.groupMember(groupIdStr, item)
);
const _groupMembers = await Promise.all(memberPromises);
const MemberMap = new Map(_groupMembers.map(member => [member.user_id, member]));
return Array.from(MemberMap.values());
return groupMembers;
}
stringToBoolean(str: string | boolean): boolean {
return typeof str === 'boolean' ? str : str.toLowerCase() === "true";
}
}
}

View File

@@ -14,6 +14,6 @@ export class GroupPoke extends GetPacketStatusDepends<Payload, any> {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.user_id, +payload.group_id);
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
}
}

View File

@@ -21,15 +21,9 @@ export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
const url = await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index);
const { path, errMsg, success } = (await uriToLocalFile(this.core.NapCatTempPath, url));
if (!success) {
throw new Error(errMsg);
}
const peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() } as Peer;
const element = await this.core.apis.FileApi.createValidSendPttElement(path);
const sendRes = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [element], [path]);
return { message_id: sendRes.id ?? -1 };
await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
return {
message_id: 0 // can't get message_id from GetAiVoice
};
}
}

View File

@@ -15,9 +15,9 @@ export class SendPoke extends GetPacketStatusDepends<Payload, any> {
async _handle(payload: Payload) {
if (payload.group_id) {
this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.group_id, +payload.user_id);
} else {
this.core.apis.PacketApi.pkt.operation.FriendPoke(+payload.user_id);
await this.core.apis.PacketApi.pkt.operation.FriendPoke(+payload.user_id);
}
}
}
}

View File

@@ -24,6 +24,7 @@ import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNotic
import { OB11GroupNameEvent } from '../event/notice/OB11GroupNameEvent';
import { pathToFileURL } from 'node:url';
import { FileNapCatOneBotUUID } from '@/common/helper';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
export class OneBotGroupApi {
obContext: NapCatOneBot11Adapter;
@@ -219,15 +220,51 @@ export class OneBotGroupApi {
} else if (element.type === TipGroupElementType.KSHUTUP) {
let event = await this.parseGroupBanEvent(msg.peerUid, elementWrapper);
return event;
} else if (element.type === TipGroupElementType.KMEMBERADD) {
// 自己的通知 协议推送为type->85 在这里实现为了避免邀请出现问题
if (element.memberUid == this.core.selfInfo.uid) {
await this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid, false);
return new OB11GroupIncreaseEvent(
this.core,
parseInt(msg.peerUid),
+this.core.selfInfo.uin,
element.adminUid ? +await this.core.apis.UserApi.getUinByUidV2(element.adminUid) : 0,
'approve'
);
}
}
}
async parseSelfInviteEvent(msg: RawMessage, inviterUin: string, inviteeUin: string) {
return new OB11GroupIncreaseEvent(
this.core,
parseInt(msg.peerUid),
+inviteeUin,
+inviterUin,
'invite'
);
}
async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) {
if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) {
// 解析群组事件 由sysmsg解析
return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement);
} else if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_XMLMSG) {
// 筛选出表情回应 事件
// 筛选自身入群情况
// if (grayTipElement.xmlElement.busiId === '10145') {
// const inviteData = new fastXmlParser.XMLParser({
// ignoreAttributes: false,
// attributeNamePrefix: '',
// }).parse(grayTipElement.xmlElement.content);
// const inviterUin: string = inviteData.gtip.qq[0].jp;
// const inviteeUin: string = inviteData.gtip.qq[1].jp;
// //刷新群缓存
// if (inviteeUin === this.core.selfInfo.uin) {
// this.core.apis.GroupApi.refreshGroupMemberCache(msg.peerUid).then().catch();
// return this.parseSelfInviteEvent(msg, inviterUin, inviteeUin);
// }
// } else
if (grayTipElement.xmlElement?.templId === '10382') {
return await this.obContext.apis.GroupApi.parseGroupEmojiLikeEventByGrayTip(msg.peerUid, grayTipElement);
} else {

View File

@@ -797,6 +797,13 @@ export class OneBotMsgApi {
private async handlePrivateMessage(resMsg: OB11Message, msg: RawMessage) {
resMsg.sub_type = 'friend';
if (await this.core.apis.FriendApi.isBuddy(msg.senderUid)) {
let nickname = (await this.core.apis.UserApi.getCoreAndBaseInfo([msg.senderUid])).get(msg.senderUid)?.coreInfo.nick;
if (nickname) {
resMsg.sender.nickname = nickname;
return;
}
}
resMsg.sender.nickname = (await this.core.apis.UserApi.getUserDetailInfo(msg.senderUid)).nick;
}
@@ -972,6 +979,7 @@ export class OneBotMsgApi {
return { path, fileName: inputdata.name ?? fileName };
}
groupChangDecreseType2String(type: number): GroupDecreaseSubType {
switch (type) {
case 130:
@@ -985,12 +993,41 @@ export class OneBotMsgApi {
}
}
async waitGroupNotify(groupUin: string, memberUid?: string, operatorUid?: string) {
let groupRole = this.core.apis.GroupApi.groupMemberCache.get(groupUin)?.get(this.core.selfInfo.uid.toString())?.role;
let isAdminOrOwner = groupRole === 3 || groupRole === 4;
if (isAdminOrOwner && !operatorUid) {
let dataNotify: GroupNotify | undefined;
await this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onGroupNotifiesUpdated',
(doubt, notifies) => {
for (const notify of notifies) {
if (notify.group.groupCode === groupUin && notify.user1.uid === memberUid) {
dataNotify = notify;
return true;
}
}
return false;
}, 1, 1000).catch(undefined);
if (dataNotify) {
return !dataNotify.actionUser.uid ? dataNotify.user2.uid : dataNotify.actionUser.uid;
}
}
return operatorUid;
}
async parseSysMessage(msg: number[]) {
const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
// 邀请需要解grayTipElement
if (SysMessage.contentHead.type == 33 && SysMessage.body?.msgContent) {
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString()).then().catch();
const operatorUid = groupChange.operatorInfo ? Buffer.from(groupChange.operatorInfo).toString() : '';
await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), false);
let operatorUid = await this.waitGroupNotify(
groupChange.groupUin.toString(),
groupChange.memberUid,
groupChange.operatorInfo ? Buffer.from(groupChange.operatorInfo).toString() : ''
);
return new OB11GroupIncreaseEvent(
this.core,
groupChange.groupUin,
@@ -998,19 +1035,24 @@ export class OneBotMsgApi {
operatorUid ? +await this.core.apis.UserApi.getUinByUidV2(operatorUid) : 0,
groupChange.decreaseType == 131 ? 'invite' : 'approve',
);
} else if (SysMessage.contentHead.type == 34 && SysMessage.body?.msgContent) {
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
// 自身被踢出时operatorInfo会是一个protobuf 否则大多数情况为一个string
const operatorUid = groupChange.decreaseType === 3 && groupChange.operatorInfo ?
new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid :
groupChange.operatorInfo?.toString();
let operatorUid = await this.waitGroupNotify(
groupChange.groupUin.toString(),
groupChange.memberUid,
groupChange.decreaseType === 3 && groupChange.operatorInfo ?
new NapProtoMsg(GroupChangeInfo).decode(groupChange.operatorInfo).operator?.operatorUid :
groupChange.operatorInfo?.toString()
);
if (groupChange.memberUid === this.core.selfInfo.uid) {
setTimeout(() => {
this.core.apis.GroupApi.groupMemberCache.delete(groupChange.groupUin.toString());
}, 5000);
// 自己被踢了 5S后回收
} else {
this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString()).then().catch();
await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), false);
}
return new OB11GroupDecreaseEvent(
this.core,
@@ -1021,7 +1063,7 @@ export class OneBotMsgApi {
);
} else if (SysMessage.contentHead.type == 44 && SysMessage.body?.msgContent) {
const groupAmin = new NapProtoMsg(GroupAdmin).decode(SysMessage.body.msgContent);
this.core.apis.GroupApi.refreshGroupMemberCache(groupAmin.groupUin.toString()).then().catch();
await this.core.apis.GroupApi.refreshGroupMemberCache(groupAmin.groupUin.toString(), false);
let enabled = false;
let uid = '';
if (groupAmin.body.extraEnable != null) {
@@ -1103,8 +1145,7 @@ export class OneBotMsgApi {
'',
request_seq
);
}
else if (SysMessage.contentHead.type == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) {
} else if (SysMessage.contentHead.type == 528 && SysMessage.contentHead.subType == 39 && SysMessage.body?.msgContent) {
return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent);
}
}

View File

@@ -390,9 +390,6 @@ export class NapCatOneBot11Adapter {
this.context.logger.logDebug('有加群请求');
try {
let requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
if (isNaN(parseInt(requestUin))) {
requestUin = (await this.core.apis.UserApi.getUserDetailInfo(notify.user1.uid)).uin;
}
const groupRequestEvent = new OB11GroupRequestEvent(
this.core,
parseInt(notify.group.groupCode),

View File

@@ -152,7 +152,7 @@ async function handleLogin(
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number, errMsg: string) => {
if (!isLogined) {
logger.logError('[Core] [Login] Login Error,ErrCode: ', errCode, ' ErrMsg:', errMsg);
logger.logError('[Core] [Login] Login Error,ErrType: ', errType, ' ErrCode:', errCode);
if (errType == 1 && errCode == 3) {
// 二维码过期刷新
}
@@ -160,8 +160,8 @@ async function handleLogin(
}
};
loginListener.onLoginFailed = (args) => {
logger.logError('[Core] [Login] Login Error , ErrInfo: ', args);
loginListener.onLoginFailed = (...args) => {
logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args));
};
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
@@ -222,7 +222,7 @@ async function handleLogin(
logger.log(`可用于快速登录的 QQ\n${historyLoginList
.map((u, index) => `${index + 1}. ${u.uin} ${u.nickName}`)
.join('\n')
}`);
}`);
}
loginService.getQRCodePicture();
}
@@ -266,7 +266,6 @@ export async function NCoreInitShell() {
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
handleUncaughtExceptions(logger);
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
@@ -295,9 +294,7 @@ export async function NCoreInitShell() {
const dataTimestape = new Date().getTime().toString();
o3Service.reportAmgomWeather('login', 'a1', [dataTimestape, '0', '0']);
const selfInfo = await handleLogin(loginService, logger, pathWrapper, quickLoginUin, historyLoginList);
const amgomDataPiece = 'eb1fd6ac257461580dc7438eb099f23aae04ca679f4d88f53072dc56e3bb1129';
o3Service.setAmgomDataPiece(basicInfoWrapper.QQVersionAppid, new Uint8Array(Buffer.from(amgomDataPiece, 'hex')));

View File

@@ -90,7 +90,7 @@ export class WebUiConfigWrapper {
try {
const configPath = resolve(webUiPathWrapper.configPath, './webui.json');
if (!await fs.access(configPath, constants.R_OK | constants.W_OK).then(() => true).catch(() => false)) {
if (!await fs.access(configPath, constants.F_OK).then(() => true).catch(() => false)) {
await fs.writeFile(configPath, JSON.stringify(defaultconfig, null, 4));
}
@@ -101,7 +101,12 @@ export class WebUiConfigWrapper {
if (!parsedConfig.prefix.startsWith('/')) parsedConfig.prefix = '/' + parsedConfig.prefix;
if (parsedConfig.prefix.endsWith('/')) parsedConfig.prefix = parsedConfig.prefix.slice(0, -1);
// 配置已经被操作过了,还是回写一下吧,不然新配置不会出现在配置文件里
await fs.writeFile(configPath, JSON.stringify(parsedConfig, null, 4));
if (await fs.access(configPath, constants.W_OK).then(() => true).catch(() => false)) {
await fs.writeFile(configPath, JSON.stringify(parsedConfig, null, 4));
}
else {
console.warn(`文件: ${configPath} 没有写入权限, 配置的更改部分可能会在重启后还原.`);
}
// 不希望回写的配置放后面
// 查询主机地址是否可用

View File

@@ -1,10 +1,10 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES2021",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": [
"ES2020",
"ES2021",
"DOM",
"DOM.Iterable"
],