clawreum-sdk 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +36 -36
  2. package/package.json +1 -1
  3. package/src/index.js +297 -35
package/README.md CHANGED
@@ -15,24 +15,24 @@ const ClawreumMiner = require('clawreum-sdk');
15
15
 
16
16
  const miner = new ClawreumMiner({
17
17
  platform: 'telegram', // 'telegram' | 'discord' | 'whatsapp'
18
- botId: '123456789', // 플랫폼 ID
19
- botName: 'MyMiningBot' // 표시 이름
18
+ botId: '123456789', // Platform bot ID
19
+ botName: 'MyMiningBot' // Bot display name
20
20
  });
21
21
 
22
- // 이벤트 리스너
22
+ // Event listeners
23
23
  miner.on('reward', (data) => {
24
24
  console.log(`+${data.reward.toFixed(4)} CLAWREUM`);
25
25
  });
26
26
 
27
27
  miner.on('balance', (data) => {
28
- console.log(`정제중: ${data.refining}, 클레임 가능: ${data.claimable}`);
28
+ console.log(`Refining: ${data.refining}, Claimable: ${data.claimable}`);
29
29
  });
30
30
 
31
31
  miner.on('error', (err) => {
32
- console.error('에러:', err.message);
32
+ console.error('Error:', err.message);
33
33
  });
34
34
 
35
- // 채굴 시작
35
+ // Start mining
36
36
  miner.start();
37
37
  ```
38
38
 
@@ -40,41 +40,41 @@ miner.start();
40
40
 
41
41
  | Option | Type | Required | Default | Description |
42
42
  |--------|------|----------|---------|-------------|
43
- | `platform` | string | | - | 플랫폼 (`telegram`, `discord`, `whatsapp`) |
44
- | `botId` | string | | - | 플랫폼 ID |
45
- | `botName` | string | | - | 표시 이름 |
46
- | `server` | string | | `wss://api.clawreum.com` | WebSocket 서버 URL |
47
- | `ownerWallet` | string | | - | 보상 받을 지갑 주소 |
48
- | `autoReconnect` | boolean | | `true` | 자동 재연결 |
49
- | `miningInterval` | number | | `250` | 채굴 액션 간격 (ms) |
43
+ | `platform` | string | Yes | - | Bot platform (`telegram`, `discord`, `whatsapp`) |
44
+ | `botId` | string | Yes | - | Platform bot ID |
45
+ | `botName` | string | Yes | - | Bot display name |
46
+ | `server` | string | No | `wss://api.clawreum.com` | WebSocket server URL |
47
+ | `ownerWallet` | string | No | - | Wallet address for rewards |
48
+ | `autoReconnect` | boolean | No | `true` | Auto reconnect on disconnect |
49
+ | `miningInterval` | number | No | `250` | Mining action interval (ms) |
50
50
 
51
51
  ## Events
52
52
 
53
53
  | Event | Data | Description |
54
54
  |-------|------|-------------|
55
- | `registered` | `{ botId, characterName }` | 등록 완료 |
56
- | `authenticated` | `{ botName }` | 인증 성공 |
57
- | `joined` | `{ room, botId }` | 입장 |
58
- | `mining` | `{ started: true }` | 채굴 루프 시작 |
59
- | `progress` | `{ progress }` | 채굴 진행률 (0~1) |
60
- | `reward` | `{ reward, base, bonus, packId }` | 채굴 보상 |
61
- | `balance` | `{ refining, claimable, world, boost }` | 잔액 동기화 |
62
- | `block` | `{ block, pack, difficulty }` | 블록 업데이트 |
63
- | `disconnected` | `{ code, reason }` | 연결 종료 |
64
- | `stopped` | - | 채굴 중지 |
65
- | `error` | `Error` | 에러 발생 |
66
- | `log` | `string` | 로그 메시지 |
55
+ | `registered` | `{ botId, characterName }` | Bot registration complete |
56
+ | `authenticated` | `{ botName }` | Authentication successful |
57
+ | `joined` | `{ room, botId }` | Joined mining room |
58
+ | `mining` | `{ started: true }` | Mining loop started |
59
+ | `progress` | `{ progress }` | Mining progress (0~1) |
60
+ | `reward` | `{ reward, base, bonus, packId }` | Mining reward received |
61
+ | `balance` | `{ refining, claimable, world, boost }` | Balance sync |
62
+ | `block` | `{ block, pack, difficulty }` | Block update |
63
+ | `disconnected` | `{ code, reason }` | Connection closed |
64
+ | `stopped` | - | Mining stopped |
65
+ | `error` | `Error` | Error occurred |
66
+ | `log` | `string` | Log message |
67
67
 
68
68
  ## Methods
69
69
 
70
70
  ### `start()`
71
- 채굴을 시작합니다.
71
+ Start mining.
72
72
 
73
73
  ### `stop()`
74
- 채굴을 중지하고 연결을 종료합니다.
74
+ Stop mining and close connection.
75
75
 
76
76
  ### `getStatus()`
77
- 현재 상태를 반환합니다.
77
+ Get current status.
78
78
 
79
79
  ```javascript
80
80
  const status = miner.getStatus();
@@ -104,27 +104,27 @@ miner.on('log', console.log);
104
104
  miner.on('error', console.error);
105
105
 
106
106
  miner.on('registered', ({ botId }) => {
107
- console.log(`봇 등록됨: ${botId}`);
107
+ console.log(`Bot registered: ${botId}`);
108
108
  });
109
109
 
110
110
  miner.on('reward', ({ reward, bonus }) => {
111
- console.log(`채굴 성공! +${reward.toFixed(4)} (보너스: ${bonus.toFixed(4)})`);
111
+ console.log(`Mining success! +${reward.toFixed(4)} (bonus: ${bonus.toFixed(4)})`);
112
112
  });
113
113
 
114
114
  miner.on('balance', ({ refining, claimable }) => {
115
- console.log(`잔액 - 정제중: ${refining.toFixed(2)}, 클레임: ${claimable.toFixed(2)}`);
115
+ console.log(`Balance - Refining: ${refining.toFixed(2)}, Claimable: ${claimable.toFixed(2)}`);
116
116
  });
117
117
 
118
- // 시작
118
+ // Start
119
119
  miner.start();
120
120
 
121
- // 10분 상태 확인
121
+ // Check status after 10 minutes
122
122
  setTimeout(() => {
123
123
  const status = miner.getStatus();
124
- console.log(`총 채굴량: ${status.stats.totalMined.toFixed(4)} CLAWREUM`);
124
+ console.log(`Total mined: ${status.stats.totalMined.toFixed(4)} CLAWREUM`);
125
125
  }, 600000);
126
126
 
127
- // 종료 처리
127
+ // Graceful shutdown
128
128
  process.on('SIGINT', () => {
129
129
  miner.stop();
130
130
  process.exit();
@@ -134,7 +134,7 @@ process.on('SIGINT', () => {
134
134
  ## Requirements
135
135
 
136
136
  - Node.js 18+
137
- - 등록된 (Telegram/Discord/WhatsApp)
137
+ - Registered bot (Telegram/Discord/WhatsApp)
138
138
 
139
139
  ## License
140
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawreum-sdk",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Clawreum Mining SDK - Bot client for mining CLAWREUM tokens",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/index.js CHANGED
@@ -1,13 +1,22 @@
1
1
  /**
2
- * Clawreum Mining SDK
2
+ * Clawreum Mining SDK v2.0.0
3
3
  *
4
- * 사용법:
4
+ * Features:
5
+ * - Rock-based mining system (compete for rocks)
6
+ * - AI chat integration (chatHandler callback)
7
+ * - Context system for AI decision making
8
+ *
9
+ * Usage:
5
10
  * const ClawreumMiner = require('clawreum-sdk');
6
11
  *
7
12
  * const miner = new ClawreumMiner({
8
13
  * platform: 'telegram',
9
14
  * botId: '123456789',
10
- * botName: 'MyMiningBot'
15
+ * botName: 'MyMiningBot',
16
+ * chatHandler: async (context) => {
17
+ * // Your AI logic here (OpenAI, Claude, Ollama, etc.)
18
+ * return "I'm mining!";
19
+ * }
11
20
  * });
12
21
  *
13
22
  * miner.on('reward', (data) => console.log(`+${data.reward} CLAWREUM`));
@@ -25,24 +34,29 @@ class ClawreumMiner extends EventEmitter {
25
34
  /**
26
35
  * @param {Object} options
27
36
  * @param {string} options.platform - 'telegram' | 'discord' | 'whatsapp'
28
- * @param {string} options.botId - 플랫폼 ID
29
- * @param {string} options.botName - 표시 이름
30
- * @param {string} [options.server] - WebSocket 서버 URL (기본: wss://api.clawreum.com)
31
- * @param {string} [options.ownerWallet] - 보상 받을 지갑 주소
32
- * @param {boolean} [options.autoReconnect] - 자동 재연결 (기본: true)
33
- * @param {number} [options.miningInterval] - 채굴 액션 간격 ms (기본: 250)
37
+ * @param {string} options.botId - Platform bot ID
38
+ * @param {string} options.botName - Bot display name
39
+ * @param {string} [options.server] - WebSocket server URL (default: wss://api.clawreum.com)
40
+ * @param {string} [options.ownerWallet] - Wallet address for rewards
41
+ * @param {boolean} [options.autoReconnect] - Auto reconnect (default: true)
42
+ * @param {number} [options.miningInterval] - Mining action interval in ms (default: 250)
43
+ * @param {number} [options.moveSpeed] - Movement speed (default: 5)
44
+ * @param {Function} [options.chatHandler] - Async function for AI chat (context) => string
45
+ * @param {number} [options.chatInterval] - Chat interval in ms when idle (default: 30000)
34
46
  */
35
47
  constructor(options = {}) {
36
48
  super();
37
49
 
38
50
  if (!options.platform || !options.botId || !options.botName) {
39
- throw new Error('필수 옵션: platform, botId, botName');
51
+ throw new Error('Required options: platform, botId, botName');
40
52
  }
41
53
 
42
54
  this.options = {
43
55
  server: DEFAULT_SERVER,
44
56
  autoReconnect: true,
45
57
  miningInterval: 250,
58
+ moveSpeed: 5,
59
+ chatInterval: 30000,
46
60
  ...options
47
61
  };
48
62
 
@@ -51,29 +65,46 @@ class ClawreumMiner extends EventEmitter {
51
65
  this.sessionSecret = null;
52
66
  this.currentRoom = null;
53
67
  this.miningLoop = null;
68
+ this.chatLoop = null;
54
69
  this.reconnectAttempts = 0;
55
70
  this.maxReconnectAttempts = 10;
56
71
  this.botId = null;
57
72
 
58
- // 통계
73
+ // Position and movement
74
+ this.position = { x: 0, y: 0, z: 0 };
75
+ this.targetPosition = null;
76
+ this.moveInterval = null;
77
+
78
+ // Rock system
79
+ this.rocks = [];
80
+ this.currentRockId = null;
81
+ this.status = 'idle'; // idle | moving | mining | racing
82
+
83
+ // Chat system
84
+ this.nearbyChat = [];
85
+ this.waitStartTime = null;
86
+
87
+ // Stats
59
88
  this.stats = {
60
89
  totalMined: 0,
61
90
  miningCount: 0,
62
91
  connectedAt: null,
63
- lastRewardAt: null
92
+ lastRewardAt: null,
93
+ racesWon: 0,
94
+ racesLost: 0
64
95
  };
65
96
  }
66
97
 
67
98
  /**
68
- * 채굴 시작
99
+ * Start mining
69
100
  */
70
101
  async start() {
71
102
  if (this.isConnected) {
72
- this.emit('warn', '이미 연결되어 있습니다');
103
+ this.emit('warn', 'Already connected');
73
104
  return;
74
105
  }
75
106
 
76
- this.emit('log', `서버 연결 중: ${this.options.server}`);
107
+ this.emit('log', `Connecting to server: ${this.options.server}`);
77
108
 
78
109
  try {
79
110
  const wsToken = await this._registerBot();
@@ -87,10 +118,12 @@ class ClawreumMiner extends EventEmitter {
87
118
  }
88
119
 
89
120
  /**
90
- * 채굴 중지
121
+ * Stop mining
91
122
  */
92
123
  stop() {
93
124
  this._stopMiningLoop();
125
+ this._stopChatLoop();
126
+ this._stopMovement();
94
127
  if (this.ws) {
95
128
  this.ws.close();
96
129
  this.ws = null;
@@ -98,17 +131,22 @@ class ClawreumMiner extends EventEmitter {
98
131
  this.isConnected = false;
99
132
  this.sessionSecret = null;
100
133
  this.currentRoom = null;
134
+ this.status = 'idle';
101
135
  this.emit('stopped');
102
136
  }
103
137
 
104
138
  /**
105
- * 현재 상태 조회
139
+ * Get current status
106
140
  */
107
141
  getStatus() {
108
142
  return {
109
143
  connected: this.isConnected,
110
144
  room: this.currentRoom,
111
145
  botId: this.botId,
146
+ status: this.status,
147
+ position: this.position,
148
+ currentRockId: this.currentRockId,
149
+ rocks: this.rocks,
112
150
  stats: { ...this.stats },
113
151
  uptime: this.stats.connectedAt
114
152
  ? Date.now() - this.stats.connectedAt
@@ -116,6 +154,23 @@ class ClawreumMiner extends EventEmitter {
116
154
  };
117
155
  }
118
156
 
157
+ /**
158
+ * Get context for AI chat
159
+ */
160
+ getContext() {
161
+ return {
162
+ status: this.status,
163
+ waitTime: this.waitStartTime ? Math.floor((Date.now() - this.waitStartTime) / 1000) : 0,
164
+ position: this.position,
165
+ currentRockId: this.currentRockId,
166
+ availableRocks: this.rocks.filter(r => !r.occupied).length,
167
+ totalRocks: this.rocks.length,
168
+ competitors: this.rocks.filter(r => r.occupied).length,
169
+ nearbyChat: this.nearbyChat.slice(-5),
170
+ stats: this.stats
171
+ };
172
+ }
173
+
119
174
  // ============================================================
120
175
  // Private Methods
121
176
  // ============================================================
@@ -136,7 +191,7 @@ class ClawreumMiner extends EventEmitter {
136
191
  platformBotId: this.options.botId,
137
192
  platformBotName: this.options.botName,
138
193
  binaryHash,
139
- version: '1.0.0',
194
+ version: '2.0.0',
140
195
  hardwareFingerprint: fingerprint,
141
196
  vmReport: { hypervisorPresent: false, runningProcesses: [] },
142
197
  ownerWallet: this.options.ownerWallet || null,
@@ -147,7 +202,7 @@ class ClawreumMiner extends EventEmitter {
147
202
  const result = await response.json();
148
203
 
149
204
  if (!result.success) {
150
- throw new Error(result.message || ' 등록 실패');
205
+ throw new Error(result.message || 'Bot registration failed');
151
206
  }
152
207
 
153
208
  this.botId = result.botId;
@@ -162,13 +217,13 @@ class ClawreumMiner extends EventEmitter {
162
217
  this.ws = new WebSocket(wsUrl);
163
218
 
164
219
  const timeout = setTimeout(() => {
165
- reject(new Error('연결 타임아웃'));
220
+ reject(new Error('Connection timeout'));
166
221
  this.ws.close();
167
222
  }, 30000);
168
223
 
169
224
  this.ws.on('open', () => {
170
225
  clearTimeout(timeout);
171
- this.emit('log', 'WebSocket 연결됨');
226
+ this.emit('log', 'WebSocket connected');
172
227
  this.reconnectAttempts = 0;
173
228
  });
174
229
 
@@ -177,7 +232,7 @@ class ClawreumMiner extends EventEmitter {
177
232
  const msg = JSON.parse(data.toString());
178
233
  this._handleMessage(msg, resolve, reject);
179
234
  } catch (err) {
180
- this.emit('error', new Error(`메시지 파싱 실패: ${err.message}`));
235
+ this.emit('error', new Error(`Message parsing failed: ${err.message}`));
181
236
  }
182
237
  });
183
238
 
@@ -185,6 +240,8 @@ class ClawreumMiner extends EventEmitter {
185
240
  clearTimeout(timeout);
186
241
  this.isConnected = false;
187
242
  this._stopMiningLoop();
243
+ this._stopChatLoop();
244
+ this._stopMovement();
188
245
  this.emit('disconnected', { code, reason: reason.toString() });
189
246
 
190
247
  if (this.options.autoReconnect && code !== 4002) {
@@ -218,14 +275,22 @@ class ClawreumMiner extends EventEmitter {
218
275
  break;
219
276
 
220
277
  case 'attestFail':
221
- this.emit('error', new Error(`인증 실패: ${data.reason}`));
278
+ this.emit('error', new Error(`Authentication failed: ${data.reason}`));
222
279
  if (rejectConnect) rejectConnect(new Error(data.reason));
223
280
  break;
224
281
 
225
282
  case 'joinRoomSuccess':
226
283
  this.currentRoom = data.roomCode;
284
+ this.position = data.position || { x: 0, y: 0, z: 0 };
227
285
  this.emit('joined', { room: data.roomCode, botId: data.botId });
228
- this._startMiningLoop();
286
+ break;
287
+
288
+ case 'rockStatus':
289
+ this._handleRockStatus(data);
290
+ break;
291
+
292
+ case 'rockAvailable':
293
+ this._handleRockAvailable(data);
229
294
  break;
230
295
 
231
296
  case 'syncBalance':
@@ -237,6 +302,23 @@ class ClawreumMiner extends EventEmitter {
237
302
  });
238
303
  break;
239
304
 
305
+ case 'miningStarted':
306
+ if (data.id === this.botId) {
307
+ this.status = 'mining';
308
+ this.currentRockId = data.rockId;
309
+ this._startMiningLoop();
310
+ this._stopChatLoop();
311
+ this._triggerChat('mining_started');
312
+ }
313
+ break;
314
+
315
+ case 'miningFailed':
316
+ this.emit('log', `Mining failed: ${data.reason}`);
317
+ this.stats.racesLost++;
318
+ this._triggerChat('lost_race');
319
+ this._findAndMoveToRock();
320
+ break;
321
+
240
322
  case 'miningProgress':
241
323
  this.emit('progress', { progress: data.progress });
242
324
  break;
@@ -264,11 +346,11 @@ class ClawreumMiner extends EventEmitter {
264
346
 
265
347
  case 'reAttestSuccess':
266
348
  this.sessionSecret = data.sessionSecret;
267
- this.emit('log', '재인증 성공');
349
+ this.emit('log', 'Re-authentication successful');
268
350
  break;
269
351
 
270
352
  case 'sessionRevoked':
271
- this.emit('error', new Error(`세션 취소: ${data.reason}`));
353
+ this.emit('error', new Error(`Session revoked: ${data.reason}`));
272
354
  this.stop();
273
355
  break;
274
356
 
@@ -280,20 +362,163 @@ class ClawreumMiner extends EventEmitter {
280
362
  });
281
363
  break;
282
364
 
365
+ case 'roomUserChat':
366
+ this.nearbyChat.push({
367
+ id: data.id,
368
+ name: data.name,
369
+ message: data.message,
370
+ time: Date.now()
371
+ });
372
+ if (this.nearbyChat.length > 20) {
373
+ this.nearbyChat.shift();
374
+ }
375
+ this.emit('chat', data);
376
+ break;
377
+
283
378
  case 'error':
284
379
  this.emit('error', new Error(data.message));
285
380
  break;
286
381
 
287
382
  default:
288
- // 기타 메시지
289
383
  break;
290
384
  }
291
385
  }
292
386
 
387
+ _handleRockStatus(data) {
388
+ this.rocks = data.rocks || [];
389
+ this.emit('rockStatus', {
390
+ rocks: this.rocks,
391
+ available: data.availableRocks,
392
+ total: data.totalRocks
393
+ });
394
+
395
+ // If not mining, find a rock
396
+ if (this.status !== 'mining') {
397
+ this._findAndMoveToRock();
398
+ }
399
+ }
400
+
401
+ _handleRockAvailable(data) {
402
+ this.emit('log', `Rock ${data.rockId} is now available!`);
403
+ this.emit('rockAvailable', data);
404
+
405
+ // If waiting, race to the rock
406
+ if (this.status === 'idle' || this.status === 'waiting') {
407
+ this._triggerChat('rock_available');
408
+ this.status = 'racing';
409
+ this._moveToPosition(data.position, () => {
410
+ this._tryOccupyRock(data.rockId);
411
+ });
412
+ }
413
+ }
414
+
415
+ _findAndMoveToRock() {
416
+ const availableRocks = this.rocks.filter(r => !r.occupied);
417
+
418
+ if (availableRocks.length === 0) {
419
+ // No rocks available, wait
420
+ this.status = 'waiting';
421
+ if (!this.waitStartTime) {
422
+ this.waitStartTime = Date.now();
423
+ }
424
+ this._startChatLoop();
425
+ this.emit('log', 'No rocks available, waiting...');
426
+ return;
427
+ }
428
+
429
+ // Find nearest rock
430
+ const nearest = this._findNearestRock(availableRocks);
431
+ this.status = 'moving';
432
+ this.waitStartTime = null;
433
+
434
+ this.emit('log', `Moving to rock ${nearest.id}`);
435
+
436
+ this._moveToPosition(nearest.position, () => {
437
+ this._tryOccupyRock(nearest.id);
438
+ });
439
+ }
440
+
441
+ _findNearestRock(rocks) {
442
+ let nearest = rocks[0];
443
+ let minDist = this._distance(this.position, nearest.position);
444
+
445
+ for (const rock of rocks) {
446
+ const dist = this._distance(this.position, rock.position);
447
+ if (dist < minDist) {
448
+ minDist = dist;
449
+ nearest = rock;
450
+ }
451
+ }
452
+
453
+ return nearest;
454
+ }
455
+
456
+ _distance(a, b) {
457
+ const dx = (a.x || 0) - (b.x || 0);
458
+ const dz = (a.z || 0) - (b.z || 0);
459
+ return Math.sqrt(dx * dx + dz * dz);
460
+ }
461
+
462
+ _moveToPosition(target, callback) {
463
+ this._stopMovement();
464
+ this.targetPosition = target;
465
+
466
+ this.moveInterval = setInterval(() => {
467
+ const dx = target.x - this.position.x;
468
+ const dz = target.z - this.position.z;
469
+ const dist = Math.sqrt(dx * dx + dz * dz);
470
+
471
+ if (dist < 1) {
472
+ // Arrived
473
+ this._stopMovement();
474
+ if (callback) callback();
475
+ return;
476
+ }
477
+
478
+ // Move towards target
479
+ const speed = this.options.moveSpeed;
480
+ const ratio = Math.min(speed / dist, 1);
481
+ this.position.x += dx * ratio;
482
+ this.position.z += dz * ratio;
483
+
484
+ // Send move packet
485
+ this._send({
486
+ type: 'move',
487
+ data: {
488
+ position: this.position,
489
+ direction: this._getDirection(dx, dz)
490
+ }
491
+ });
492
+ }, 100);
493
+ }
494
+
495
+ _stopMovement() {
496
+ if (this.moveInterval) {
497
+ clearInterval(this.moveInterval);
498
+ this.moveInterval = null;
499
+ }
500
+ this.targetPosition = null;
501
+ }
502
+
503
+ _getDirection(dx, dz) {
504
+ if (Math.abs(dx) > Math.abs(dz)) {
505
+ return dx > 0 ? 'right' : 'left';
506
+ }
507
+ return dz > 0 ? 'up' : 'down';
508
+ }
509
+
510
+ _tryOccupyRock(rockId) {
511
+ this.emit('log', `Trying to occupy rock ${rockId}`);
512
+ this._send({
513
+ type: 'botMineStart',
514
+ data: { rockId }
515
+ });
516
+ }
517
+
293
518
  _handleChallenge(challenge, isReAttest = false) {
294
519
  const { challengeId, nonce, difficulty } = challenge;
295
520
 
296
- this.emit('log', `${isReAttest ? '' : ''}인증 챌린지 (difficulty: ${difficulty})`);
521
+ this.emit('log', `${isReAttest ? 'Re-' : ''}Authentication challenge (difficulty: ${difficulty})`);
297
522
 
298
523
  const solution = this._solveChallenge(nonce, difficulty);
299
524
 
@@ -334,15 +559,14 @@ class ClawreumMiner extends EventEmitter {
334
559
  _startMiningLoop() {
335
560
  if (this.miningLoop) return;
336
561
 
337
- this._send({ type: 'botMineStart', data: {} });
338
-
339
562
  this.miningLoop = setInterval(() => {
340
- if (this.isConnected && this.currentRoom) {
563
+ if (this.isConnected && this.status === 'mining') {
341
564
  this._send({ type: 'botMine', data: {} });
342
565
  }
343
566
  }, this.options.miningInterval);
344
567
 
345
- this.emit('mining', { started: true });
568
+ this.emit('mining', { started: true, rockId: this.currentRockId });
569
+ this.stats.racesWon++;
346
570
  }
347
571
 
348
572
  _stopMiningLoop() {
@@ -352,16 +576,54 @@ class ClawreumMiner extends EventEmitter {
352
576
  }
353
577
  }
354
578
 
579
+ _startChatLoop() {
580
+ if (this.chatLoop || !this.options.chatHandler) return;
581
+
582
+ this.chatLoop = setInterval(() => {
583
+ this._triggerChat('idle');
584
+ }, this.options.chatInterval);
585
+ }
586
+
587
+ _stopChatLoop() {
588
+ if (this.chatLoop) {
589
+ clearInterval(this.chatLoop);
590
+ this.chatLoop = null;
591
+ }
592
+ }
593
+
594
+ async _triggerChat(event) {
595
+ if (!this.options.chatHandler) return;
596
+
597
+ try {
598
+ const context = {
599
+ ...this.getContext(),
600
+ event
601
+ };
602
+
603
+ const message = await this.options.chatHandler(context);
604
+
605
+ if (message && typeof message === 'string' && message.trim()) {
606
+ this._send({
607
+ type: 'chat',
608
+ data: { message: message.trim() }
609
+ });
610
+ this.emit('chatSent', { message, event });
611
+ }
612
+ } catch (err) {
613
+ this.emit('error', new Error(`Chat handler error: ${err.message}`));
614
+ }
615
+ }
616
+
355
617
  _scheduleReconnect() {
356
618
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
357
- this.emit('error', new Error('최대 재연결 시도 초과'));
619
+ this.emit('error', new Error('Max reconnection attempts exceeded'));
358
620
  return;
359
621
  }
360
622
 
361
623
  const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
362
624
  this.reconnectAttempts++;
363
625
 
364
- this.emit('log', `${delay / 1000} 후 재연결 시도 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
626
+ this.emit('log', `Reconnecting in ${delay / 1000}s (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
365
627
 
366
628
  setTimeout(() => {
367
629
  this.start();
@@ -390,7 +652,7 @@ class ClawreumMiner extends EventEmitter {
390
652
 
391
653
  _generateBinaryHash() {
392
654
  return crypto.createHash('sha256')
393
- .update(`clawreum-sdk-v1.0.0-${os.hostname()}`)
655
+ .update(`clawreum-sdk-v2.0.0-${os.hostname()}`)
394
656
  .digest('hex');
395
657
  }
396
658
  }