mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0848d5a39e | ||
![]() |
7660646059 | ||
![]() |
bcd90fc744 | ||
![]() |
638fc22d62 | ||
![]() |
c87d365b88 | ||
![]() |
aee9602f25 | ||
![]() |
976fbd0220 | ||
![]() |
afd955d06f | ||
![]() |
4d548da66b | ||
![]() |
41b70f53d1 | ||
![]() |
a47a618bcd | ||
![]() |
62170a30af | ||
![]() |
780c5ac23c | ||
![]() |
9fba519a5a | ||
![]() |
3cd0e7d26b | ||
![]() |
a8fd6af994 | ||
![]() |
35427b0768 |
@@ -4,16 +4,12 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.2.56",
|
"version": "4.2.60",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "MliKiowa",
|
"name": "NapNeko",
|
||||||
"link": "https://github.com/MliKiowa"
|
"link": "https://github.com/NapNeko"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Young",
|
|
||||||
"link": "https://github.com/Wesley-Young"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.2.56",
|
"version": "4.2.60",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"@typescript-eslint/parser": "^8.3.0",
|
"@typescript-eslint/parser": "^8.3.0",
|
||||||
"ajv": "^8.13.0",
|
"ajv": "^8.13.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"commander": "^12.1.0",
|
"commander": "^13.0.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
"eslint-import-resolver-typescript": "^3.6.1",
|
"eslint-import-resolver-typescript": "^3.6.1",
|
||||||
|
116
src/common/cloudflare.ts
Normal file
116
src/common/cloudflare.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import * as net from 'net';
|
||||||
|
import * as https from 'https';
|
||||||
|
|
||||||
|
export const cloudflareIps = [
|
||||||
|
'103.21.244.0/22',
|
||||||
|
'103.22.200.0/22',
|
||||||
|
'103.31.4.0/22',
|
||||||
|
'104.16.0.0/13',
|
||||||
|
'104.24.0.0/14',
|
||||||
|
'108.162.192.0/18',
|
||||||
|
'131.0.72.0/22',
|
||||||
|
'141.101.64.0/18',
|
||||||
|
'162.158.0.0/15',
|
||||||
|
'172.64.0.0/13',
|
||||||
|
'173.245.48.0/20',
|
||||||
|
'188.114.96.0/20',
|
||||||
|
'190.93.240.0/20',
|
||||||
|
'197.234.240.0/22',
|
||||||
|
'198.41.128.0/17'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 将IP地址转换为整数
|
||||||
|
const ipToInt = (ip: string): number => ip.split('.').reduce((acc, octet) => (acc << 8) + Number(octet), 0) >>> 0;
|
||||||
|
|
||||||
|
// 将整数转换为IP地址
|
||||||
|
const intToIp = (int: number): string => `${(int >>> 24) & 0xFF}.${(int >>> 16) & 0xFF}.${(int >>> 8) & 0xFF}.${int & 0xFF}`;
|
||||||
|
|
||||||
|
// 解析CIDR范围
|
||||||
|
const parseCIDR = (cidr: string): string[] => {
|
||||||
|
const [base, maskLength] = cidr.split('/');
|
||||||
|
const baseIP = ipToInt(base);
|
||||||
|
return Array.from({ length: 1 << (32 - Number(maskLength)) }, (_, i) => intToIp(baseIP + i))
|
||||||
|
.filter(ip => !ip.endsWith('.0')); // 排除以 .0 结尾的 IP 地址
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析IP地址和IP范围
|
||||||
|
const parseIPs = (ipList: string[]): string[] => ipList.flatMap(ip => ip.includes('/') ? parseCIDR(ip) : net.isIP(ip) ? [ip] : []);
|
||||||
|
|
||||||
|
// 检查URL的HTTP连接性
|
||||||
|
const checkUrl = (ip: string, domain: string): Promise<boolean> => new Promise((resolve) => {
|
||||||
|
const options = {
|
||||||
|
hostname: ip,
|
||||||
|
port: 443,
|
||||||
|
method: 'GET',
|
||||||
|
path: '/',
|
||||||
|
timeout: 2000,
|
||||||
|
headers: {
|
||||||
|
'Host': domain,
|
||||||
|
"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.2128.93 Safari/537.36'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const req = https.request(options, (res) => {
|
||||||
|
res.on('data', (data) => {
|
||||||
|
//console.log('[NetWork] [Scan] Checking Data! ', domain, ' --> ', ip, data.toString());
|
||||||
|
});
|
||||||
|
if (res.statusCode && res.statusCode > 0 && res.statusCode < 500) {
|
||||||
|
console.log('[NetWork] [Scan] Checking Connect! ', domain, ' --> ', ip);
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
console.log('[NetWork] [Scan] Checking Failed! ', domain, ' --> ', ip, ' Status Code: ', res.statusCode);
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.on('error', () => {
|
||||||
|
console.log('[NetWork] [Scan] Checking Error! ', domain, ' --> ', ip);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
req.on('timeout', () => {
|
||||||
|
req.destroy();
|
||||||
|
console.log('[NetWork] [Scan] Checking Timeout! ', domain, ' --> ', ip);
|
||||||
|
resolve(false);
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查特定域名的HTTP连接性
|
||||||
|
const checkDomainConnectivity = async (ip: string, domains: string[]): Promise<boolean> => {
|
||||||
|
const checkPromises = domains.map(domain => checkUrl(ip, domain));
|
||||||
|
const results = await Promise.all(checkPromises);
|
||||||
|
return results.some(result => result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const optimizeCloudflareIPs = async (ipList: string[], domains: string[]): Promise<string | undefined> => {
|
||||||
|
if ((await Promise.all(domains.map(domain => checkUrl(domain, domain)))).every(result => result)) return undefined;
|
||||||
|
for (const ip of parseIPs(ipList)) {
|
||||||
|
if (await checkDomainConnectivity(ip, domains)) return ip;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const findRealHost = async () => {
|
||||||
|
const domains = ['umami.napneko.icu', 'rkey.napneko.icu'];
|
||||||
|
const ip = await optimizeCloudflareIPs(cloudflareIps, domains);
|
||||||
|
return {
|
||||||
|
'umami.napneko.icu': ip ?? 'umami.napneko.icu',
|
||||||
|
'rkey.napneko.icu': ip ?? 'rkey.napneko.icu'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cache = {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let cache: Cache = {
|
||||||
|
'cached': '',
|
||||||
|
'umami.napneko.icu': 'umami.napneko.icu',
|
||||||
|
'rkey.napneko.icu': 'rkey.napneko.icu'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDomainIp = async (domain: string) => {
|
||||||
|
if (!cache.cached) {
|
||||||
|
cache = { cached: 'cached', ...(await findRealHost()) };
|
||||||
|
}
|
||||||
|
console.log('[NetWork] [Cache] ', domain, ' --> ', cache[domain] ?? domain);
|
||||||
|
return cache[domain] ?? domain;
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
import { napCatVersion } from './version';
|
import { napCatVersion } from './version';
|
||||||
import os from 'os';
|
import os from 'node:os';
|
||||||
export class UmamiTraceCore {
|
export class UmamiTraceCore {
|
||||||
napcatVersion = napCatVersion;
|
napcatVersion = napCatVersion;
|
||||||
qqversion = '1.0.0';
|
qqversion = '1.0.0';
|
||||||
@@ -11,6 +11,7 @@ export class UmamiTraceCore {
|
|||||||
hostname: string = 'trace.napneko.icu';
|
hostname: string = 'trace.napneko.icu';
|
||||||
ua: string = '';
|
ua: string = '';
|
||||||
workname: string = 'default';
|
workname: string = 'default';
|
||||||
|
bootTime = Date.now();
|
||||||
init(qqversion: string, guid: string, workname: string) {
|
init(qqversion: string, guid: string, workname: string) {
|
||||||
this.qqversion = qqversion;
|
this.qqversion = qqversion;
|
||||||
this.workname = workname;
|
this.workname = workname;
|
||||||
@@ -43,27 +44,41 @@ export class UmamiTraceCore {
|
|||||||
const data = {
|
const data = {
|
||||||
napcat_version: this.napcatVersion,
|
napcat_version: this.napcatVersion,
|
||||||
qq_version: this.qqversion,
|
qq_version: this.qqversion,
|
||||||
guid: guid,
|
napcat_working: this.workname,
|
||||||
workname: this.workname,
|
device_guid: this.guid,
|
||||||
|
device_platform: os.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.sendRequest({ website: this.website, ...data }, 'identify');
|
this.sendEvent(
|
||||||
|
{
|
||||||
|
website: this.website,
|
||||||
|
hostname: this.hostname,
|
||||||
|
referrer: this.referrer,
|
||||||
|
tittle: 'NapCat ' + this.napcatVersion,
|
||||||
|
url: `/${this.qqversion}/${this.napcatVersion}/${this.workname}/identify`,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
'identify'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(event: string, data?: object) {
|
sendEvent(event: string | object, data?: object, type = 'event') {
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
const language = env.LANG || env.LANGUAGE || env.LC_ALL || env.LC_MESSAGES;
|
const language = env.LANG || env.LANGUAGE || env.LC_ALL || env.LC_MESSAGES;
|
||||||
const payload = {
|
const payload = {
|
||||||
name: event,
|
...(typeof event === 'string' ? { event } : event),
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
referrer: this.referrer,
|
referrer: this.referrer,
|
||||||
website: this.website,
|
website: this.website,
|
||||||
language: language || 'en-US',
|
language: language || 'en-US',
|
||||||
napcat_version: this.napcatVersion,
|
screen: '1920x1080',
|
||||||
qq_version: this.qqversion,
|
data: {
|
||||||
workname: this.workname,
|
...data
|
||||||
...data
|
}
|
||||||
};
|
};
|
||||||
this.sendRequest(payload);
|
this.sendRequest(payload, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTrace(eventName: string, data: string = '') {
|
sendTrace(eventName: string, data: string = '') {
|
||||||
@@ -97,7 +112,7 @@ export class UmamiTraceCore {
|
|||||||
res.on('data', (data) => {
|
res.on('data', (data) => {
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
}).on('error', () => {} );
|
||||||
|
|
||||||
request.write(JSON.stringify({ type, payload }));
|
request.write(JSON.stringify({ type, payload }));
|
||||||
request.end();
|
request.end();
|
||||||
@@ -108,13 +123,10 @@ export class UmamiTraceCore {
|
|||||||
clearInterval(this.heartbeatInterval);
|
clearInterval(this.heartbeatInterval);
|
||||||
}
|
}
|
||||||
this.heartbeatInterval = setInterval(() => {
|
this.heartbeatInterval = setInterval(() => {
|
||||||
this.sendEvent('heartbeat', {
|
this.sendEvent({
|
||||||
|
name: 'heartbeat',
|
||||||
title: 'NapCat ' + this.napcatVersion,
|
title: 'NapCat ' + this.napcatVersion,
|
||||||
language: process.env.LANG || 'en-US',
|
|
||||||
url: `/${this.qqversion}/${this.napcatVersion}/${this.workname}/heartbeat`,
|
url: `/${this.qqversion}/${this.napcatVersion}/${this.workname}/heartbeat`,
|
||||||
version: this.napcatVersion,
|
|
||||||
qq_version: this.qqversion,
|
|
||||||
user_id: this.guid
|
|
||||||
});
|
});
|
||||||
}, 5 * 60 * 1000);
|
}, 5 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.2.56';
|
export const napCatVersion = '4.2.60';
|
||||||
|
@@ -10,3 +10,4 @@ export * from './element';
|
|||||||
export * from './constant';
|
export * from './constant';
|
||||||
export * from './graytip';
|
export * from './graytip';
|
||||||
export * from './emoji';
|
export * from './emoji';
|
||||||
|
export * from './service';
|
35
src/core/types/service.ts
Normal file
35
src/core/types/service.ts
Normal 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,
|
||||||
|
}
|
@@ -17,7 +17,7 @@ class SetGroupSignBase extends GetPacketStatusDepends<Payload, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SetGroupSign extends SetGroupSignBase {
|
export class SetGroupSign extends SetGroupSignBase {
|
||||||
actionName = ActionName.SendGroupSign;
|
actionName = ActionName.SetGroupSign;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SendGroupSign extends SetGroupSignBase {
|
export class SendGroupSign extends SetGroupSignBase {
|
||||||
|
@@ -155,7 +155,7 @@ async function handleLogin(
|
|||||||
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number, errMsg: string) => {
|
loginListener.onQRCodeSessionFailed = (errType: number, errCode: number, errMsg: string) => {
|
||||||
UmamiTrace.sendTrace('qrlogin/error', [errType, errCode, errMsg].toString());
|
UmamiTrace.sendTrace('qrlogin/error', [errType, errCode, errMsg].toString());
|
||||||
if (!isLogined) {
|
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) {
|
if (errType == 1 && errCode == 3) {
|
||||||
// 二维码过期刷新
|
// 二维码过期刷新
|
||||||
}
|
}
|
||||||
@@ -163,9 +163,9 @@ async function handleLogin(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loginListener.onLoginFailed = (args) => {
|
loginListener.onLoginFailed = (...args) => {
|
||||||
UmamiTrace.sendTrace('login/error', args.toString());
|
UmamiTrace.sendTrace('login/error', args.toString());
|
||||||
logger.logError('[Core] [Login] Login Error , ErrInfo: ', args.toString());
|
logger.logError('[Core] [Login] Login Error , ErrInfo: ', JSON.stringify(args));
|
||||||
};
|
};
|
||||||
|
|
||||||
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
loginService.addKernelLoginListener(proxiedListenerOf(loginListener, logger));
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2021",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2020",
|
"ES2021",
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.Iterable"
|
"DOM.Iterable"
|
||||||
],
|
],
|
||||||
|
Reference in New Issue
Block a user