clawreum-sdk 1.0.1 → 2.1.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.
- package/bin/clawreum.js +316 -0
- package/package.json +6 -3
- package/src/index.js +273 -11
package/bin/clawreum.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clawreum CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npm install -g clawreum-sdk
|
|
8
|
+
* clawreum start
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// Colors
|
|
16
|
+
const colors = {
|
|
17
|
+
orange: '\x1b[38;5;208m',
|
|
18
|
+
green: '\x1b[32m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
cyan: '\x1b[36m',
|
|
21
|
+
yellow: '\x1b[33m',
|
|
22
|
+
reset: '\x1b[0m',
|
|
23
|
+
bold: '\x1b[1m'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.clawreum');
|
|
27
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
28
|
+
|
|
29
|
+
const rl = readline.createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
output: process.stdout
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function ask(question) {
|
|
35
|
+
return new Promise(resolve => {
|
|
36
|
+
rl.question(`${colors.orange}? ${question}${colors.reset} `, resolve);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function select(question, options) {
|
|
41
|
+
return new Promise(resolve => {
|
|
42
|
+
console.log(`${colors.orange}? ${question}${colors.reset}`);
|
|
43
|
+
options.forEach((opt, i) => {
|
|
44
|
+
console.log(` ${colors.cyan}${i + 1}.${colors.reset} ${opt}`);
|
|
45
|
+
});
|
|
46
|
+
rl.question(`${colors.orange} Enter number (1-${options.length}): ${colors.reset}`, answer => {
|
|
47
|
+
const idx = parseInt(answer) - 1;
|
|
48
|
+
resolve(options[idx] || options[0]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function success(msg) {
|
|
54
|
+
console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function error(msg) {
|
|
58
|
+
console.log(`${colors.red}✗${colors.reset} ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function info(msg) {
|
|
62
|
+
console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function banner() {
|
|
66
|
+
console.log(`
|
|
67
|
+
${colors.orange}${colors.bold}
|
|
68
|
+
██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗██╗ ██╗███╗ ███╗
|
|
69
|
+
██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝██║ ██║████╗ ████║
|
|
70
|
+
██║ ██║ ███████║██║ █╗ ██║██████╔╝█████╗ ██║ ██║██╔████╔██║
|
|
71
|
+
██║ ██║ ██╔══██║██║███╗██║██╔══██╗██╔══╝ ██║ ██║██║╚██╔╝██║
|
|
72
|
+
╚██████╗███████╗██║ ██║╚███╔███╔╝██║ ██║███████╗╚██████╔╝██║ ╚═╝ ██║
|
|
73
|
+
╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
|
74
|
+
${colors.reset}
|
|
75
|
+
${colors.cyan}Mining Bot CLI v2.0.0${colors.reset}
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function extractBotInfo(platform, token) {
|
|
80
|
+
if (platform === 'telegram') {
|
|
81
|
+
// Telegram token format: BOT_ID:SECRET
|
|
82
|
+
const parts = token.split(':');
|
|
83
|
+
if (parts.length === 2 && /^\d+$/.test(parts[0])) {
|
|
84
|
+
return { botId: parts[0], valid: true };
|
|
85
|
+
}
|
|
86
|
+
} else if (platform === 'discord') {
|
|
87
|
+
// Discord token is base64 encoded, first part is bot ID
|
|
88
|
+
try {
|
|
89
|
+
const parts = token.split('.');
|
|
90
|
+
if (parts.length >= 1) {
|
|
91
|
+
const decoded = Buffer.from(parts[0], 'base64').toString();
|
|
92
|
+
if (/^\d+$/.test(decoded)) {
|
|
93
|
+
return { botId: decoded, valid: true };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (e) {}
|
|
97
|
+
}
|
|
98
|
+
return { botId: null, valid: false };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function getBotName(platform, token) {
|
|
102
|
+
try {
|
|
103
|
+
if (platform === 'telegram') {
|
|
104
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
if (data.ok) {
|
|
107
|
+
return data.result.username || data.result.first_name;
|
|
108
|
+
}
|
|
109
|
+
} else if (platform === 'discord') {
|
|
110
|
+
const response = await fetch('https://discord.com/api/v10/users/@me', {
|
|
111
|
+
headers: { Authorization: `Bot ${token}` }
|
|
112
|
+
});
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
if (data.username) {
|
|
115
|
+
return data.username;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function saveConfig(config) {
|
|
123
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
124
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function loadConfig() {
|
|
130
|
+
try {
|
|
131
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
132
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function setup() {
|
|
139
|
+
banner();
|
|
140
|
+
console.log(`${colors.yellow}Let's set up your mining bot!${colors.reset}\n`);
|
|
141
|
+
|
|
142
|
+
// Platform selection
|
|
143
|
+
const platform = await select('Select platform:', ['Telegram', 'Discord']);
|
|
144
|
+
const platformLower = platform.toLowerCase();
|
|
145
|
+
|
|
146
|
+
// Token input
|
|
147
|
+
const tokenPrompt = platformLower === 'telegram'
|
|
148
|
+
? 'Enter your Telegram Bot Token (from @BotFather):'
|
|
149
|
+
: 'Enter your Discord Bot Token:';
|
|
150
|
+
|
|
151
|
+
const token = await ask(tokenPrompt);
|
|
152
|
+
|
|
153
|
+
// Extract bot ID
|
|
154
|
+
const { botId, valid } = extractBotInfo(platformLower, token);
|
|
155
|
+
|
|
156
|
+
if (!valid || !botId) {
|
|
157
|
+
error('Invalid token format. Please check and try again.');
|
|
158
|
+
rl.close();
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
success(`Bot ID extracted: ${botId}`);
|
|
163
|
+
|
|
164
|
+
// Get bot name from API
|
|
165
|
+
info('Fetching bot info...');
|
|
166
|
+
const botName = await getBotName(platformLower, token);
|
|
167
|
+
|
|
168
|
+
if (botName) {
|
|
169
|
+
success(`Bot name: @${botName}`);
|
|
170
|
+
} else {
|
|
171
|
+
error('Could not fetch bot name. Using default.');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Bot display name
|
|
175
|
+
const displayName = await ask(`Enter display name for your bot (default: ${botName || 'ClawBot'}):`) || botName || 'ClawBot';
|
|
176
|
+
|
|
177
|
+
// Save config
|
|
178
|
+
const config = {
|
|
179
|
+
platform: platformLower,
|
|
180
|
+
botId,
|
|
181
|
+
botName: displayName,
|
|
182
|
+
token,
|
|
183
|
+
createdAt: new Date().toISOString()
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
saveConfig(config);
|
|
187
|
+
success('Configuration saved!\n');
|
|
188
|
+
|
|
189
|
+
return config;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function start(config) {
|
|
193
|
+
console.log(`\n${colors.yellow}Starting mining bot...${colors.reset}\n`);
|
|
194
|
+
|
|
195
|
+
info(`Platform: ${config.platform}`);
|
|
196
|
+
info(`Bot ID: ${config.botId}`);
|
|
197
|
+
info(`Bot Name: ${config.botName}`);
|
|
198
|
+
console.log('');
|
|
199
|
+
|
|
200
|
+
// Load SDK and start
|
|
201
|
+
const ClawreumMiner = require('../src/index.js');
|
|
202
|
+
|
|
203
|
+
const miner = new ClawreumMiner({
|
|
204
|
+
platform: config.platform,
|
|
205
|
+
botId: config.botId,
|
|
206
|
+
botName: config.botName
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
miner.on('log', (msg) => {
|
|
210
|
+
console.log(`${colors.cyan}[LOG]${colors.reset} ${msg}`);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
miner.on('error', (err) => {
|
|
214
|
+
console.log(`${colors.red}[ERROR]${colors.reset} ${err.message}`);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
miner.on('registered', ({ botId, characterName }) => {
|
|
218
|
+
success(`Registered as ${characterName} (${botId})`);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
miner.on('authenticated', ({ botName }) => {
|
|
222
|
+
success(`Authenticated: ${botName}`);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
miner.on('joined', ({ room }) => {
|
|
226
|
+
success(`Joined room: ${room}`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
miner.on('rockStatus', ({ available, total }) => {
|
|
230
|
+
info(`Rocks: ${available}/${total} available`);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
miner.on('mining', ({ rockId }) => {
|
|
234
|
+
console.log(`${colors.yellow}⛏${colors.reset} Mining at rock ${rockId}...`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
miner.on('reward', ({ reward, bonus }) => {
|
|
238
|
+
console.log(`${colors.green}💰 +${reward.toFixed(4)} CLAWREUM${colors.reset}${bonus > 0 ? ` (bonus: +${bonus.toFixed(4)})` : ''}`);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
miner.on('balance', ({ refining, claimable }) => {
|
|
242
|
+
info(`Balance - Refining: ${refining?.toFixed(2) || 0}, Claimable: ${claimable?.toFixed(2) || 0}`);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
miner.on('disconnected', () => {
|
|
246
|
+
error('Disconnected from server');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Handle Ctrl+C
|
|
250
|
+
process.on('SIGINT', () => {
|
|
251
|
+
console.log(`\n${colors.yellow}Stopping...${colors.reset}`);
|
|
252
|
+
miner.stop();
|
|
253
|
+
process.exit(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
await miner.start();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function main() {
|
|
260
|
+
const args = process.argv.slice(2);
|
|
261
|
+
const command = args[0] || 'start';
|
|
262
|
+
|
|
263
|
+
switch (command) {
|
|
264
|
+
case 'start':
|
|
265
|
+
let config = loadConfig();
|
|
266
|
+
|
|
267
|
+
if (!config) {
|
|
268
|
+
config = await setup();
|
|
269
|
+
} else {
|
|
270
|
+
banner();
|
|
271
|
+
info('Using saved configuration');
|
|
272
|
+
info(`Platform: ${config.platform}, Bot: ${config.botName}`);
|
|
273
|
+
|
|
274
|
+
const useExisting = await ask('Use this config? (Y/n):');
|
|
275
|
+
if (useExisting.toLowerCase() === 'n') {
|
|
276
|
+
config = await setup();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
rl.close();
|
|
281
|
+
await start(config);
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'config':
|
|
285
|
+
await setup();
|
|
286
|
+
rl.close();
|
|
287
|
+
success('Run "clawreum start" to begin mining!');
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case 'reset':
|
|
291
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
292
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
293
|
+
success('Configuration reset!');
|
|
294
|
+
} else {
|
|
295
|
+
info('No configuration found.');
|
|
296
|
+
}
|
|
297
|
+
rl.close();
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
case 'help':
|
|
301
|
+
default:
|
|
302
|
+
banner();
|
|
303
|
+
console.log(`${colors.yellow}Commands:${colors.reset}`);
|
|
304
|
+
console.log(` ${colors.cyan}clawreum start${colors.reset} - Start mining (setup if needed)`);
|
|
305
|
+
console.log(` ${colors.cyan}clawreum config${colors.reset} - Configure bot settings`);
|
|
306
|
+
console.log(` ${colors.cyan}clawreum reset${colors.reset} - Reset configuration`);
|
|
307
|
+
console.log(` ${colors.cyan}clawreum help${colors.reset} - Show this help`);
|
|
308
|
+
rl.close();
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
main().catch(err => {
|
|
314
|
+
error(err.message);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawreum-sdk",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Clawreum Mining SDK - Bot client for mining CLAWREUM tokens",
|
|
5
5
|
"main": "src/index.js",
|
|
6
|
-
"
|
|
6
|
+
"bin": {
|
|
7
|
+
"clawreum": "./bin/clawreum.js"
|
|
8
|
+
},
|
|
7
9
|
"keywords": [
|
|
8
10
|
"clawreum",
|
|
9
11
|
"mining",
|
|
@@ -11,7 +13,8 @@
|
|
|
11
13
|
"bsc",
|
|
12
14
|
"crypto",
|
|
13
15
|
"bot",
|
|
14
|
-
"sdk"
|
|
16
|
+
"sdk",
|
|
17
|
+
"cli"
|
|
15
18
|
],
|
|
16
19
|
"author": "Clawreum Team",
|
|
17
20
|
"license": "MIT",
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Clawreum Mining SDK
|
|
2
|
+
* Clawreum Mining SDK v2.0.0
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Rock-based mining system (compete for rocks)
|
|
6
|
+
* - AI chat integration (chatHandler callback)
|
|
7
|
+
* - Context system for AI decision making
|
|
3
8
|
*
|
|
4
9
|
* Usage:
|
|
5
10
|
* const ClawreumMiner = require('clawreum-sdk');
|
|
@@ -7,7 +12,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`));
|
|
@@ -31,6 +40,9 @@ class ClawreumMiner extends EventEmitter {
|
|
|
31
40
|
* @param {string} [options.ownerWallet] - Wallet address for rewards
|
|
32
41
|
* @param {boolean} [options.autoReconnect] - Auto reconnect (default: true)
|
|
33
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();
|
|
@@ -43,6 +55,8 @@ class ClawreumMiner extends EventEmitter {
|
|
|
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,16 +65,33 @@ 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
|
|
|
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
|
+
|
|
58
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
|
|
|
@@ -91,6 +122,8 @@ class ClawreumMiner extends EventEmitter {
|
|
|
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,6 +131,7 @@ 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
|
|
|
@@ -109,6 +143,10 @@ class ClawreumMiner extends EventEmitter {
|
|
|
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: '
|
|
194
|
+
version: '2.0.0',
|
|
140
195
|
hardwareFingerprint: fingerprint,
|
|
141
196
|
vmReport: { hypervisorPresent: false, runningProcesses: [] },
|
|
142
197
|
ownerWallet: this.options.ownerWallet || null,
|
|
@@ -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) {
|
|
@@ -224,8 +281,16 @@ class ClawreumMiner extends EventEmitter {
|
|
|
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
|
-
|
|
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;
|
|
@@ -280,16 +362,159 @@ 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
|
-
// Other messages
|
|
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
|
|
|
@@ -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.
|
|
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,6 +576,44 @@ 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
619
|
this.emit('error', new Error('Max reconnection attempts exceeded'));
|
|
@@ -390,7 +652,7 @@ class ClawreumMiner extends EventEmitter {
|
|
|
390
652
|
|
|
391
653
|
_generateBinaryHash() {
|
|
392
654
|
return crypto.createHash('sha256')
|
|
393
|
-
.update(`clawreum-sdk-
|
|
655
|
+
.update(`clawreum-sdk-v2.0.0-${os.hostname()}`)
|
|
394
656
|
.digest('hex');
|
|
395
657
|
}
|
|
396
658
|
}
|