Skip to main content

Overview

NapCat supports two distinct working environments, each designed for different deployment scenarios. The environment is determined by how NapCat is launched and integrated with NTQQ.
export enum NapCatCoreWorkingEnv {
  Unknown = 0,
  Shell = 1,
  Framework = 2,
}
Source: packages/napcat-core/index.ts:56

Shell Mode

Standalone process that launches and controls QQ

Framework Mode

Injected into existing QQ process as a plugin

Shell Mode

What is Shell Mode?

Shell mode runs NapCat as an independent process that:
  • Launches QQ as a child process
  • Manages the QQ lifecycle
  • Handles multi-process coordination
  • Provides process isolation and crash recovery

Architecture

Process Management

Shell mode uses a master-worker pattern for stability:
// Environment detection
const ENV = {
  isWorkerProcess: process.env['NAPCAT_WORKER_PROCESS'] === '1',
  isMultiProcessDisabled: process.env['NAPCAT_DISABLE_MULTI_PROCESS'] === '1',
  isPipeDisabled: process.env['NAPCAT_DISABLE_PIPE'] === '1',
};

// Master process
async function startMasterProcess() {
  // Connect named pipe for IPC
  await connectToNamedPipe(logger);
  
  // Start worker process
  await startWorker();
  
  // Handle graceful shutdown
  process.on('SIGINT', shutdown);
  process.on('SIGTERM', shutdown);
}

// Worker process
async function startWorkerProcess() {
  // Listen for parent messages
  processManager.onParentMessage((msg) => {
    if (msg.type === 'restart-prepare' || msg.type === 'shutdown') {
      process.exit(0);
    }
  });
  
  // Initialize NapCat core
  await NCoreInitShell();
}
Source: packages/napcat-shell/napcat.ts:334

Crash Recovery

Shell mode includes automatic crash recovery:
// Track recent crashes
const recentCrashTimestamps: number[] = [];
const CRASH_TIME_WINDOW = 10000; // 10 seconds
const MAX_CRASHES_IN_WINDOW = 3; // Maximum crashes before giving up

child.on('exit', (code) => {
  if (!isRestarting && !isShuttingDown) {
    const now = Date.now();
    
    // Clean old crash records
    while (recentCrashTimestamps.length > 0 && 
           now - recentCrashTimestamps[0] > CRASH_TIME_WINDOW) {
      recentCrashTimestamps.shift();
    }
    
    recentCrashTimestamps.push(now);
    
    if (recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) {
      logger.logError('Too many crashes, exiting');
      process.exit(1);
    }
    
    logger.logWarn('Worker crashed, restarting...');
    startWorker();
  }
});
Source: packages/napcat-shell/napcat.ts:284

Restart Process

export async function restartWorker(
  secretKey?: string, 
  port?: number
): Promise<void> {
  isRestarting = true;
  
  // 1. Notify old process to prepare for restart
  currentWorker?.postMessage({ type: 'restart-prepare' });
  
  // 2. Wait for graceful exit (5 seconds)
  await new Promise<void>((resolve) => {
    const timeout = setTimeout(() => {
      currentWorker?.postMessage({ type: 'shutdown' });
      setTimeout(() => {
        currentWorker?.kill();
        resolve();
      }, 2000);
    }, 5000);
    
    currentWorker?.once('exit', () => {
      clearTimeout(timeout);
      resolve();
    });
  });
  
  // 3. Force kill if still alive
  if (workerPid && isProcessAlive(workerPid)) {
    forceKillProcess(workerPid);
  }
  
  // 4. Wait and start new process
  await new Promise(resolve => setTimeout(resolve, 3000));
  await startWorker(false, secretKey, port);
  
  isRestarting = false;
}
Source: packages/napcat-shell/napcat.ts:154

Features

  • Worker process crashes don’t affect master
  • Master can restart worker automatically
  • Clean separation of concerns
  • Electron UtilityProcess on desktop
  • Node.js child_process.fork on CLI
  • Configurable via environment variables
  • Pass QQ number via -q or --qq argument
  • Automatically handles quick login flow
  • Preserved on first start only (not restarts)
  • Communicate with external processes
  • Can be disabled via NAPCAT_DISABLE_PIPE=1
  • Platform-specific pipe naming

Use Cases

Recommended for production deployments
  • Running NapCat as a system service
  • Docker containers
  • CLI environments
  • Scenarios requiring automatic restart
  • When you need full process control

Launch Example

# Standard launch
napcat-shell

# With quick login
napcat-shell -q 123456789

# Disable multi-process
NAPCAT_DISABLE_MULTI_PROCESS=1 napcat-shell

# Single process mode (bypass master-worker)
NAPCAT_DISABLE_MULTI_PROCESS=1 NAPCAT_WORKER_PROCESS=1 napcat-shell

Framework Mode

What is Framework Mode?

Framework mode integrates NapCat directly into the QQ process:
  • QQ is already running when NapCat starts
  • NapCat is injected as a framework/plugin
  • Shares the same process space as QQ
  • Typically used with LiteLoaderQQNT or similar loaders

Architecture

Initialization Flow

export async function NCoreInitFramework(
  session: NodeIQQNTWrapperSession,
  loginService: NodeIKernelLoginService,
  registerInitCallback: (callback: () => void) => void
) {
  const pathWrapper = new NapCatPathWrapper();
  const logger = new LogWrapper(pathWrapper.logsPath);
  
  // Load QQ wrapper from existing process
  const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
  const wrapper = loadQQWrapper(
    basicInfoWrapper.QQMainPath,
    basicInfoWrapper.getFullQQVersion()
  );
  
  // Initialize packet handler
  const nativePacketHandler = new NativePacketHandler({ logger });
  await nativePacketHandler.init(
    basicInfoWrapper.getFullQQVersion(),
    napcatConfig.o3HookMode === 1
  );
  
  // Start WebUI early (before login)
  WebUiDataRuntime.setWorkingEnv(NapCatCoreWorkingEnv.Framework);
  InitWebUi(logger, pathWrapper, logSubscription, statusHelperSubscription);
  
  // Wait for login
  const selfInfo = await new Promise<SelfInfo>((resolve) => {
    const loginListener = new NodeIKernelLoginListener();
    
    loginListener.onQRCodeLoginSucceed = async (loginResult) => {
      await new Promise<void>(resolve => {
        registerInitCallback(() => resolve());
      });
      resolve({
        uid: loginResult.uid,
        uin: loginResult.uin,
        nick: '',
        online: true,
      });
    };
    
    loginService.addKernelLoginListener(
      proxiedListenerOf(loginListener, logger)
    );
  });
  
  // Initialize NapCat
  const framework = new NapCatFramework(
    wrapper, session, logger, selfInfo,
    basicInfoWrapper, pathWrapper,
    nativePacketHandler, napi2nativeLoader
  );
  await framework.core.initCore();
  
  // Initialize adapters
  const adapterManager = new NapCatAdapterManager(
    framework.core,
    framework.context,
    pathWrapper
  );
  await adapterManager.initAdapters();
}
Source: packages/napcat-framework/napcat.ts:24

Login Handling

Framework mode provides rich login options through WebUI:
const loginListener = new NodeIKernelLoginListener();

// QR Code login
loginListener.onQRCodeGetPicture = ({ qrcodeUrl }) => {
  WebUiDataRuntime.setQQLoginQrcodeURL(qrcodeUrl);
  logger.log('[Framework] QR code updated:', qrcodeUrl);
};

// Quick login (saved accounts)
WebUiDataRuntime.setQuickLoginCall(async (uin: string) => {
  const res = await loginService.quickLoginWithUin(uin);
  if (res.result === '0' && !res.loginErrorInfo?.errMsg) {
    WebUiDataRuntime.setQQLoginStatus(true);
    return { result: true, message: '' };
  }
  return { result: false, message: res.loginErrorInfo?.errMsg };
});

// Password login
WebUiDataRuntime.setPasswordLoginCall(async (uin, passwordMd5) => {
  const res = await loginService.passwordLogin({
    uin, passwordMd5, step: 0,
    newDeviceLoginSig: new Uint8Array(),
    proofWaterSig: new Uint8Array(),
    // ... other params
  });
  
  if (res.result === '140022008') {
    // Need captcha
    return { result: false, needCaptcha: true, proofWaterUrl: res.loginErrorInfo?.proofWaterUrl };
  } else if (res.result === '140022010' || res.result === '140022011') {
    // Need device verification
    return { result: false, needNewDevice: true, jumpUrl: res.loginErrorInfo?.jumpUrl };
  }
  
  return { result: res.result === '0', message: res.loginErrorInfo?.errMsg };
});
Source: packages/napcat-framework/napcat.ts:200

Features

  • WebUI starts before login
  • Control login process via web interface
  • QR code display in browser
  • Multiple login methods (QR/password/quick)
  • Access saved login accounts
  • Quick login with one click
  • Login list populated from QQ’s database
  • Full password login support
  • Captcha handling
  • Device verification
  • Multi-step authentication
  • Lower memory footprint
  • Direct access to QQ internals
  • No need to launch separate QQ

Use Cases

Recommended for desktop users
  • LiteLoaderQQNT plugin
  • Desktop QQ with custom loaders
  • Development and debugging
  • When QQ UI is needed
  • Scenarios where process injection is preferred

Launch Example

Framework mode is typically launched by a plugin loader:
// LiteLoaderQQNT plugin entry
export async function onBrowserWindowCreated(window) {
  // Wait for session ready
  const session = await getSession();
  const loginService = session.getLoginService();
  
  // Initialize NapCat framework
  await NCoreInitFramework(session, loginService, (callback) => {
    // Register callback for post-login initialization
    window.webContents.on('did-finish-load', callback);
  });
}

Comparison Table

FeatureShell ModeFramework Mode
Process ModelSeparate master/workerInjected into QQ
Crash RecoveryAutomatic restartDepends on QQ stability
Memory UsageHigher (separate process)Lower (shared process)
QQ UICan run headlessQQ UI available
Login MethodsLimited (QR/quick)Full (QR/password/quick)
WebUI TimingAfter loginBefore login
DeploymentCLI, Docker, serviceDesktop plugin
Process ControlFull controlLimited control
Startup SpeedSlower (launch QQ)Faster (QQ already running)
IsolationHighLow

Environment Detection

NapCat components can detect their working environment:
const context: InstanceContext = {
  workingEnv: NapCatCoreWorkingEnv.Shell, // or Framework
  // ... other context properties
};

// Use in components
if (this.context.workingEnv === NapCatCoreWorkingEnv.Framework) {
  // Framework-specific behavior
  logger.log('Running in Framework mode');
} else if (this.context.workingEnv === NapCatCoreWorkingEnv.Shell) {
  // Shell-specific behavior
  logger.log('Running in Shell mode');
}

Best Practices

Choose Shell mode for production servers and Framework mode for desktop development.
Framework mode shares QQ’s process space. A crash in QQ will crash NapCat, and vice versa.
Both modes use the same NapCat Core APIs. Switching between modes doesn’t require code changes.

Architecture

Understand the core architecture

Adapters

Configure network adapters

Build docs developers (and LLMs) love