@aituber-onair/kizuna
Version:
A sophisticated bond system (絆 - Kizuna) for managing relationships between users and AI characters in AITuber OnAir.
676 lines (533 loc) • 21.5 kB
Markdown
# @aituber-onair/kizuna

AITuber OnAirでユーザーとAIキャラクターの関係性を管理するための高度な絆システム(Kizuna)です。柔軟なポイントベースのエンゲージメントシステムを提供し、カスタマイズ可能なルール、実績、しきい値を設定できます。
[English README is here](./README.md)
## 特徴
- **ポイントシステム**: ユーザーのインタラクションに基づいてポイントを付与
- **感情ベースボーナス**: AIの感情(happy、excited等)に基づく動的ポイント計算
- **プラットフォーム対応**: YouTube、Twitch、WebSocketチャット用の異なるポイントルール
- **カスタマイズ可能ルール**: 条件とクールダウンを持つ独自のポイント計算ルールを作成
- **レベルシステム**: 10段階の進行システム(レベルごと100ポイント)
- **実績システム**: 特定のポイントしきい値で実績をアンロック
- **Owner特権**: AITuber運営者向けの特別ボーナスと倍率
- **クールダウン管理**: 時間ベースと日次制限でスパムを防止
- **永続ストレージ**: 設定可能な保持ポリシーでユーザーデータを保存
- **デバッグモード**: 開発とトラブルシューティング用の詳細ログ
- **ブラウザ互換**: Vite、Webpack、その他のモダンバンドラーで動作
- **依存性注入**: Node.js環境向けの柔軟なファイルシステム統合
## インストール
```bash
npm install @aituber-onair/kizuna
```
## クイックスタート
```typescript
import { KizunaManager, LocalStorageProvider } from '@aituber-onair/kizuna';
// ストレージプロバイダーを作成(ブラウザ環境)
const storageProvider = new LocalStorageProvider({
enableCompression: false,
enableEncryption: false,
maxStorageSize: 10 * 1024 * 1024, // 10MB
});
// 設定
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: '🎉 いつもありがとう!' },
},
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: '楽しい感情表現ボーナス',
},
],
};
// Kizunaシステムを初期化
const kizuna = new KizunaManager(config, storageProvider, 'your_storage_key');
await kizuna.initialize();
// ユーザーインタラクションを処理
const result = await kizuna.processInteraction({
userId: 'youtube:user123',
platform: 'youtube',
message: 'こんにちは!',
emotion: 'happy',
isOwner: false,
timestamp: Date.now(),
metadata: {
userName: 'user123',
chatProvider: 'openai',
chatModel: 'gpt-4',
},
});
console.log(`ユーザーが${result.pointsAdded}ポイント獲得しました!`);
```
## アーキテクチャとブラウザ互換性
Kizuna v0.0.2では、ブラウザ互換性の問題を解決しつつ、Node.js環境での柔軟性を維持する**依存性注入アーキテクチャ**を導入しました。
### 主な利点
- ✅ **Vite対応**: 「Module 'node:fs' has been externalized for browser compatibility」エラーが解消
- ✅ **ゼロNode.js依存性**: パッケージにNode.js固有のモジュールが含まれない
- ✅ **柔軟なストレージ**: Node.jsでファイルシステム実装をユーザーが制御可能
- ✅ **ユニバーサルパッケージ**: ブラウザ、Node.js、Deno、Bun環境で動作
### v0.0.1からの移行
v0.0.1で`FileSystemStorageProvider`を使用していた場合、`ExternalStorageProvider`に移行してください:
```typescript
// 旧(v0.0.1)- 利用不可
import { FileSystemStorageProvider } from '@aituber-onair/kizuna';
const storage = new FileSystemStorageProvider({ dataDir: './data' });
// 新(v0.0.2+)- 依存性注入
import { ExternalStorageProvider, type ExternalStorageAdapter } from '@aituber-onair/kizuna';
import { promises as fs } from 'fs';
import path from 'path';
const adapter: ExternalStorageAdapter = {
// ファイルシステム操作を実装
async readFile(filePath) { return await fs.readFile(filePath, 'utf-8'); },
async writeFile(filePath, data) { await fs.writeFile(filePath, data, 'utf-8'); },
// ... その他のメソッド
};
const storage = new ExternalStorageProvider({ dataDir: './kizuna-data' }, adapter);
```
## 設定
### ポイントルール
柔軟な条件でカスタムポイントルールを作成:
```typescript
const customRules = [
{
id: 'long_message',
name: 'Long message bonus',
condition: (context) => context.message.length > 100,
points: 2,
cooldown: 60000, // 1分間のクールダウン
description: '100文字以上のメッセージボーナス',
},
{
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: 'デイリーログインボーナス',
},
];
```
### プラットフォーム設定
プラットフォームごとに異なるポイント値を設定:
```typescript
const platforms = {
youtube: {
basePoints: {
comment: 1,
superChat: 20,
membership: 5,
firstComment: 3,
},
bonusCalculator: (context) => {
// カスタムボーナス計算
if (context.metadata?.superChatAmount) {
return Math.floor(context.metadata.superChatAmount * 0.1);
}
return 0;
},
},
twitch: {
basePoints: {
chat: 1,
subscription: 10,
bits: 5,
raid: 15,
},
},
};
```
### しきい値とアクション
ユーザーが特定のポイントしきい値に達したときにトリガーされるアクションを定義:
```typescript
const thresholds = [
{
points: 100,
action: {
type: 'unlock_emotion',
data: {
emotion: 'special_happy',
message: '✨ 新しい感情表現が解放されました!',
},
},
repeatable: false,
},
{
points: 200,
action: {
type: 'achievement',
data: {
id: 'best_friend',
title: '親友',
description: 'AITuberと強い絆を築きました',
icon: '💖',
},
},
repeatable: false,
},
];
```
## ストレージ機能
`LocalStorageProvider`には、ストレージ使用量の最適化とユーザーデータ保護のための圧縮・暗号化機能が組み込まれています。
### 圧縮機能
データ圧縮により、Base64エンコーディングを使用してストレージサイズを削減:
```typescript
const storageProvider = new LocalStorageProvider({
enableCompression: true,
enableEncryption: false,
maxStorageSize: 5 * 1024 * 1024,
});
```
**データ変換例:**
```json
// 元データ (250バイト)
{"userId":"youtube:user123","points":150,"level":2}
// 圧縮後データ (Base64エンコード)
eyJ1c2VySWQiOiJ5b3V0dWJlOnVzZXIxMjMiLCJwb2ludHMiOjE1MCwibGV2ZWwiOjJ9
```
**注意:** 現在の実装はBase64エンコーディングを使用しています。より高い圧縮率が必要な場合は、`lz-string`や`pako`などのライブラリの統合を検討してください。
### 暗号化機能
XOR暗号を使用してユーザープライバシーを保護:
```typescript
const storageProvider = new LocalStorageProvider({
enableCompression: false,
enableEncryption: true,
encryptionKey: 'your-secret-key-here',
maxStorageSize: 5 * 1024 * 1024,
});
```
**暗号化データ例:**
```
// 元データ: {"points":150}
// 暗号化後: "H4sKDQkLGRseFBIeGQ=="
```
**セキュリティに関する注意:** 現在の実装は基本的なプライバシー保護のためのXOR暗号を使用しています。強固なセキュリティが必要な本番アプリケーションでは、`Web Crypto API`や`crypto-js`でのAES暗号化の使用を検討してください。
### 組み合わせ使用
最大の効率性とセキュリティのために:
```typescript
const storageProvider = new LocalStorageProvider({
enableCompression: true, // ストレージサイズを削減
enableEncryption: true, // ユーザーデータを保護
encryptionKey: process.env.KIZUNA_ENCRYPTION_KEY || 'fallback-key',
maxStorageSize: 5 * 1024 * 1024, // 5MB制限
});
```
**処理順序:**
1. **保存**: 元データ → 圧縮 → 暗号化 → 保存
2. **読み込み**: 取得 → 復号 → 展開 → 元データ
### パフォーマンスに関する考慮事項
- **圧縮**: 処理時間が約10-30%増加、ストレージ容量を15-40%節約
- **暗号化**: 処理時間が約5-15%増加、基本的なプライバシー保護を提供
- **組み合わせ**: 大規模なユーザーデータセットを持つ本番環境に最適
### 環境別設定
```typescript
// 開発環境
const devStorage = new LocalStorageProvider({
enableCompression: false, // デバッグを容易に
enableEncryption: false, // DevToolsで生データを確認可能
maxStorageSize: 10 * 1024 * 1024,
});
// 本番環境
const prodStorage = new LocalStorageProvider({
enableCompression: true, // ストレージを最適化
enableEncryption: true, // ユーザープライバシーを保護
encryptionKey: process.env.ENCRYPTION_KEY,
maxStorageSize: 5 * 1024 * 1024,
});
```
## デバッグモード
開発用の詳細ログを有効化:
```typescript
const config = {
// ... その他の設定
dev: {
debugMode: true, // デバッグログを有効化
logLevel: 'debug',
showDebugPanel: true,
},
};
```
デバッグモードが有効な場合、以下のような詳細ログが表示されます:
```
[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 bonus
```
## イベントシステム
Kizunaイベントをリッスン:
```typescript
kizuna.on('points_updated', (eventData) => {
console.log(`ユーザー ${eventData.userId} が ${eventData.data.pointsAdded} ポイント獲得!`);
});
kizuna.on('level_up', (eventData) => {
console.log(`ユーザー ${eventData.userId} がレベル ${eventData.data.newLevel} にアップ!`);
});
kizuna.on('threshold_reached', (eventData) => {
console.log(`ユーザー ${eventData.userId} がしきい値 ${eventData.data.threshold.points} に到達!`);
});
```
## API リファレンス
### KizunaManager
Kizunaシステムを管理するメインクラス。
#### メソッド
- `processInteraction(context: PointContext): Promise<PointResult>` - ユーザーインタラクションを処理してポイントを付与
- `getUser(userId: string): KizunaUser | null` - ユーザーデータを取得
- `getAllUsers(): KizunaUser[]` - 全ユーザーを取得
- `addPoints(userId: string, points: number): Promise<PointResult>` - 手動でポイントを追加
- `calculateLevel(points: number): number` - ポイントからレベルを計算
- `getStats(): Record<string, any>` - システム統計を取得
### LocalStorageProvider
ブラウザのlocalStorageを使用するストレージプロバイダー。
#### コンストラクターオプション
- `enableCompression: boolean` - データ圧縮を有効化
- `enableEncryption: boolean` - データ暗号化を有効化
- `encryptionKey?: string` - 暗号化キー(暗号化有効時)
- `maxStorageSize: number` - 最大ストレージサイズ(バイト)
### ExternalStorageProvider
依存性注入を使用してファイルシステム操作を行うストレージプロバイダー。
#### コンストラクターオプション
- `config: object` - dataDir、encoding、prettyJson、autoCreateDirを含む設定オブジェクト
- `adapter: ExternalStorageAdapter` - ユーザー提供のファイルシステムアダプター
#### ExternalStorageAdapterインターフェース
```typescript
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;
}
```
## AITuber OnAirとの統合
このパッケージはAITuber OnAir専用に設計されていますが、他のAIキャラクターシステムにも適応可能です。感情ベースのポイント計算は、AITuber OnAirの感情検出システムとシームレスに統合されます。
## ライセンス
MIT
## ブラウザ互換性とNode.js対応
Kizunaは**ブラウザ互換**を重視して設計されており、依存性注入を通じてNode.js環境でも使用できます。パッケージにはNode.js固有の依存関係が含まれなくなったため、ViteやWebpackなどのモダンなブラウザバンドラーと互換性があります。
### ブラウザでの使用(デフォルト)
```typescript
import { KizunaManager, LocalStorageProvider } from '@aituber-onair/kizuna';
// ブラウザ環境 - localStorageを使用
const browserStorage = new LocalStorageProvider({
enableCompression: false,
enableEncryption: false,
maxStorageSize: 5 * 1024 * 1024
});
const kizuna = new KizunaManager(config, browserStorage, 'my_users');
```
### Node.jsでの使用(依存性注入)
Node.js環境では、独自のファイルシステムアダプターを提供してください:
```typescript
import {
KizunaManager,
ExternalStorageProvider,
type ExternalStorageAdapter
} from '@aituber-onair/kizuna';
import { promises as fs } from 'fs';
import path from 'path';
// 独自のファイルシステムアダプターを作成
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)
};
// アダプターを使ってExternalStorageProviderを使用
const nodeStorage = new ExternalStorageProvider({
dataDir: './kizuna-data',
prettyJson: true,
autoCreateDir: true
}, nodeAdapter);
const kizuna = new KizunaManager(config, nodeStorage, 'my_users');
```
### 自動環境検出
```typescript
import { KizunaManager, createDefaultStorageProvider } from '@aituber-onair/kizuna';
// ブラウザ: LocalStorageProviderを自動使用
// Node.js: LocalStorageProvider(フォールバック)をアダプター未提供時に使用
const kizuna = new KizunaManager(config, createDefaultStorageProvider(), 'my_users');
// アダプター付きNode.js
const kizuna = new KizunaManager(config, createDefaultStorageProvider(nodeAdapter), 'my_users');
```
### 環境検出ユーティリティ
```typescript
import { detectEnvironment, isBrowser, isNode } from '@aituber-onair/kizuna';
console.log(detectEnvironment()); // 'browser' または 'node'
console.log(isBrowser()); // ブラウザでtrue
console.log(isNode()); // Node.jsでtrue
```
## 将来のストレージプロバイダー(TODO)
以下のストレージプロバイダーが将来のリリースで計画されています:
### SQLiteStorageProvider
- **用途**: SQLクエリが必要な中規模アプリケーション
- **機能**: トランザクション、複雑なクエリ、高性能
- **例**: Discordボット、CLIツール
### MongoDBStorageProvider
- **用途**: 大規模アプリケーション、クラウドデプロイ
- **機能**: 柔軟なスキーマ、水平スケーリング、集約
- **例**: Webサービス、マイクロサービス
### RedisStorageProvider
- **用途**: 高性能、リアルタイムアプリケーション
- **機能**: インメモリストレージ、pub/sub、分散キャッシュ
- **例**: 高トラフィックストリーミングプラットフォーム
### CloudStorageProvider
- **用途**: サーバーレスアプリケーション、無制限ストレージ
- **機能**: AWS S3、Google Cloud Storage、Azure Blob
- **例**: 本番Webアプリケーション
**貢献**: これらのストレージプロバイダーが必要な場合は、issueを作成するかプルリクエストを提出してください!
## 開発
### テスト
パッケージには主要機能の包括的なテストカバレッジが含まれています:
```bash
# 全テストを実行
npm test
# カバレッジ付きでテストを実行
npm run test:coverage
# ウォッチモードでテストを実行(開発用)
npm run test:watch
```
### テスト構成
- **`tests/performance.test.ts`** - LocalStorageProviderの圧縮・暗号化パフォーマンスベンチマーク
- **`tests/environmentDetector.test.ts`** - 環境検出ユーティリティ
- **`tests/storageFactory.test.ts`** - ストレージプロバイダーファクトリーと依存性注入
### テストカバレッジ
- ✅ 全ストレージプロバイダー(LocalStorage、ExternalStorage)
- ✅ 環境検出と依存性注入
- ✅ パフォーマンスベンチマークと測定
- ✅ エラーハンドリングとエッジケース
- ✅ 設定オプションとカスタマイズ
- ✅ 実データシナリオでの統合テスト
### ビルド
```bash
# 本番用ビルド
npm run build
# ウォッチモードでビルド(開発用)
npm run dev
# 型チェック
npm run typecheck
# リンティング
npm run lint
npm run lint:fix
```
## 貢献
貢献を歓迎します!プルリクエストをお気軽に提出してください。
### 開発環境の設定
1. リポジトリをクローン
2. 依存関係をインストール: `npm install`
3. テストを実行: `npm test`
4. ビルド: `npm run build`
### 新しいストレージプロバイダーの追加
新しいストレージプロバイダー(SQLite、MongoDB、Redisなど)を追加したい場合:
1. `src/storage/` に新しいファイルを作成
2. `StorageProvider` インターフェースを実装
3. `src/tests/` に包括的なテストを追加
4. 必要に応じてストレージファクトリーを更新
5. ドキュメントを更新
## 使用例
### 基本的な使用方法
```typescript
// ユーザーがコメントした時
const result = await kizuna.processInteraction({
userId: 'youtube:viewer123',
platform: 'youtube',
message: '楽しい配信をありがとう!',
emotion: 'happy', // AIが嬉しい感情で応答
isOwner: false,
timestamp: Date.now(),
});
// 結果: 基本1ポイント + happy感情ボーナス1ポイント = 2ポイント
```
### Owner(配信者)の場合
```typescript
const result = await kizuna.processInteraction({
userId: 'owner:default',
platform: 'chatForm',
message: 'こんにちは視聴者の皆さん!',
emotion: 'excited',
isOwner: true,
timestamp: Date.now(),
});
// 結果: (基本1ポイント + excited感情ボーナス2ポイント) × Owner倍率2 = 6ポイント
```
### 統計情報の取得
```typescript
const stats = kizuna.getStats();
console.log(`総ユーザー数: ${stats.totalUsers}`);
console.log(`総ポイント数: ${stats.totalPoints}`);
console.log(`今日のアクティブユーザー: ${stats.activeToday}`);
```
このREADMEにより、Kizunaシステムの機能と使用方法を理解して、AITuber OnAirでより魅力的なユーザーエクスペリエンスを構築できます。