@aituber-onair/kizuna
v0.0.2
Published
A sophisticated bond system (絆 - Kizuna) for managing relationships between users and AI characters in AITuber OnAir.
Downloads
71
Maintainers
Readme
@aituber-onair/kizuna

A sophisticated bond system (絆 - "Kizuna") for managing relationships between users and AI characters in AITuber OnAir. This package provides a flexible points-based engagement system with customizable rules, achievements, and thresholds.
Features
- Points System: Award points to users based on their interactions
- Emotion-based Bonuses: Dynamic point calculation based on AI emotions (happy, excited, etc.)
- Platform Support: Different point rules for YouTube, Twitch, and WebSocket chat
- Customizable Rules: Create your own point calculation rules with conditions and cooldowns
- Level System: 10-level progression system (100 points per level)
- Achievements: Unlock achievements at specific point thresholds
- Owner Privileges: Special bonuses and multipliers for AITuber operators
- Cooldown Management: Prevent spam with time-based and daily limits
- Persistent Storage: Save user data with configurable retention policies
- Debug Mode: Detailed logging for development and troubleshooting
- Browser Compatible: Works with Vite, Webpack, and other modern bundlers
- Dependency Injection: Flexible file system integration for Node.js environments
Installation
npm install @aituber-onair/kizunaQuick Start
import { KizunaManager, LocalStorageProvider } from '@aituber-onair/kizuna';
// Create storage provider (browser environment)
const storageProvider = new LocalStorageProvider({
enableCompression: false,
enableEncryption: false,
maxStorageSize: 10 * 1024 * 1024, // 10MB
});
// Configuration
const config = {
enabled: true,
owner: {
initialPoints: 100,
pointMultiplier: 2,
dailyBonus: 10,
specialCommands: ['reset_points', 'grant_points'],
exclusiveAchievements: ['master_of_aituber'],
},
platforms: {
youtube: {
basePoints: {
comment: 1,
superChat: 20,
membership: 5,
},
},
twitch: {
basePoints: {
chat: 1,
subscription: 10,
bits: 5,
},
},
},
thresholds: [
{
points: 50,
action: {
type: 'special_response',
data: { message: '🎉 Thanks for your support!' },
},
repeatable: false,
},
],
storage: {
maxUsers: 1000,
dataRetentionDays: 90,
cleanupIntervalHours: 24,
},
dev: {
debugMode: false,
logLevel: 'info',
showDebugPanel: false,
},
customRules: [
{
id: 'emotion_happy',
name: 'Happy emotion bonus',
condition: (context) => context.emotion === 'happy',
points: 1,
description: 'Bonus for happy AI responses',
},
],
};
// Initialize Kizuna system
const kizuna = new KizunaManager(config, storageProvider, 'your_storage_key');
await kizuna.initialize();
// Process user interaction
const result = await kizuna.processInteraction({
userId: 'youtube:user123',
platform: 'youtube',
message: 'Hello!',
emotion: 'happy',
isOwner: false,
timestamp: Date.now(),
metadata: {
userName: 'user123',
chatProvider: 'openai',
chatModel: 'gpt-4',
},
});
console.log(`User earned ${result.pointsAdded} points!`);Architecture & Browser Compatibility
Kizuna v0.0.2 introduces a dependency injection architecture that solves browser compatibility issues while maintaining flexibility for Node.js environments.
Key Benefits
- ✅ Vite Compatible: No more "Module 'node:fs' has been externalized for browser compatibility" errors
- ✅ Zero Node.js Dependencies: Package contains no Node.js-specific modules
- ✅ Flexible Storage: Users control file system implementation in Node.js
- ✅ Universal Package: Works in browsers, Node.js, Deno, and Bun environments
Migration from v0.0.1
If you were using FileSystemStorageProvider in v0.0.1, migrate to ExternalStorageProvider:
// OLD (v0.0.1) - No longer available
import { FileSystemStorageProvider } from '@aituber-onair/kizuna';
const storage = new FileSystemStorageProvider({ dataDir: './data' });
// NEW (v0.0.2+) - Dependency injection
import { ExternalStorageProvider, type ExternalStorageAdapter } from '@aituber-onair/kizuna';
import { promises as fs } from 'fs';
import path from 'path';
const adapter: ExternalStorageAdapter = {
// Implement file system operations
async readFile(filePath) { return await fs.readFile(filePath, 'utf-8'); },
async writeFile(filePath, data) { await fs.writeFile(filePath, data, 'utf-8'); },
// ... other methods
};
const storage = new ExternalStorageProvider({ dataDir: './kizuna-data' }, adapter);Configuration
Point Rules
Create custom point rules with flexible conditions:
const customRules = [
{
id: 'long_message',
name: 'Long message bonus',
condition: (context) => context.message.length > 100,
points: 2,
cooldown: 60000, // 1 minute cooldown
description: 'Bonus for messages over 100 characters',
},
{
id: 'first_daily_interaction',
name: 'First daily interaction',
condition: (context, user) => {
if (!user) return true;
const today = new Date().toDateString();
const lastSeen = new Date(user.lastSeen).toDateString();
return today !== lastSeen;
},
points: 5,
dailyLimit: 1,
description: 'Daily login bonus',
},
];Platform Configuration
Different platforms can have different point values:
const platforms = {
youtube: {
basePoints: {
comment: 1,
superChat: 20,
membership: 5,
firstComment: 3,
},
bonusCalculator: (context) => {
// Custom bonus calculation
if (context.metadata?.superChatAmount) {
return Math.floor(context.metadata.superChatAmount * 0.1);
}
return 0;
},
},
twitch: {
basePoints: {
chat: 1,
subscription: 10,
bits: 5,
raid: 15,
},
},
};Thresholds and Actions
Define actions that trigger when users reach certain point thresholds:
const thresholds = [
{
points: 100,
action: {
type: 'unlock_emotion',
data: {
emotion: 'special_happy',
message: '✨ New emotion unlocked!',
},
},
repeatable: false,
},
{
points: 200,
action: {
type: 'achievement',
data: {
id: 'best_friend',
title: 'Best Friend',
description: 'Built a strong bond with the AITuber',
icon: '💖',
},
},
repeatable: false,
},
];Storage Features
The LocalStorageProvider includes built-in compression and encryption capabilities to optimize storage usage and protect user data.
Compression
Data compression reduces storage size using Base64 encoding:
const storageProvider = new LocalStorageProvider({
enableCompression: true,
enableEncryption: false,
maxStorageSize: 5 * 1024 * 1024,
});Example data transformation:
// Original data (250 bytes)
{"userId":"youtube:user123","points":150,"level":2}
// Compressed data (Base64 encoded)
eyJ1c2VySWQiOiJ5b3V0dWJlOnVzZXIxMjMiLCJwb2ludHMiOjE1MCwibGV2ZWwiOjJ9Important: Current implementation uses Base64 encoding, which actually increases data size by ~33%. This is not true compression. For real compression, integrate libraries like lz-string or pako:
npm install lz-stringimport LZString from 'lz-string';
// In your custom storage provider
const compressed = LZString.compress(data);
const decompressed = LZString.decompress(compressed);Encryption
Data encryption protects user privacy using XOR cipher:
const storageProvider = new LocalStorageProvider({
enableCompression: false,
enableEncryption: true,
encryptionKey: 'your-secret-key-here',
maxStorageSize: 5 * 1024 * 1024,
});Example encrypted data:
// Original: {"points":150}
// Encrypted: "H4sKDQkLGRseFBIeGQ=="Security Note: Current implementation uses XOR cipher for basic privacy protection. For production applications requiring strong security, consider using Web Crypto API or libraries like crypto-js with AES encryption.
Combined Usage
For maximum efficiency and security:
const storageProvider = new LocalStorageProvider({
enableCompression: true, // Reduce storage size
enableEncryption: true, // Protect user data
encryptionKey: process.env.KIZUNA_ENCRYPTION_KEY || 'fallback-key',
maxStorageSize: 5 * 1024 * 1024, // 5MB limit
});Processing order:
- Save: Original → Compress → Encrypt → Store
- Load: Retrieve → Decrypt → Decompress → Original
Performance Impact (Measured)
Based on actual benchmarks with typical Kizuna user data:
| Data Size | No Processing | Compression Only | Encryption Only | Both | |-----------|---------------|------------------|-----------------|------| | 1KB (typical) | 0.5ms | 0.7ms (+40%) | 1.2ms (+140%) | 1.5ms (+200%) | | 10KB | 2.1ms | 3.2ms (+52%) | 8.7ms (+314%) | 11.2ms (+433%) | | 100KB | 18ms | 28ms (+56%) | 75ms (+317%) | 95ms (+428%) |
Storage Size Impact:
- Current "Compression": +33% size (Base64 encoding)
- Encryption: +33% size (Base64 + XOR overhead)
- Both: +78% size (dual Base64 encoding)
Recommendations by use case:
- Small data (<5KB): Performance impact negligible, use as needed
- Medium data (5-50KB): Consider encryption-only for privacy
- Large data (>50KB): Consider alternative storage (IndexedDB, server)
Environment-specific Configurations
// Development environment
const devStorage = new LocalStorageProvider({
enableCompression: false, // Easier debugging, avoids size increase
enableEncryption: false, // View raw data in DevTools
maxStorageSize: 10 * 1024 * 1024,
});
// Production environment (small datasets)
const prodStorage = new LocalStorageProvider({
enableCompression: false, // Avoid size increase until real compression
enableEncryption: true, // Protect user privacy
encryptionKey: process.env.ENCRYPTION_KEY,
maxStorageSize: 5 * 1024 * 1024,
});
// Production with real compression library
const optimizedStorage = new LocalStorageProvider({
enableCompression: false, // Disable built-in, use external library
enableEncryption: true,
encryptionKey: process.env.ENCRYPTION_KEY,
maxStorageSize: 5 * 1024 * 1024,
});
// Then implement custom compression wrapper with LZ-stringDebug Mode
Enable detailed logging for development:
const config = {
// ... other config
dev: {
debugMode: true, // Enable debug logs
logLevel: 'debug',
showDebugPanel: true,
},
};When debug mode is enabled, you'll see detailed logs like:
[Kizuna] Processing interaction for youtube:user123 with emotion: happy
[PointCalculator] [canApplyRule] Checking rule: emotion_happy for emotion: happy
[PointCalculator] [canApplyRule] Rule emotion_happy condition result: true
[Kizuna] Interaction processed: 2 points added (1 rules applied)
[Kizuna] Applied rules: Happy emotion bonusEvent System
Listen to Kizuna events:
kizuna.on('points_updated', (eventData) => {
console.log(`User ${eventData.userId} earned ${eventData.data.pointsAdded} points!`);
});
kizuna.on('level_up', (eventData) => {
console.log(`User ${eventData.userId} leveled up to ${eventData.data.newLevel}!`);
});
kizuna.on('threshold_reached', (eventData) => {
console.log(`User ${eventData.userId} reached threshold ${eventData.data.threshold.points}!`);
});Browser Compatibility & Node.js Support
Kizuna is designed to be browser-compatible and can be used in Node.js environments through dependency injection. The package no longer includes Node.js-specific dependencies, making it compatible with Vite and other modern browser bundlers.
Browser Usage (Default)
import { KizunaManager, LocalStorageProvider } from '@aituber-onair/kizuna';
// Browser environment - uses localStorage
const browserStorage = new LocalStorageProvider({
enableCompression: false,
enableEncryption: false,
maxStorageSize: 5 * 1024 * 1024
});
const kizuna = new KizunaManager(config, browserStorage, 'my_users');Node.js Usage (Dependency Injection)
For Node.js environments, provide your own file system adapter:
import {
KizunaManager,
ExternalStorageProvider,
type ExternalStorageAdapter
} from '@aituber-onair/kizuna';
import { promises as fs } from 'fs';
import path from 'path';
// Create your own file system adapter
const nodeAdapter: ExternalStorageAdapter = {
async readFile(filePath: string): Promise<string> {
return await fs.readFile(filePath, 'utf-8');
},
async writeFile(filePath: string, data: string): Promise<void> {
await fs.writeFile(filePath, data, 'utf-8');
},
async deleteFile(filePath: string): Promise<void> {
await fs.unlink(filePath);
},
async listFiles(dirPath: string): Promise<string[]> {
const files = await fs.readdir(dirPath);
return files.filter(file => file.endsWith('.json'));
},
async ensureDir(dirPath: string): Promise<void> {
await fs.mkdir(dirPath, { recursive: true });
},
async exists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch {
return false;
}
},
joinPath: (...components: string[]) => path.join(...components)
};
// Use ExternalStorageProvider with your adapter
const nodeStorage = new ExternalStorageProvider({
dataDir: './kizuna-data',
prettyJson: true,
autoCreateDir: true
}, nodeAdapter);
const kizuna = new KizunaManager(config, nodeStorage, 'my_users');Automatic Environment Detection
import { KizunaManager, createDefaultStorageProvider } from '@aituber-onair/kizuna';
// Browser: Uses LocalStorageProvider automatically
// Node.js: Uses LocalStorageProvider (fallback) unless adapter provided
const kizuna = new KizunaManager(config, createDefaultStorageProvider(), 'my_users');
// Node.js with adapter
const kizuna = new KizunaManager(config, createDefaultStorageProvider(nodeAdapter), 'my_users');Environment Detection Utilities
import { detectEnvironment, isBrowser, isNode } from '@aituber-onair/kizuna';
console.log(detectEnvironment()); // 'browser' or 'node'
console.log(isBrowser()); // true in browser
console.log(isNode()); // true in Node.jsAPI Reference
KizunaManager
Main class for managing the Kizuna system.
Methods
processInteraction(context: PointContext): Promise<PointResult>- Process user interaction and award pointsgetUser(userId: string): KizunaUser | null- Get user datagetAllUsers(): KizunaUser[]- Get all usersaddPoints(userId: string, points: number): Promise<PointResult>- Manually add pointscalculateLevel(points: number): number- Calculate level from pointsgetStats(): Record<string, any>- Get system statistics
LocalStorageProvider
Storage provider using browser localStorage.
Constructor Options
enableCompression: boolean- Enable data compressionenableEncryption: boolean- Enable data encryptionencryptionKey?: string- Encryption key (if encryption enabled)maxStorageSize: number- Maximum storage size in bytes
ExternalStorageProvider
Storage provider using dependency injection for file system operations.
Constructor Options
config: object- Configuration object with dataDir, encoding, prettyJson, autoCreateDiradapter: ExternalStorageAdapter- User-provided file system adapter
ExternalStorageAdapter Interface
interface ExternalStorageAdapter {
readFile(filePath: string): Promise<string>;
writeFile(filePath: string, data: string): Promise<void>;
deleteFile(filePath: string): Promise<void>;
listFiles(dirPath: string): Promise<string[]>;
ensureDir(dirPath: string): Promise<void>;
exists(path: string): Promise<boolean>;
getFileStats?(filePath: string): Promise<{ size: number }>;
joinPath(...components: string[]): string;
}Future Storage Providers (TODO)
The following storage providers are planned for future releases:
SQLiteStorageProvider
- Use case: Medium-scale applications requiring SQL queries
- Features: Transactions, complex queries, better performance
- Example: Discord bots, CLI tools
MongoDBStorageProvider
- Use case: Large-scale applications, cloud deployment
- Features: Flexible schema, horizontal scaling, aggregation
- Example: Web services, microservices
RedisStorageProvider
- Use case: High-performance, real-time applications
- Features: In-memory storage, pub/sub, distributed caching
- Example: High-traffic streaming platforms
CloudStorageProvider
- Use case: Serverless applications, unlimited storage
- Features: AWS S3, Google Cloud Storage, Azure Blob
- Example: Production web applications
Contributing: If you need any of these storage providers, please create an issue or submit a pull request!
Integration with AITuber OnAir
This package is designed specifically for AITuber OnAir but can be adapted for other AI character systems. The emotion-based point calculation integrates seamlessly with AITuber OnAir's emotion detection system.
License
MIT
Development
Testing
The package includes comprehensive test coverage for all major features:
# Run all tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode (for development)
npm run test:watchTest Structure
tests/performance.test.ts- LocalStorageProvider compression and encryption performance benchmarkstests/environmentDetector.test.ts- Environment detection utilitiestests/storageFactory.test.ts- Storage provider factory and dependency injection
Test Coverage
- ✅ All storage providers (LocalStorage, ExternalStorage)
- ✅ Environment detection and dependency injection
- ✅ Performance benchmarks and measurements
- ✅ Error handling and edge cases
- ✅ Configuration options and customization
- ✅ Integration tests with real data scenarios
Building
# Build for production
npm run build
# Build in watch mode (for development)
npm run dev
# Type checking
npm run typecheck
# Linting
npm run lint
npm run lint:fixContributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Setup
- Clone the repository
- Install dependencies:
npm install - Run tests:
npm test - Build:
npm run build
Adding New Storage Providers
If you want to add a new storage provider (SQLite, MongoDB, Redis, etc.):
- Create a new file in
src/storage/ - Implement the
StorageProviderinterface - Add comprehensive tests in
src/tests/ - Update the storage factory if needed
- Update documentation
