NapCatQQ source snapshot
This commit is contained in:
commit
f954252a34
1111 files changed
+139867
No files matched your search
@@ -0,0 +1,178 @@
|
||||
const process = require('process');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { pathToFileURL } = require('url');
|
||||
|
||||
// 保存原始dlopen
|
||||
const dlopenOrig = process.dlopen;
|
||||
|
||||
let wrapperSession;
|
||||
let wrapperNodeApi;
|
||||
let wrapperLoginService;
|
||||
let getWebUiUrlFunc;
|
||||
let wrapperHooked = false;
|
||||
let sessionReady = false;
|
||||
let warnedMissingSession = false;
|
||||
const initCallbacks = new Set();
|
||||
const sessionProxies = new WeakMap();
|
||||
let resolveSession;
|
||||
const sessionPromise = new Promise(resolve => { resolveSession = resolve; });
|
||||
|
||||
// 原生导出可能含只读、不可配置的属性,使用独立 target 避免替换方法时违反 Proxy 约束。
|
||||
// 同时保留导出枚举、prototype 和属性探测,不能把 wrapper 伪装成空对象。
|
||||
function nativeProxy (target, get, construct) {
|
||||
// bound function 没有不可配置的 prototype/caller 属性,可转发原生 class 的属性。
|
||||
// eslint-disable-next-line no-extra-bind
|
||||
const facade = typeof target === 'function' ? function () {}.bind(null) : {};
|
||||
const proxy = new Proxy(facade, {
|
||||
get: (_, key) => get(key),
|
||||
has: (_, key) => Reflect.has(target, key),
|
||||
ownKeys: () => Reflect.ownKeys(target),
|
||||
getOwnPropertyDescriptor: (_, key) => {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
|
||||
return descriptor ? { ...descriptor, configurable: true } : undefined;
|
||||
},
|
||||
getPrototypeOf: () => Reflect.getPrototypeOf(target),
|
||||
set: (_, key, value) => Reflect.set(target, key, value, target),
|
||||
apply: (_, thisArg, args) => Reflect.apply(target, thisArg, args),
|
||||
construct: (_, args, newTarget) => {
|
||||
const instance = Reflect.construct(target, args, newTarget === proxy ? target : newTarget);
|
||||
return construct ? construct(instance) : instance;
|
||||
},
|
||||
});
|
||||
return proxy;
|
||||
}
|
||||
|
||||
function callInitCallback (callback) {
|
||||
try {
|
||||
callback();
|
||||
} catch (error) {
|
||||
// NapCat 的回调不能中断 QQ 自己的初始化回调。
|
||||
console.log('[NapCat] [Error] 初始化回调失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
function registerInitCallback (callback) {
|
||||
if (sessionReady) callInitCallback(callback);
|
||||
else initCallbacks.add(callback);
|
||||
}
|
||||
|
||||
function markSessionReady (session) {
|
||||
if (session !== wrapperSession || sessionReady) return;
|
||||
sessionReady = true;
|
||||
for (const callback of initCallbacks) callInitCallback(callback);
|
||||
initCallbacks.clear();
|
||||
}
|
||||
|
||||
function wrapSessionListener (listener, session) {
|
||||
const target = listener && typeof listener === 'object' ? listener : {};
|
||||
const methods = new Map();
|
||||
// 缺失第四参/原方法时也提供监听器,不修改 QQ 的原对象(可能已冻结)。
|
||||
const telemetryCallback = (...args) => {
|
||||
const origin = target.onOpentelemetryInit;
|
||||
const ret = typeof origin === 'function' ? Reflect.apply(origin, target, args) : undefined;
|
||||
if (args[0]?.is_init) markSessionReady(session);
|
||||
return ret;
|
||||
};
|
||||
const listenerTarget = Object.create(Object.getPrototypeOf(target), {
|
||||
...Object.getOwnPropertyDescriptors(target),
|
||||
onOpentelemetryInit: { value: telemetryCallback, enumerable: true, configurable: true },
|
||||
});
|
||||
return nativeProxy(listenerTarget, key => {
|
||||
if (key === 'onOpentelemetryInit') return telemetryCallback;
|
||||
const value = Reflect.get(target, key, target);
|
||||
if (typeof value !== 'function' || key === 'constructor') return value;
|
||||
if (!methods.has(value)) methods.set(value, value.bind(target));
|
||||
return methods.get(value);
|
||||
});
|
||||
}
|
||||
|
||||
function captureSession (session) {
|
||||
if (!session || typeof session.init !== 'function') {
|
||||
if (!warnedMissingSession) {
|
||||
warnedMissingSession = true;
|
||||
console.log('[NapCat] [Warn] QQ 尚未返回有效 Session,等待 QQ 后续创建会话');
|
||||
}
|
||||
return session;
|
||||
}
|
||||
if (!wrapperSession) {
|
||||
wrapperSession = session;
|
||||
resolveSession(session);
|
||||
}
|
||||
if (sessionProxies.has(session)) return sessionProxies.get(session);
|
||||
const methods = new Map();
|
||||
const proxy = nativeProxy(session, key => {
|
||||
const value = Reflect.get(session, key, session);
|
||||
if (typeof value !== 'function' || key === 'constructor') return value;
|
||||
if (!methods.has(key)) {
|
||||
methods.set(key, (...args) => {
|
||||
if (key === 'init') args[3] = wrapSessionListener(args[3], session);
|
||||
return Reflect.apply(value, session, args);
|
||||
});
|
||||
}
|
||||
return methods.get(key);
|
||||
});
|
||||
sessionProxies.set(session, proxy);
|
||||
return proxy;
|
||||
}
|
||||
|
||||
function createSessionServiceProxy (service) {
|
||||
const methods = new Map();
|
||||
return nativeProxy(service, key => {
|
||||
const value = Reflect.get(service, key, service);
|
||||
if (typeof value !== 'function' || key === 'constructor') return value;
|
||||
if (!methods.has(key)) {
|
||||
methods.set(key, (...args) => {
|
||||
// 使用原生 class 作为 this;把 QQ 的参数原样传递,不能提前固定调用 nt_1。
|
||||
const ret = Reflect.apply(value, service, args);
|
||||
return key === 'create' || key === 'getNTWrapperSession' ? captureSession(ret) : ret;
|
||||
});
|
||||
}
|
||||
return methods.get(key);
|
||||
}, captureSession);
|
||||
}
|
||||
|
||||
function clearHook () {
|
||||
process.dlopen = dlopenOrig;
|
||||
}
|
||||
|
||||
async function initializeNapCat () {
|
||||
console.log('[NapCat] [Info] 开始初始化NapCat');
|
||||
try {
|
||||
const currentPath = path.dirname(__filename);
|
||||
const { NCoreInitFramework, getWebUiUrl } = await import(pathToFileURL(path.join(currentPath, './napcat.mjs')).href);
|
||||
getWebUiUrlFunc = getWebUiUrl;
|
||||
await NCoreInitFramework(sessionPromise, wrapperLoginService, registerInitCallback);
|
||||
} catch (error) {
|
||||
console.log('[NapCat] [Error] 初始化NapCat', error);
|
||||
}
|
||||
}
|
||||
|
||||
process.dlopen = function (module, filename, flags = os.constants.dlopen.RTLD_LAZY) {
|
||||
const dlopenRet = dlopenOrig(module, filename, flags);
|
||||
if (path.basename(filename) !== 'wrapper.node' || wrapperHooked) return dlopenRet;
|
||||
wrapperHooked = true;
|
||||
clearHook();
|
||||
wrapperNodeApi = module.exports;
|
||||
wrapperLoginService = wrapperNodeApi.NodeIKernelLoginService.get();
|
||||
const sessionService = createSessionServiceProxy(wrapperNodeApi.NodeIQQNTWrapperSession);
|
||||
module.exports = nativeProxy(wrapperNodeApi, key => {
|
||||
if (key === 'NodeIQQNTWrapperSession') return sessionService;
|
||||
return Reflect.get(wrapperNodeApi, key, wrapperNodeApi);
|
||||
});
|
||||
|
||||
// 先安装拦截,再异步加载 Framework。会话由 QQ 创建,不调用已移除的工厂或抢跑初始化。
|
||||
initializeNapCat().catch(error => console.log('[NapCat] [Error] 初始化NapCat', error));
|
||||
|
||||
return dlopenRet;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
NCgetWebUiUrl: async () => {
|
||||
if (!getWebUiUrlFunc) {
|
||||
console.log('[NapCat] [Error] 未初始化完成');
|
||||
return '';
|
||||
}
|
||||
return await getWebUiUrlFunc();
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user