clawmate 1.4.0 → 1.4.2
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/index.js +441 -442
- package/main/ai-bridge.js +59 -59
- package/main/ai-connector.js +60 -60
- package/main/autostart.js +6 -6
- package/main/desktop-path.js +4 -4
- package/main/file-command-parser.js +46 -46
- package/main/file-ops.js +27 -27
- package/main/index.js +17 -17
- package/main/ipc-handlers.js +24 -24
- package/main/manifest.js +2 -2
- package/main/platform.js +16 -16
- package/main/smart-file-ops.js +64 -64
- package/main/store.js +1 -1
- package/main/telegram.js +137 -137
- package/main/tray.js +61 -61
- package/main/updater.js +13 -13
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/preload/preload.js +18 -18
- package/renderer/css/effects.css +6 -6
- package/renderer/css/pet.css +8 -8
- package/renderer/css/speech.css +5 -5
- package/renderer/first-run.html +14 -14
- package/renderer/index.html +4 -4
- package/renderer/js/ai-controller.js +91 -91
- package/renderer/js/app.js +24 -24
- package/renderer/js/browser-watcher.js +32 -32
- package/renderer/js/character.js +33 -33
- package/renderer/js/interactions.js +21 -21
- package/renderer/js/memory.js +60 -60
- package/renderer/js/metrics.js +141 -141
- package/renderer/js/mode-manager.js +13 -13
- package/renderer/js/pet-engine.js +236 -236
- package/renderer/js/speech.js +19 -19
- package/renderer/js/state-machine.js +23 -23
- package/renderer/js/time-aware.js +15 -15
- package/renderer/launcher.html +8 -8
- package/shared/constants.js +11 -11
- package/shared/messages.js +130 -130
- package/shared/personalities.js +44 -44
- package/skills/launch-pet/index.js +57 -47
- package/skills/launch-pet/skill.json +12 -23
package/main/telegram.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Telegram Bot Integration Module
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
4
|
+
* Bidirectional communication between Telegram messages and ClawMate.
|
|
5
|
+
* - Parses incoming Telegram messages and forwards commands to AI Bridge
|
|
6
|
+
* - Relays pet state/speech back to Telegram
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* 1.
|
|
10
|
-
* 2.
|
|
11
|
-
* 3.
|
|
8
|
+
* Bot token priority:
|
|
9
|
+
* 1. Environment variable CLAWMATE_TELEGRAM_TOKEN
|
|
10
|
+
* 2. Config file (Store)
|
|
11
|
+
* 3. If neither exists, silently disabled (no error)
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* Dependency: node-telegram-bot-api (npm install node-telegram-bot-api)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const EventEmitter = require('events');
|
|
17
17
|
const { parseMessage } = require('./file-command-parser');
|
|
18
18
|
const { executeSmartFileOp } = require('./smart-file-ops');
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Dynamically load Telegram Bot API (silently ignored if not installed)
|
|
21
21
|
let TelegramBotAPI = null;
|
|
22
22
|
try {
|
|
23
23
|
TelegramBotAPI = require('node-telegram-bot-api');
|
|
24
24
|
} catch {
|
|
25
|
-
// node-telegram-bot-api
|
|
25
|
+
// node-telegram-bot-api not installed — Telegram features disabled
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
class TelegramBot extends EventEmitter {
|
|
29
29
|
/**
|
|
30
|
-
* @param {object} bridge - AIBridge
|
|
31
|
-
* @param {object} options -
|
|
32
|
-
* - token:
|
|
33
|
-
* - allowedChatIds:
|
|
30
|
+
* @param {object} bridge - AIBridge instance
|
|
31
|
+
* @param {object} options - Additional options
|
|
32
|
+
* - token: Bot token (takes priority over env variable)
|
|
33
|
+
* - allowedChatIds: List of allowed chat IDs (security)
|
|
34
34
|
*/
|
|
35
35
|
constructor(bridge, options = {}) {
|
|
36
36
|
super();
|
|
@@ -38,24 +38,24 @@ class TelegramBot extends EventEmitter {
|
|
|
38
38
|
this.bot = null;
|
|
39
39
|
this.active = false;
|
|
40
40
|
this.allowedChatIds = options.allowedChatIds || null;
|
|
41
|
-
this.activeChatIds = new Set(); //
|
|
41
|
+
this.activeChatIds = new Set(); // Track active chat IDs
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Track in-progress file operations
|
|
44
44
|
this._fileOpInProgress = false;
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// Determine bot token
|
|
47
47
|
const token = options.token
|
|
48
48
|
|| process.env.CLAWMATE_TELEGRAM_TOKEN
|
|
49
49
|
|| null;
|
|
50
50
|
|
|
51
51
|
if (!token) {
|
|
52
|
-
console.log('[Telegram]
|
|
52
|
+
console.log('[Telegram] No bot token — Telegram features disabled');
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
if (!TelegramBotAPI) {
|
|
57
|
-
console.log('[Telegram] node-telegram-bot-api
|
|
58
|
-
console.log('[Telegram]
|
|
57
|
+
console.log('[Telegram] node-telegram-bot-api not installed — Telegram features disabled');
|
|
58
|
+
console.log('[Telegram] Install: npm install node-telegram-bot-api');
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -63,46 +63,46 @@ class TelegramBot extends EventEmitter {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Initialize bot and register message listeners
|
|
67
67
|
*/
|
|
68
68
|
_init(token) {
|
|
69
69
|
try {
|
|
70
70
|
this.bot = new TelegramBotAPI(token, { polling: true });
|
|
71
71
|
this.active = true;
|
|
72
|
-
console.log('[Telegram]
|
|
72
|
+
console.log('[Telegram] Bot initialized — waiting for messages');
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Message receive handler
|
|
75
75
|
this.bot.on('message', (msg) => this._handleMessage(msg));
|
|
76
76
|
|
|
77
|
-
//
|
|
77
|
+
// Error handler (disconnection, etc.)
|
|
78
78
|
this.bot.on('polling_error', (err) => {
|
|
79
|
-
//
|
|
79
|
+
// Silently retry unless fatal error like invalid token
|
|
80
80
|
if (err.code === 'ETELEGRAM' && err.response?.statusCode === 401) {
|
|
81
|
-
console.error('[Telegram]
|
|
81
|
+
console.error('[Telegram] Invalid bot token — Telegram disabled');
|
|
82
82
|
this.stop();
|
|
83
83
|
}
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
// AI Bridge
|
|
86
|
+
// Receive pet events from AI Bridge and forward to Telegram
|
|
87
87
|
this._setupBridgeListeners();
|
|
88
88
|
} catch (err) {
|
|
89
|
-
console.error('[Telegram]
|
|
89
|
+
console.error('[Telegram] Bot initialization failed:', err.message);
|
|
90
90
|
this.active = false;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* AI Bridge
|
|
95
|
+
* Set up AI Bridge event listeners (pet -> Telegram)
|
|
96
96
|
*/
|
|
97
97
|
_setupBridgeListeners() {
|
|
98
98
|
if (!this.bridge) return;
|
|
99
99
|
|
|
100
|
-
//
|
|
100
|
+
// Forward pet speech to Telegram
|
|
101
101
|
this.bridge.on('speak', (payload) => {
|
|
102
102
|
this._broadcastToChats(`[Claw] ${payload.text}`);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
//
|
|
105
|
+
// Forward speech from AI decisions
|
|
106
106
|
this.bridge.on('ai_decision', (payload) => {
|
|
107
107
|
if (payload.speech) {
|
|
108
108
|
this._broadcastToChats(`[Claw] ${payload.speech}`);
|
|
@@ -111,7 +111,7 @@ class TelegramBot extends EventEmitter {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
114
|
+
* Handle incoming Telegram message
|
|
115
115
|
*/
|
|
116
116
|
async _handleMessage(msg) {
|
|
117
117
|
if (!this.active) return;
|
|
@@ -119,29 +119,29 @@ class TelegramBot extends EventEmitter {
|
|
|
119
119
|
|
|
120
120
|
const chatId = msg.chat.id;
|
|
121
121
|
|
|
122
|
-
//
|
|
122
|
+
// Security: only process allowed chat IDs
|
|
123
123
|
if (this.allowedChatIds && !this.allowedChatIds.includes(chatId)) {
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
//
|
|
127
|
+
// Track active chat IDs (for broadcasting)
|
|
128
128
|
this.activeChatIds.add(chatId);
|
|
129
129
|
|
|
130
130
|
const text = msg.text.trim();
|
|
131
|
-
console.log(`[Telegram]
|
|
131
|
+
console.log(`[Telegram] Received (${chatId}): ${text}`);
|
|
132
132
|
|
|
133
|
-
//
|
|
133
|
+
// Handle special commands
|
|
134
134
|
if (text === '/start') {
|
|
135
135
|
await this.bot.sendMessage(chatId,
|
|
136
|
-
'ClawMate
|
|
137
|
-
'
|
|
138
|
-
'-
|
|
139
|
-
'-
|
|
140
|
-
'-
|
|
141
|
-
'-
|
|
142
|
-
'- /reset:
|
|
143
|
-
'- /status:
|
|
144
|
-
'- /undo:
|
|
136
|
+
'ClawMate connected! \n\n' +
|
|
137
|
+
'Available commands:\n' +
|
|
138
|
+
'- Any message: Talk to the pet\n' +
|
|
139
|
+
'- Action keywords: jump, sleep, dance, walk...\n' +
|
|
140
|
+
'- File organization: "Move .md files on desktop to docs folder"\n' +
|
|
141
|
+
'- Character change: "Change to blue cat"\n' +
|
|
142
|
+
'- /reset: Reset to default character\n' +
|
|
143
|
+
'- /status: Check pet status\n' +
|
|
144
|
+
'- /undo: Undo last file move'
|
|
145
145
|
);
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
@@ -158,22 +158,22 @@ class TelegramBot extends EventEmitter {
|
|
|
158
158
|
|
|
159
159
|
if (text === '/reset') {
|
|
160
160
|
this._sendToBridge('reset_character', {});
|
|
161
|
-
await this.bot.sendMessage(chatId, '
|
|
161
|
+
await this.bot.sendMessage(chatId, 'Reset to default character!');
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Parse and process message
|
|
166
166
|
const parsed = parseMessage(text);
|
|
167
167
|
await this._executeCommand(chatId, parsed);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
|
-
*
|
|
171
|
+
* Execute parsed command
|
|
172
172
|
*/
|
|
173
173
|
async _executeCommand(chatId, command) {
|
|
174
174
|
switch (command.type) {
|
|
175
175
|
case 'speak':
|
|
176
|
-
//
|
|
176
|
+
// Normal conversation -> display in pet speech bubble
|
|
177
177
|
this._sendToBridge('speak', { text: command.text, style: 'normal' });
|
|
178
178
|
this._sendToBridge('ai_decision', {
|
|
179
179
|
speech: command.text,
|
|
@@ -182,50 +182,50 @@ class TelegramBot extends EventEmitter {
|
|
|
182
182
|
break;
|
|
183
183
|
|
|
184
184
|
case 'action':
|
|
185
|
-
//
|
|
185
|
+
// Action command -> change pet behavior
|
|
186
186
|
this._sendToBridge('action', { state: command.action });
|
|
187
|
-
await this.bot.sendMessage(chatId,
|
|
187
|
+
await this.bot.sendMessage(chatId, `Pet is performing "${command.action}"!`);
|
|
188
188
|
break;
|
|
189
189
|
|
|
190
190
|
case 'smart_file_op':
|
|
191
|
-
//
|
|
191
|
+
// File operation command
|
|
192
192
|
await this._executeFileOp(chatId, command);
|
|
193
193
|
break;
|
|
194
194
|
|
|
195
195
|
case 'character_change':
|
|
196
|
-
//
|
|
196
|
+
// Character change command -> AI generation request
|
|
197
197
|
await this._handleCharacterChange(chatId, command.concept);
|
|
198
198
|
break;
|
|
199
199
|
|
|
200
200
|
case 'mode_change':
|
|
201
|
-
//
|
|
201
|
+
// Mode change command
|
|
202
202
|
this._sendToBridge('set_mode', { mode: command.mode });
|
|
203
|
-
const modeNames = { pet: 'Pet (Clawby)', incarnation: 'Incarnation (Claw)', both: '
|
|
204
|
-
await this.bot.sendMessage(chatId,
|
|
203
|
+
const modeNames = { pet: 'Pet (Clawby)', incarnation: 'Incarnation (Claw)', both: 'Both' };
|
|
204
|
+
await this.bot.sendMessage(chatId, `Mode changed: ${modeNames[command.mode] || command.mode}`);
|
|
205
205
|
break;
|
|
206
206
|
|
|
207
207
|
case 'preset_character': {
|
|
208
|
-
//
|
|
208
|
+
// Character preset selection
|
|
209
209
|
const presets = {
|
|
210
|
-
default: { name: '
|
|
211
|
-
blue: { name: '
|
|
212
|
-
green: { name: '
|
|
213
|
-
purple: { name: '
|
|
214
|
-
gold: { name: '
|
|
215
|
-
pink: { name: '
|
|
216
|
-
cat: { name: '
|
|
217
|
-
robot: { name: '
|
|
218
|
-
ghost: { name: '
|
|
219
|
-
dragon: { name: '
|
|
210
|
+
default: { name: 'Default Claw', colorMap: { primary: '#ff4f40', secondary: '#ff775f', dark: '#8B4513', eye: '#ffffff', pupil: '#111111', claw: '#ff4f40' } },
|
|
211
|
+
blue: { name: 'Blue Claw', colorMap: { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', eye: '#ffffff', pupil: '#111111', claw: '#4488ff' } },
|
|
212
|
+
green: { name: 'Green Claw', colorMap: { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', eye: '#ffffff', pupil: '#111111', claw: '#44cc44' } },
|
|
213
|
+
purple: { name: 'Purple Claw', colorMap: { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', eye: '#ffffff', pupil: '#111111', claw: '#8844cc' } },
|
|
214
|
+
gold: { name: 'Gold Claw', colorMap: { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', eye: '#ffffff', pupil: '#111111', claw: '#ffcc00' } },
|
|
215
|
+
pink: { name: 'Pink Claw', colorMap: { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', eye: '#ffffff', pupil: '#111111', claw: '#ff69b4' } },
|
|
216
|
+
cat: { name: 'Cat', colorMap: { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', eye: '#88ff88', pupil: '#111111', claw: '#ff9944' } },
|
|
217
|
+
robot: { name: 'Robot', colorMap: { primary: '#888888', secondary: '#aaaaaa', dark: '#444444', eye: '#66aaff', pupil: '#0044aa', claw: '#66aaff' } },
|
|
218
|
+
ghost: { name: 'Ghost', colorMap: { primary: '#ccccff', secondary: '#eeeeff', dark: '#6666aa', eye: '#ff6666', pupil: '#cc0000', claw: '#ccccff' } },
|
|
219
|
+
dragon: { name: 'Dragon', colorMap: { primary: '#cc2222', secondary: '#ff4444', dark: '#661111', eye: '#ffaa00', pupil: '#111111', claw: '#ffaa00' } },
|
|
220
220
|
};
|
|
221
221
|
const preset = presets[command.preset];
|
|
222
222
|
if (preset) {
|
|
223
223
|
if (command.preset === 'default') {
|
|
224
224
|
this._sendToBridge('reset_character', {});
|
|
225
225
|
} else {
|
|
226
|
-
this._sendToBridge('set_character', { colorMap: preset.colorMap, speech:
|
|
226
|
+
this._sendToBridge('set_character', { colorMap: preset.colorMap, speech: `Transforming into ${preset.name}!` });
|
|
227
227
|
}
|
|
228
|
-
await this.bot.sendMessage(chatId,
|
|
228
|
+
await this.bot.sendMessage(chatId, `Character changed: ${preset.name}`);
|
|
229
229
|
}
|
|
230
230
|
break;
|
|
231
231
|
}
|
|
@@ -233,24 +233,24 @@ class TelegramBot extends EventEmitter {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
*
|
|
236
|
+
* Handle character change request
|
|
237
237
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
238
|
+
* Passes concept text to AI to generate
|
|
239
|
+
* color + frame data and apply to pet.
|
|
240
240
|
*
|
|
241
|
-
*
|
|
241
|
+
* Falls back to extracting colors from concept if AI is unavailable.
|
|
242
242
|
*/
|
|
243
243
|
async _handleCharacterChange(chatId, concept) {
|
|
244
|
-
await this.bot.sendMessage(chatId, `"${concept}"
|
|
244
|
+
await this.bot.sendMessage(chatId, `Creating "${concept}" character...`);
|
|
245
245
|
|
|
246
|
-
//
|
|
246
|
+
// Request character generation from AI via AI Bridge
|
|
247
247
|
this._sendToBridge('ai_decision', {
|
|
248
|
-
speech:
|
|
248
|
+
speech: `Preparing to transform into ${concept}...`,
|
|
249
249
|
emotion: 'curious',
|
|
250
250
|
action: 'excited',
|
|
251
251
|
});
|
|
252
252
|
|
|
253
|
-
//
|
|
253
|
+
// Forward character change request via user_event (AI generates it)
|
|
254
254
|
if (this.bridge) {
|
|
255
255
|
this.bridge.send('user_event', {
|
|
256
256
|
event: 'character_request',
|
|
@@ -259,25 +259,25 @@ class TelegramBot extends EventEmitter {
|
|
|
259
259
|
});
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
//
|
|
262
|
+
// Fallback: keyword-based color conversion if no AI response within 3 seconds
|
|
263
263
|
this._characterFallbackTimer = setTimeout(() => {
|
|
264
264
|
const colorMap = this._extractColorsFromConcept(concept);
|
|
265
265
|
if (colorMap) {
|
|
266
266
|
this._sendToBridge('set_character', {
|
|
267
267
|
colorMap,
|
|
268
|
-
speech:
|
|
268
|
+
speech: `Transformed into ${concept}!`,
|
|
269
269
|
});
|
|
270
|
-
this.bot.sendMessage(chatId, `"${concept}"
|
|
270
|
+
this.bot.sendMessage(chatId, `Changed to "${concept}" character! (color-based)`);
|
|
271
271
|
}
|
|
272
272
|
}, 3000);
|
|
273
273
|
|
|
274
|
-
//
|
|
274
|
+
// Cancel this timer when AI generates the character
|
|
275
275
|
this._pendingCharacterChatId = chatId;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/**
|
|
279
|
-
*
|
|
280
|
-
*
|
|
279
|
+
* Called when AI completes character generation
|
|
280
|
+
* Cancels fallback timer and notifies Telegram
|
|
281
281
|
*/
|
|
282
282
|
onCharacterGenerated(concept) {
|
|
283
283
|
if (this._characterFallbackTimer) {
|
|
@@ -286,52 +286,52 @@ class TelegramBot extends EventEmitter {
|
|
|
286
286
|
}
|
|
287
287
|
if (this._pendingCharacterChatId) {
|
|
288
288
|
this.bot?.sendMessage(this._pendingCharacterChatId,
|
|
289
|
-
`"${concept}"
|
|
289
|
+
`"${concept}" character created! Custom character generated by AI!`);
|
|
290
290
|
this._pendingCharacterChatId = null;
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
295
|
+
* Extract colors from concept text (fallback when AI unavailable)
|
|
296
|
+
* Determines color palette via keyword matching
|
|
297
297
|
*/
|
|
298
298
|
_extractColorsFromConcept(concept) {
|
|
299
299
|
const c = concept.toLowerCase();
|
|
300
300
|
|
|
301
|
-
//
|
|
301
|
+
// Color keyword -> palette mapping
|
|
302
302
|
const colorKeywords = {
|
|
303
|
-
//
|
|
303
|
+
// Blue family
|
|
304
304
|
'파란': { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', claw: '#4488ff' },
|
|
305
305
|
'파랑': { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', claw: '#4488ff' },
|
|
306
306
|
'blue': { primary: '#4488ff', secondary: '#6699ff', dark: '#223388', claw: '#4488ff' },
|
|
307
|
-
//
|
|
307
|
+
// Green family
|
|
308
308
|
'초록': { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', claw: '#44cc44' },
|
|
309
309
|
'녹색': { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', claw: '#44cc44' },
|
|
310
310
|
'green': { primary: '#44cc44', secondary: '#66dd66', dark: '#226622', claw: '#44cc44' },
|
|
311
|
-
//
|
|
311
|
+
// Purple family
|
|
312
312
|
'보라': { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', claw: '#8844cc' },
|
|
313
313
|
'purple': { primary: '#8844cc', secondary: '#aa66dd', dark: '#442266', claw: '#8844cc' },
|
|
314
|
-
//
|
|
314
|
+
// Yellow family
|
|
315
315
|
'노란': { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', claw: '#ffcc00' },
|
|
316
316
|
'금색': { primary: '#ffd700', secondary: '#ffe44d', dark: '#8B7500', claw: '#ffd700' },
|
|
317
317
|
'yellow': { primary: '#ffcc00', secondary: '#ffdd44', dark: '#886600', claw: '#ffcc00' },
|
|
318
318
|
'gold': { primary: '#ffd700', secondary: '#ffe44d', dark: '#8B7500', claw: '#ffd700' },
|
|
319
|
-
//
|
|
319
|
+
// Pink family
|
|
320
320
|
'분홍': { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', claw: '#ff69b4' },
|
|
321
321
|
'핑크': { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', claw: '#ff69b4' },
|
|
322
322
|
'pink': { primary: '#ff69b4', secondary: '#ff8cc4', dark: '#8B3060', claw: '#ff69b4' },
|
|
323
|
-
//
|
|
323
|
+
// White family
|
|
324
324
|
'하얀': { primary: '#eeeeee', secondary: '#ffffff', dark: '#999999', claw: '#dddddd' },
|
|
325
325
|
'흰': { primary: '#eeeeee', secondary: '#ffffff', dark: '#999999', claw: '#dddddd' },
|
|
326
326
|
'white': { primary: '#eeeeee', secondary: '#ffffff', dark: '#999999', claw: '#dddddd' },
|
|
327
|
-
//
|
|
327
|
+
// Black family
|
|
328
328
|
'검정': { primary: '#333333', secondary: '#555555', dark: '#111111', claw: '#444444' },
|
|
329
329
|
'까만': { primary: '#333333', secondary: '#555555', dark: '#111111', claw: '#444444' },
|
|
330
330
|
'black': { primary: '#333333', secondary: '#555555', dark: '#111111', claw: '#444444' },
|
|
331
|
-
//
|
|
331
|
+
// Orange family
|
|
332
332
|
'주황': { primary: '#ff8800', secondary: '#ffaa33', dark: '#884400', claw: '#ff8800' },
|
|
333
333
|
'orange': { primary: '#ff8800', secondary: '#ffaa33', dark: '#884400', claw: '#ff8800' },
|
|
334
|
-
//
|
|
334
|
+
// Teal/Mint family
|
|
335
335
|
'민트': { primary: '#00BFA5', secondary: '#33D4BC', dark: '#006655', claw: '#00BFA5' },
|
|
336
336
|
'틸': { primary: '#00BFA5', secondary: '#33D4BC', dark: '#006655', claw: '#00BFA5' },
|
|
337
337
|
'teal': { primary: '#00BFA5', secondary: '#33D4BC', dark: '#006655', claw: '#00BFA5' },
|
|
@@ -347,7 +347,7 @@ class TelegramBot extends EventEmitter {
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
//
|
|
350
|
+
// Creature keywords -> characteristic colors
|
|
351
351
|
const creatureColors = {
|
|
352
352
|
'고양이': { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', claw: '#ff9944' },
|
|
353
353
|
'cat': { primary: '#ff9944', secondary: '#ffbb66', dark: '#663300', claw: '#ff9944' },
|
|
@@ -381,7 +381,7 @@ class TelegramBot extends EventEmitter {
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
//
|
|
384
|
+
// Random color if no match
|
|
385
385
|
const hue = Math.floor(Math.random() * 360);
|
|
386
386
|
return {
|
|
387
387
|
primary: `hsl(${hue}, 70%, 55%)`,
|
|
@@ -394,11 +394,11 @@ class TelegramBot extends EventEmitter {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
/**
|
|
397
|
-
*
|
|
397
|
+
* Execute smart file operation + pet animation + Telegram feedback
|
|
398
398
|
*/
|
|
399
399
|
async _executeFileOp(chatId, command) {
|
|
400
400
|
if (this._fileOpInProgress) {
|
|
401
|
-
await this.bot.sendMessage(chatId, '
|
|
401
|
+
await this.bot.sendMessage(chatId, 'A file operation is already in progress! Please wait.');
|
|
402
402
|
return;
|
|
403
403
|
}
|
|
404
404
|
|
|
@@ -406,16 +406,16 @@ class TelegramBot extends EventEmitter {
|
|
|
406
406
|
|
|
407
407
|
const callbacks = {
|
|
408
408
|
onStart: (totalFiles) => {
|
|
409
|
-
this.bot.sendMessage(chatId,
|
|
409
|
+
this.bot.sendMessage(chatId, `Found ${totalFiles} files! Starting to carry them~`);
|
|
410
410
|
this._sendToBridge('ai_decision', {
|
|
411
411
|
action: 'excited',
|
|
412
|
-
speech:
|
|
412
|
+
speech: `Starting to organize ${totalFiles} files!`,
|
|
413
413
|
emotion: 'happy',
|
|
414
414
|
});
|
|
415
415
|
},
|
|
416
416
|
|
|
417
417
|
onPickUp: (fileName, index) => {
|
|
418
|
-
//
|
|
418
|
+
// Pet picks up file animation
|
|
419
419
|
this._sendToBridge('smart_file_op', {
|
|
420
420
|
phase: 'pick_up',
|
|
421
421
|
fileName,
|
|
@@ -423,13 +423,13 @@ class TelegramBot extends EventEmitter {
|
|
|
423
423
|
});
|
|
424
424
|
this._sendToBridge('ai_decision', {
|
|
425
425
|
action: 'carrying',
|
|
426
|
-
speech:
|
|
426
|
+
speech: `Picked up ${fileName}!`,
|
|
427
427
|
emotion: 'focused',
|
|
428
428
|
});
|
|
429
429
|
},
|
|
430
430
|
|
|
431
431
|
onDrop: (fileName, targetName, index) => {
|
|
432
|
-
//
|
|
432
|
+
// Pet drops file animation
|
|
433
433
|
this._sendToBridge('smart_file_op', {
|
|
434
434
|
phase: 'drop',
|
|
435
435
|
fileName,
|
|
@@ -438,7 +438,7 @@ class TelegramBot extends EventEmitter {
|
|
|
438
438
|
});
|
|
439
439
|
this._sendToBridge('ai_decision', {
|
|
440
440
|
action: 'walking',
|
|
441
|
-
speech:
|
|
441
|
+
speech: `Placed ${fileName} in ${targetName}!`,
|
|
442
442
|
emotion: 'happy',
|
|
443
443
|
});
|
|
444
444
|
},
|
|
@@ -448,11 +448,11 @@ class TelegramBot extends EventEmitter {
|
|
|
448
448
|
|
|
449
449
|
let message;
|
|
450
450
|
if (result.movedCount === 0) {
|
|
451
|
-
message = '
|
|
451
|
+
message = 'No files to move!';
|
|
452
452
|
} else {
|
|
453
|
-
message =
|
|
453
|
+
message = `Moved ${result.movedCount} files!`;
|
|
454
454
|
if (result.errors.length > 0) {
|
|
455
|
-
message += `\n(${result.errors.length}
|
|
455
|
+
message += `\n(${result.errors.length} failed)`;
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
|
|
@@ -463,7 +463,7 @@ class TelegramBot extends EventEmitter {
|
|
|
463
463
|
emotion: 'proud',
|
|
464
464
|
});
|
|
465
465
|
|
|
466
|
-
// smart_file_op
|
|
466
|
+
// smart_file_op completion event
|
|
467
467
|
this._sendToBridge('smart_file_op', {
|
|
468
468
|
phase: 'complete',
|
|
469
469
|
movedCount: result.movedCount,
|
|
@@ -473,10 +473,10 @@ class TelegramBot extends EventEmitter {
|
|
|
473
473
|
|
|
474
474
|
onError: (error) => {
|
|
475
475
|
this._fileOpInProgress = false;
|
|
476
|
-
this.bot.sendMessage(chatId,
|
|
476
|
+
this.bot.sendMessage(chatId, `Error during file operation: ${error}`);
|
|
477
477
|
this._sendToBridge('ai_decision', {
|
|
478
478
|
action: 'scared',
|
|
479
|
-
speech: '
|
|
479
|
+
speech: 'Oops, something went wrong...',
|
|
480
480
|
emotion: 'scared',
|
|
481
481
|
});
|
|
482
482
|
},
|
|
@@ -486,28 +486,28 @@ class TelegramBot extends EventEmitter {
|
|
|
486
486
|
}
|
|
487
487
|
|
|
488
488
|
/**
|
|
489
|
-
*
|
|
489
|
+
* Query pet status and send to Telegram
|
|
490
490
|
*/
|
|
491
491
|
async _sendStatus(chatId) {
|
|
492
492
|
if (!this.bridge) {
|
|
493
|
-
await this.bot.sendMessage(chatId, 'AI Bridge
|
|
493
|
+
await this.bot.sendMessage(chatId, 'Not connected to AI Bridge.');
|
|
494
494
|
return;
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
const state = this.bridge.petState;
|
|
498
498
|
const statusText =
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
`AI
|
|
499
|
+
`State: ${state.state}\n` +
|
|
500
|
+
`Position: (${state.position.x}, ${state.position.y})\n` +
|
|
501
|
+
`Mode: ${state.mode}\n` +
|
|
502
|
+
`Emotion: ${state.emotion}\n` +
|
|
503
|
+
`Evolution: Stage ${state.evolutionStage}\n` +
|
|
504
|
+
`AI Connected: ${this.bridge.isConnected() ? 'Yes' : 'No'}`;
|
|
505
505
|
|
|
506
506
|
await this.bot.sendMessage(chatId, statusText);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
/**
|
|
510
|
-
*
|
|
510
|
+
* Undo last file move
|
|
511
511
|
*/
|
|
512
512
|
async _undoLastMove(chatId) {
|
|
513
513
|
try {
|
|
@@ -515,75 +515,75 @@ class TelegramBot extends EventEmitter {
|
|
|
515
515
|
const result = undoAllSmartMoves();
|
|
516
516
|
|
|
517
517
|
if (result.restoredCount === 0) {
|
|
518
|
-
await this.bot.sendMessage(chatId, '
|
|
518
|
+
await this.bot.sendMessage(chatId, 'No file moves to undo.');
|
|
519
519
|
} else {
|
|
520
|
-
let message =
|
|
520
|
+
let message = `Restored ${result.restoredCount} files to original locations!`;
|
|
521
521
|
if (result.errors.length > 0) {
|
|
522
|
-
message += `\n(${result.errors.length}
|
|
522
|
+
message += `\n(${result.errors.length} restore failures)`;
|
|
523
523
|
}
|
|
524
524
|
await this.bot.sendMessage(chatId, message);
|
|
525
525
|
this._sendToBridge('ai_decision', {
|
|
526
526
|
action: 'walking',
|
|
527
|
-
speech: '
|
|
527
|
+
speech: 'Restored files to their original locations!',
|
|
528
528
|
emotion: 'happy',
|
|
529
529
|
});
|
|
530
530
|
}
|
|
531
531
|
} catch (err) {
|
|
532
|
-
await this.bot.sendMessage(chatId,
|
|
532
|
+
await this.bot.sendMessage(chatId, `Undo failed: ${err.message}`);
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
/**
|
|
537
|
-
* AI Bridge
|
|
537
|
+
* Forward command to AI Bridge
|
|
538
538
|
*/
|
|
539
539
|
_sendToBridge(type, payload) {
|
|
540
540
|
if (!this.bridge) return;
|
|
541
541
|
|
|
542
|
-
// bridge
|
|
543
|
-
//
|
|
542
|
+
// Directly invoke bridge's _handleCommand (emit internal event)
|
|
543
|
+
// Mark as command from Telegram
|
|
544
544
|
payload._fromTelegram = true;
|
|
545
545
|
this.bridge.emit(type, payload);
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
/**
|
|
549
|
-
*
|
|
549
|
+
* Broadcast message to all active chats
|
|
550
550
|
*/
|
|
551
551
|
_broadcastToChats(text) {
|
|
552
552
|
if (!this.bot || !this.active) return;
|
|
553
553
|
|
|
554
554
|
for (const chatId of this.activeChatIds) {
|
|
555
555
|
this.bot.sendMessage(chatId, text).catch(() => {
|
|
556
|
-
//
|
|
556
|
+
// Remove chat ID on send failure
|
|
557
557
|
this.activeChatIds.delete(chatId);
|
|
558
558
|
});
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
|
|
562
562
|
/**
|
|
563
|
-
*
|
|
563
|
+
* Send message to specific chat
|
|
564
564
|
*/
|
|
565
565
|
async sendMessage(chatId, text) {
|
|
566
566
|
if (!this.bot || !this.active) return;
|
|
567
567
|
try {
|
|
568
568
|
await this.bot.sendMessage(chatId, text);
|
|
569
569
|
} catch (err) {
|
|
570
|
-
console.error('[Telegram]
|
|
570
|
+
console.error('[Telegram] Message send failed:', err.message);
|
|
571
571
|
}
|
|
572
572
|
}
|
|
573
573
|
|
|
574
574
|
/**
|
|
575
|
-
*
|
|
575
|
+
* Stop bot
|
|
576
576
|
*/
|
|
577
577
|
stop() {
|
|
578
578
|
if (this.bot && this.active) {
|
|
579
579
|
this.bot.stopPolling();
|
|
580
580
|
this.active = false;
|
|
581
|
-
console.log('[Telegram]
|
|
581
|
+
console.log('[Telegram] Bot stopped');
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
584
|
|
|
585
585
|
/**
|
|
586
|
-
*
|
|
586
|
+
* Check if bot is active
|
|
587
587
|
*/
|
|
588
588
|
isActive() {
|
|
589
589
|
return this.active;
|