natureco-cli 1.0.44 → 1.0.48
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/package.json +1 -1
- package/src/commands/dashboard.js +342 -14
- package/src/commands/gateway-server.js +34 -25
package/package.json
CHANGED
|
@@ -13,55 +13,383 @@ function getConfig() {
|
|
|
13
13
|
catch { return {}; }
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function getSkills() {
|
|
17
|
+
try {
|
|
18
|
+
const skillsUtil = require('../utils/skills');
|
|
19
|
+
return skillsUtil.getSkills().map(s => s.name || s.slug);
|
|
20
|
+
} catch { return []; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getMemory(botId) {
|
|
24
|
+
try {
|
|
25
|
+
const memFile = path.join(CONFIG_DIR, 'memory', `${botId}.json`);
|
|
26
|
+
return JSON.parse(fs.readFileSync(memFile, 'utf8'));
|
|
27
|
+
} catch { return {}; }
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
function openBrowser(url) {
|
|
17
31
|
const { exec } = require('child_process');
|
|
18
32
|
const p = process.platform;
|
|
19
|
-
exec(p === 'darwin' ?
|
|
33
|
+
exec(p === 'darwin' ? `open ${url}` : p === 'win32' ? `start ${url}` : `xdg-open ${url}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const HTML = `<!DOCTYPE html>
|
|
37
|
+
<html lang="tr">
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="UTF-8">
|
|
40
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
41
|
+
<title>NatureCo Dashboard</title>
|
|
42
|
+
<style>
|
|
43
|
+
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui,-apple-system,sans-serif}
|
|
44
|
+
html,body{height:100%;overflow:hidden}
|
|
45
|
+
body{
|
|
46
|
+
background:#0a1628;
|
|
47
|
+
background-image:
|
|
48
|
+
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(0,230,118,0.15) 0%, transparent 60%),
|
|
49
|
+
radial-gradient(ellipse 60% 40% at 80% 80%, rgba(0,188,212,0.08) 0%, transparent 50%),
|
|
50
|
+
radial-gradient(ellipse 40% 30% at 20% 60%, rgba(0,230,118,0.06) 0%, transparent 40%);
|
|
51
|
+
animation:bgShift 15s ease-in-out infinite alternate;
|
|
52
|
+
}
|
|
53
|
+
@keyframes bgShift{
|
|
54
|
+
0%{
|
|
55
|
+
background-image:
|
|
56
|
+
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(0,230,118,0.15) 0%, transparent 60%),
|
|
57
|
+
radial-gradient(ellipse 60% 40% at 80% 80%, rgba(0,188,212,0.08) 0%, transparent 50%),
|
|
58
|
+
radial-gradient(ellipse 40% 30% at 20% 60%, rgba(0,230,118,0.06) 0%, transparent 40%);
|
|
59
|
+
}
|
|
60
|
+
100%{
|
|
61
|
+
background-image:
|
|
62
|
+
radial-gradient(ellipse 80% 50% at 30% 120%, rgba(0,230,118,0.12) 0%, transparent 60%),
|
|
63
|
+
radial-gradient(ellipse 60% 40% at 90% 20%, rgba(0,188,212,0.1) 0%, transparent 50%),
|
|
64
|
+
radial-gradient(ellipse 40% 30% at 10% 80%, rgba(0,230,118,0.08) 0%, transparent 40%);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
.app{display:flex;height:100vh}
|
|
68
|
+
.sidebar{
|
|
69
|
+
width:240px;min-width:240px;
|
|
70
|
+
background:rgba(0,10,5,0.75);
|
|
71
|
+
backdrop-filter:blur(20px);
|
|
72
|
+
border-right:1px solid rgba(0,230,118,0.12);
|
|
73
|
+
display:flex;flex-direction:column;
|
|
74
|
+
overflow-y:auto;
|
|
75
|
+
}
|
|
76
|
+
.sidebar::-webkit-scrollbar{width:3px}
|
|
77
|
+
.sidebar::-webkit-scrollbar-thumb{background:rgba(0,230,118,0.25);border-radius:2px}
|
|
78
|
+
.logo-area{padding:18px 16px;border-bottom:1px solid rgba(0,230,118,0.08);flex-shrink:0}
|
|
79
|
+
.logo{color:#00E676;font-size:17px;font-weight:700;letter-spacing:-0.4px}
|
|
80
|
+
.logo-sub{color:rgba(0,230,118,0.35);font-size:10px;margin-top:2px}
|
|
81
|
+
.section{padding:12px 14px}
|
|
82
|
+
.section-label{color:rgba(0,230,118,0.35);font-size:9px;font-weight:600;letter-spacing:1.8px;text-transform:uppercase;margin-bottom:8px}
|
|
83
|
+
.bot-card{background:rgba(0,230,118,0.06);border:1px solid rgba(0,230,118,0.2);border-radius:8px;padding:10px;display:flex;align-items:center;gap:8px;cursor:pointer;transition:all 0.2s}
|
|
84
|
+
.bot-card:hover{background:rgba(0,230,118,0.1);border-color:rgba(0,230,118,0.35)}
|
|
85
|
+
.avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#00C853,#00E676);display:flex;align-items:center;justify-content:center;color:#000;font-weight:700;font-size:13px;flex-shrink:0}
|
|
86
|
+
.bot-info .name{color:#e8f5e9;font-size:13px;font-weight:500}
|
|
87
|
+
.bot-info .model{color:rgba(0,230,118,0.45);font-size:10px;margin-top:1px}
|
|
88
|
+
.online-dot{width:7px;height:7px;background:#00E676;border-radius:50%;margin-left:auto;flex-shrink:0;box-shadow:0 0 6px rgba(0,230,118,0.8);animation:pulse 2s infinite}
|
|
89
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
|
90
|
+
.other-bot{padding:5px 0;color:rgba(0,230,118,0.4);font-size:12px;cursor:pointer;transition:color 0.2s;display:flex;align-items:center;gap:6px}
|
|
91
|
+
.other-bot:hover{color:#a5d6a7}
|
|
92
|
+
.other-avatar{width:22px;height:22px;border-radius:50%;background:rgba(0,230,118,0.1);border:1px solid rgba(0,230,118,0.15);display:flex;align-items:center;justify-content:center;color:rgba(0,230,118,0.5);font-size:9px;font-weight:700}
|
|
93
|
+
.ch-row{display:flex;align-items:center;gap:7px;padding:4px 0}
|
|
94
|
+
.ch-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
|
|
95
|
+
.ch-dot.on{background:#00E676;box-shadow:0 0 5px rgba(0,230,118,0.7)}
|
|
96
|
+
.ch-dot.off{background:rgba(255,255,255,0.15)}
|
|
97
|
+
.ch-name{font-size:12px}
|
|
98
|
+
.ch-name.on{color:#a5d6a7}
|
|
99
|
+
.ch-name.off{color:rgba(255,255,255,0.25)}
|
|
100
|
+
.skill-tag{display:inline-block;background:rgba(0,230,118,0.06);border:1px solid rgba(0,230,118,0.15);border-radius:4px;padding:3px 8px;color:#81c784;font-size:10px;margin:2px 2px 2px 0}
|
|
101
|
+
.mem-row{font-size:11px;padding:2px 0}
|
|
102
|
+
.mem-key{color:rgba(0,230,118,0.4);font-size:9px;letter-spacing:0.5px;text-transform:uppercase;margin-top:5px}
|
|
103
|
+
.mem-val{color:#a5d6a7}
|
|
104
|
+
.sidebar-footer{margin-top:auto;padding:12px 14px;border-top:1px solid rgba(0,230,118,0.06);flex-shrink:0}
|
|
105
|
+
.footer-link{color:rgba(0,230,118,0.25);font-size:10px;text-decoration:none}
|
|
106
|
+
.footer-link:hover{color:rgba(0,230,118,0.5)}
|
|
107
|
+
.chat-area{flex:1;display:flex;flex-direction:column;min-width:0}
|
|
108
|
+
.chat-header{
|
|
109
|
+
padding:14px 20px;
|
|
110
|
+
border-bottom:1px solid rgba(0,230,118,0.08);
|
|
111
|
+
display:flex;align-items:center;gap:12px;
|
|
112
|
+
background:rgba(0,10,5,0.6);
|
|
113
|
+
backdrop-filter:blur(10px);
|
|
114
|
+
flex-shrink:0;
|
|
115
|
+
}
|
|
116
|
+
.header-bot-name{color:#e8f5e9;font-size:14px;font-weight:500}
|
|
117
|
+
.header-bot-model{color:rgba(0,230,118,0.4);font-size:11px}
|
|
118
|
+
.version-badge{margin-left:auto;background:rgba(0,230,118,0.06);border:1px solid rgba(0,230,118,0.15);border-radius:20px;padding:3px 10px;color:rgba(0,230,118,0.5);font-size:10px}
|
|
119
|
+
.messages{flex:1;padding:16px 20px;overflow-y:auto;display:flex;flex-direction:column;gap:12px}
|
|
120
|
+
.messages::-webkit-scrollbar{width:3px}
|
|
121
|
+
.messages::-webkit-scrollbar-thumb{background:rgba(0,230,118,0.2);border-radius:2px}
|
|
122
|
+
.msg{display:flex;gap:8px;align-items:flex-end;animation:fadeIn 0.3s ease}
|
|
123
|
+
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
124
|
+
.msg.user{flex-direction:row-reverse}
|
|
125
|
+
.msg-avatar{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:11px;flex-shrink:0}
|
|
126
|
+
.msg-avatar.bot{background:linear-gradient(135deg,#00C853,#00E676);color:#000}
|
|
127
|
+
.msg-avatar.user{background:rgba(0,230,118,0.15);border:1px solid rgba(0,230,118,0.3);color:#00E676}
|
|
128
|
+
.bubble{padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.6;max-width:72%}
|
|
129
|
+
.bubble.user{background:linear-gradient(135deg,#00C853,#00E676);color:#000;font-weight:500;border-radius:12px 12px 0 12px}
|
|
130
|
+
.bubble.bot{background:rgba(255,255,255,0.04);border:1px solid rgba(0,230,118,0.1);color:#c8e6c9;border-radius:12px 12px 12px 0}
|
|
131
|
+
.typing{display:flex;gap:4px;padding:10px 14px;background:rgba(255,255,255,0.04);border:1px solid rgba(0,230,118,0.1);border-radius:12px 12px 12px 0;width:fit-content}
|
|
132
|
+
.typing span{width:6px;height:6px;background:rgba(0,230,118,0.5);border-radius:50%;animation:typing 1.2s infinite}
|
|
133
|
+
.typing span:nth-child(2){animation-delay:0.2s}
|
|
134
|
+
.typing span:nth-child(3){animation-delay:0.4s}
|
|
135
|
+
@keyframes typing{0%,60%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}
|
|
136
|
+
.input-area{
|
|
137
|
+
padding:14px 20px;
|
|
138
|
+
border-top:1px solid rgba(0,230,118,0.08);
|
|
139
|
+
display:flex;gap:10px;
|
|
140
|
+
background:rgba(0,10,5,0.6);
|
|
141
|
+
backdrop-filter:blur(10px);
|
|
142
|
+
flex-shrink:0;
|
|
143
|
+
}
|
|
144
|
+
.input-box{
|
|
145
|
+
flex:1;
|
|
146
|
+
background:rgba(255,255,255,0.04);
|
|
147
|
+
border:1px solid rgba(0,230,118,0.15);
|
|
148
|
+
border-radius:8px;
|
|
149
|
+
padding:10px 14px;
|
|
150
|
+
color:#e8f5e9;
|
|
151
|
+
font-size:13px;
|
|
152
|
+
outline:none;
|
|
153
|
+
transition:border-color 0.2s;
|
|
154
|
+
}
|
|
155
|
+
.input-box:focus{border-color:rgba(0,230,118,0.4)}
|
|
156
|
+
.input-box::placeholder{color:rgba(0,230,118,0.25)}
|
|
157
|
+
.send-btn{
|
|
158
|
+
background:linear-gradient(135deg,#00C853,#00E676);
|
|
159
|
+
border:none;border-radius:8px;
|
|
160
|
+
padding:10px 18px;
|
|
161
|
+
color:#000;font-weight:600;font-size:13px;
|
|
162
|
+
cursor:pointer;transition:opacity 0.2s,transform 0.1s;
|
|
163
|
+
white-space:nowrap;
|
|
164
|
+
}
|
|
165
|
+
.send-btn:hover{opacity:0.9}
|
|
166
|
+
.send-btn:active{transform:scale(0.97)}
|
|
167
|
+
</style>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
<div class="app">
|
|
171
|
+
<div class="sidebar" id="sidebar">
|
|
172
|
+
<div class="logo-area">
|
|
173
|
+
<div class="logo">NatureCo</div>
|
|
174
|
+
<div class="logo-sub" id="ver-label">Terminal</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div class="section" id="active-bot-section">
|
|
177
|
+
<div class="section-label">Aktif Bot</div>
|
|
178
|
+
<div class="bot-card">
|
|
179
|
+
<div class="avatar">N</div>
|
|
180
|
+
<div class="bot-info">
|
|
181
|
+
<div class="name" id="active-bot-name">Yükleniyor...</div>
|
|
182
|
+
<div class="model" id="active-bot-model">NatureCo</div>
|
|
183
|
+
</div>
|
|
184
|
+
<div class="online-dot"></div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="section" id="other-bots-section" style="padding-top:0">
|
|
188
|
+
<div class="section-label">Diğer Botlar</div>
|
|
189
|
+
<div id="other-bots-list"></div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="section" style="padding-top:0">
|
|
192
|
+
<div class="section-label">Kanallar</div>
|
|
193
|
+
<div id="channels-list"></div>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="section" style="padding-top:0">
|
|
196
|
+
<div class="section-label">Skill'ler</div>
|
|
197
|
+
<div id="skills-list"></div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="section" style="padding-top:0">
|
|
200
|
+
<div class="section-label">Hafıza</div>
|
|
201
|
+
<div id="memory-section"></div>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="sidebar-footer">
|
|
204
|
+
<a href="https://natureco.me" class="footer-link">natureco.me</a>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
<div class="chat-area">
|
|
208
|
+
<div class="chat-header">
|
|
209
|
+
<div class="avatar" style="width:36px;height:36px;font-size:14px">N</div>
|
|
210
|
+
<div>
|
|
211
|
+
<div class="header-bot-name" id="header-bot-name">Nature Bot</div>
|
|
212
|
+
<div class="header-bot-model" id="header-bot-model">NatureCo</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="version-badge" id="version-badge">v1.0.48</div>
|
|
215
|
+
</div>
|
|
216
|
+
<div class="messages" id="messages"></div>
|
|
217
|
+
<div class="input-area">
|
|
218
|
+
<input class="input-box" id="input" placeholder="Mesaj yaz..." />
|
|
219
|
+
<button class="send-btn" onclick="send()">Gönder</button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
<script>
|
|
224
|
+
let cfg = {}, activeBotId = null;
|
|
225
|
+
|
|
226
|
+
async function init() {
|
|
227
|
+
try {
|
|
228
|
+
const r = await fetch('/config');
|
|
229
|
+
cfg = await r.json();
|
|
230
|
+
activeBotId = cfg.defaultBotId;
|
|
231
|
+
document.getElementById('active-bot-name').textContent = cfg.defaultBot || 'Bot';
|
|
232
|
+
document.getElementById('header-bot-name').textContent = cfg.defaultBot || 'Bot';
|
|
233
|
+
document.getElementById('ver-label').textContent = 'Terminal ' + (cfg.version || '');
|
|
234
|
+
|
|
235
|
+
const others = (cfg.bots || []).filter(b => b.id !== activeBotId);
|
|
236
|
+
const ol = document.getElementById('other-bots-list');
|
|
237
|
+
ol.innerHTML = others.map(b => \`<div class="other-bot" onclick="switchBot('\${b.id}','\${b.name}')"><div class="other-avatar">\${b.name[0]}</div>\${b.name}</div>\`).join('');
|
|
238
|
+
|
|
239
|
+
const channels = [
|
|
240
|
+
{ name: 'Telegram', key: 'telegramToken' },
|
|
241
|
+
{ name: 'WhatsApp', key: 'whatsappConnected' },
|
|
242
|
+
{ name: 'Discord', key: 'discordToken' },
|
|
243
|
+
{ name: 'Gateway', key: 'gatewayRunning' },
|
|
244
|
+
];
|
|
245
|
+
document.getElementById('channels-list').innerHTML = channels.map(c => {
|
|
246
|
+
const on = !!(cfg[c.key]);
|
|
247
|
+
return \`<div class="ch-row"><div class="ch-dot \${on?'on':'off'}"></div><div class="ch-name \${on?'on':'off'}">\${c.name}</div></div>\`;
|
|
248
|
+
}).join('');
|
|
249
|
+
} catch(e) { console.error(e); }
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const sr = await fetch('/skills');
|
|
253
|
+
const skills = await sr.json();
|
|
254
|
+
document.getElementById('skills-list').innerHTML = skills.map(s => \`<span class="skill-tag">\${s}</span>\`).join('');
|
|
255
|
+
} catch {}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const mr = await fetch('/memory');
|
|
259
|
+
const mem = await mr.json();
|
|
260
|
+
let html = '';
|
|
261
|
+
if (mem.name) html += \`<div class="mem-key">İsim</div><div class="mem-row mem-val">\${mem.name}</div>\`;
|
|
262
|
+
if (mem.facts && mem.facts.length) html += \`<div class="mem-key">Bilgiler</div><div class="mem-row mem-val">\${mem.facts.map(f=>typeof f==='object'?f.value:f).join(' · ')}</div>\`;
|
|
263
|
+
document.getElementById('memory-section').innerHTML = html || \`<div class="mem-row" style="color:rgba(0,230,118,0.25)">Hafıza boş</div>\`;
|
|
264
|
+
} catch {}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function switchBot(id, name) {
|
|
268
|
+
activeBotId = id;
|
|
269
|
+
document.getElementById('active-bot-name').textContent = name;
|
|
270
|
+
document.getElementById('header-bot-name').textContent = name;
|
|
271
|
+
document.getElementById('messages').innerHTML = '';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function addMsg(type, text) {
|
|
275
|
+
const m = document.getElementById('messages');
|
|
276
|
+
const initials = type === 'user' ? 'G' : 'N';
|
|
277
|
+
m.innerHTML += \`<div class="msg \${type}"><div class="msg-avatar \${type}">\${initials}</div><div class="bubble \${type}">\${text}</div></div>\`;
|
|
278
|
+
m.scrollTop = m.scrollHeight;
|
|
20
279
|
}
|
|
21
280
|
|
|
22
|
-
|
|
281
|
+
function showTyping() {
|
|
282
|
+
const m = document.getElementById('messages');
|
|
283
|
+
m.innerHTML += \`<div class="msg" id="typing-row"><div class="msg-avatar bot">N</div><div class="typing"><span></span><span></span><span></span></div></div>\`;
|
|
284
|
+
m.scrollTop = m.scrollHeight;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hideTyping() {
|
|
288
|
+
const t = document.getElementById('typing-row');
|
|
289
|
+
if (t) t.remove();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function send() {
|
|
293
|
+
const inp = document.getElementById('input');
|
|
294
|
+
const msg = inp.value.trim();
|
|
295
|
+
if (!msg || !cfg.apiKey) return;
|
|
296
|
+
inp.value = '';
|
|
297
|
+
addMsg('user', msg);
|
|
298
|
+
showTyping();
|
|
299
|
+
try {
|
|
300
|
+
const r = await fetch('https://api.natureco.me/api/agent/chat', {
|
|
301
|
+
method: 'POST',
|
|
302
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + cfg.apiKey, 'X-User-ID': 'dashboard-user' },
|
|
303
|
+
body: JSON.stringify({ agent_id: activeBotId, message: msg, platform: 'dashboard', user_id: 'dashboard-user' })
|
|
304
|
+
});
|
|
305
|
+
const d = await r.json();
|
|
306
|
+
hideTyping();
|
|
307
|
+
addMsg('bot', d.reply || d.message || 'Hata');
|
|
308
|
+
} catch { hideTyping(); addMsg('bot', 'Bağlantı hatası'); }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
document.getElementById('input').addEventListener('keydown', e => { if (e.key === 'Enter') send(); });
|
|
312
|
+
init();
|
|
313
|
+
</script>
|
|
314
|
+
</body>
|
|
315
|
+
</html>`;
|
|
23
316
|
|
|
24
317
|
function dashboard(action) {
|
|
25
318
|
action = action || 'start';
|
|
26
319
|
if (action === 'start') {
|
|
27
320
|
const cfg = getConfig();
|
|
28
|
-
const
|
|
321
|
+
const skills = getSkills();
|
|
322
|
+
const fs2 = require('fs');
|
|
323
|
+
|
|
324
|
+
let gatewayRunning = false;
|
|
325
|
+
try {
|
|
326
|
+
const pid = parseInt(fs2.readFileSync(path.join(CONFIG_DIR, 'gateway.pid'), 'utf8'));
|
|
327
|
+
process.kill(pid, 0);
|
|
328
|
+
gatewayRunning = true;
|
|
329
|
+
} catch {}
|
|
330
|
+
|
|
331
|
+
const bots = [];
|
|
332
|
+
|
|
333
|
+
const server = http.createServer(async (req, res) => {
|
|
334
|
+
const cors = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,DELETE', 'Access-Control-Allow-Headers': 'Content-Type' };
|
|
335
|
+
|
|
336
|
+
if (req.method === 'OPTIONS') { res.writeHead(204, cors); res.end(); return; }
|
|
337
|
+
|
|
29
338
|
if (req.url === '/config') {
|
|
30
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
31
|
-
res.end(JSON.stringify({
|
|
339
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
340
|
+
res.end(JSON.stringify({
|
|
341
|
+
apiKey: cfg.apiKey,
|
|
342
|
+
defaultBot: cfg.defaultBot,
|
|
343
|
+
defaultBotId: cfg.defaultBotId,
|
|
344
|
+
version: 'v1.0.48',
|
|
345
|
+
bots: cfg.bots || [],
|
|
346
|
+
telegramToken: cfg.telegramToken || null,
|
|
347
|
+
whatsappConnected: cfg.whatsappConnected || false,
|
|
348
|
+
discordToken: cfg.discordToken || null,
|
|
349
|
+
gatewayRunning,
|
|
350
|
+
}));
|
|
351
|
+
} else if (req.url === '/skills') {
|
|
352
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
353
|
+
res.end(JSON.stringify(skills));
|
|
354
|
+
} else if (req.url === '/memory') {
|
|
355
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
356
|
+
res.end(JSON.stringify(getMemory(cfg.defaultBotId || '')));
|
|
32
357
|
} else {
|
|
33
358
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
34
359
|
res.end(HTML);
|
|
35
360
|
}
|
|
36
361
|
});
|
|
362
|
+
|
|
37
363
|
server.listen(PORT, () => {
|
|
38
364
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
39
365
|
fs.writeFileSync(PID_FILE, String(process.pid));
|
|
40
|
-
console.log(chalk.green('Dashboard
|
|
41
|
-
console.log(chalk.cyan('URL: ') +
|
|
42
|
-
|
|
43
|
-
|
|
366
|
+
console.log(chalk.green('\n✅ Dashboard başlatıldı!'));
|
|
367
|
+
console.log(chalk.cyan('URL: ') + `http://localhost:${PORT}`);
|
|
368
|
+
console.log(chalk.gray('Durdurmak için: Ctrl+C\n'));
|
|
369
|
+
openBrowser(`http://localhost:${PORT}`);
|
|
44
370
|
});
|
|
371
|
+
|
|
45
372
|
process.on('SIGINT', () => {
|
|
46
373
|
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
47
374
|
server.close();
|
|
48
|
-
console.log(chalk.green('Dashboard stopped'));
|
|
49
375
|
process.exit(0);
|
|
50
376
|
});
|
|
377
|
+
|
|
51
378
|
} else if (action === 'stop') {
|
|
52
379
|
try {
|
|
53
380
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
|
|
54
381
|
process.kill(pid);
|
|
55
382
|
fs.unlinkSync(PID_FILE);
|
|
56
|
-
console.log(chalk.green('Dashboard
|
|
57
|
-
} catch { console.log(chalk.red('Dashboard
|
|
383
|
+
console.log(chalk.green('✅ Dashboard durduruldu'));
|
|
384
|
+
} catch { console.log(chalk.red('❌ Dashboard çalışmıyor')); }
|
|
58
385
|
} else if (action === 'status') {
|
|
59
386
|
try {
|
|
60
387
|
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
|
|
61
|
-
|
|
388
|
+
process.kill(pid, 0);
|
|
389
|
+
console.log(chalk.green('✅ Dashboard çalışıyor'));
|
|
62
390
|
console.log('PID:', pid);
|
|
63
391
|
console.log('URL: http://localhost:' + PORT);
|
|
64
|
-
} catch { console.log(chalk.gray('Dashboard
|
|
392
|
+
} catch { console.log(chalk.gray('⚪ Dashboard çalışmıyor')); }
|
|
65
393
|
}
|
|
66
394
|
}
|
|
67
395
|
|
|
@@ -194,6 +194,9 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
194
194
|
const { state, saveCreds } = await useMultiFileAuthState(sessionDir);
|
|
195
195
|
const { version } = await fetchLatestBaileysVersion();
|
|
196
196
|
|
|
197
|
+
// Track last bot reply to prevent infinite loop
|
|
198
|
+
let lastBotReply = '';
|
|
199
|
+
|
|
197
200
|
const sock = makeWASocket({
|
|
198
201
|
version,
|
|
199
202
|
auth: state,
|
|
@@ -240,43 +243,47 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
240
243
|
const remoteJid = msg.key.remoteJid || '';
|
|
241
244
|
const isLID = remoteJid.includes('@lid');
|
|
242
245
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
246
|
+
// Skip bot's own replies (fromMe=true and not LID, or matches last bot reply)
|
|
247
|
+
const messageText = msg.message?.conversation ||
|
|
248
|
+
msg.message?.extendedTextMessage?.text ||
|
|
249
|
+
msg.message?.imageMessage?.caption ||
|
|
250
|
+
msg.message?.videoMessage?.caption ||
|
|
251
|
+
msg.message?.buttonsResponseMessage?.selectedDisplayText ||
|
|
252
|
+
msg.message?.listResponseMessage?.title ||
|
|
253
|
+
msg.message?.ephemeralMessage?.message?.conversation ||
|
|
254
|
+
msg.message?.viewOnceMessage?.message?.conversation ||
|
|
255
|
+
'';
|
|
256
|
+
|
|
257
|
+
// Skip if this is the bot's own reply
|
|
258
|
+
if (msg.key.fromMe && !isLID) {
|
|
259
|
+
log('whatsapp', 'Skipping bot own reply (fromMe=true, not LID)', 'gray');
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Skip if message matches last bot reply (prevent loop)
|
|
264
|
+
if (messageText === lastBotReply && lastBotReply !== '') {
|
|
265
|
+
log('whatsapp', 'Skipping duplicate bot reply (matches last sent)', 'gray');
|
|
266
|
+
continue;
|
|
251
267
|
}
|
|
252
268
|
|
|
253
|
-
//
|
|
254
|
-
if (msg.key.fromMe
|
|
255
|
-
log('whatsapp', '
|
|
269
|
+
// Handle LID format (new WhatsApp format)
|
|
270
|
+
if (isLID && !msg.key.fromMe) {
|
|
271
|
+
log('whatsapp', 'LID format detected, fromMe=false, blocked', 'yellow');
|
|
272
|
+
continue;
|
|
256
273
|
}
|
|
257
274
|
|
|
258
275
|
const sender = remoteJid.split('@')[0].split(':')[0];
|
|
259
276
|
const allowedNumbers = config.whatsappAllowedNumbers || [];
|
|
260
277
|
|
|
261
|
-
// Log incoming number
|
|
278
|
+
// Log incoming number
|
|
262
279
|
log('whatsapp', `Incoming from: +${sender}, allowed: ${JSON.stringify(allowedNumbers)}`, 'gray');
|
|
263
280
|
|
|
264
|
-
// Access control -
|
|
281
|
+
// Access control - skip if fromMe + LID (own conversation)
|
|
265
282
|
if (!(msg.key.fromMe && isLID) && allowedNumbers.length > 0 && !allowedNumbers.some(n => numberMatches(n, sender))) {
|
|
266
283
|
log('whatsapp', `blocked message from +${sender} (not in allowed list)`, 'yellow');
|
|
267
284
|
continue;
|
|
268
285
|
}
|
|
269
286
|
|
|
270
|
-
const messageText = msg.message?.conversation ||
|
|
271
|
-
msg.message?.extendedTextMessage?.text ||
|
|
272
|
-
msg.message?.imageMessage?.caption ||
|
|
273
|
-
msg.message?.videoMessage?.caption ||
|
|
274
|
-
msg.message?.buttonsResponseMessage?.selectedDisplayText ||
|
|
275
|
-
msg.message?.listResponseMessage?.title ||
|
|
276
|
-
msg.message?.ephemeralMessage?.message?.conversation ||
|
|
277
|
-
msg.message?.viewOnceMessage?.message?.conversation ||
|
|
278
|
-
'';
|
|
279
|
-
|
|
280
287
|
if (!messageText) {
|
|
281
288
|
log('whatsapp', `Message without text content. Keys: ${Object.keys(msg.message || {}).join(', ')}`, 'gray');
|
|
282
289
|
continue;
|
|
@@ -284,7 +291,6 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
284
291
|
|
|
285
292
|
const ownNumber = sock.user?.id?.split(':')[0].replace('@s.whatsapp.net', '') || 'unknown';
|
|
286
293
|
log('whatsapp', `Inbound message +${sender} -> +${ownNumber} (${messageText.length} chars)`, 'cyan');
|
|
287
|
-
log('whatsapp', `Message: "${messageText.substring(0, 100)}${messageText.length > 100 ? '...' : ''}"`, 'gray');
|
|
288
294
|
|
|
289
295
|
try {
|
|
290
296
|
const { sendMessage } = require('../utils/api');
|
|
@@ -294,10 +300,13 @@ async function startWhatsAppProvider(sessionDir, config) {
|
|
|
294
300
|
const reply = response?.reply || response?.message || '';
|
|
295
301
|
|
|
296
302
|
if (reply) {
|
|
297
|
-
log('whatsapp', `API response: "${reply.substring(0, 100)}${reply.length > 100 ? '...' : ''}"`, 'gray');
|
|
298
303
|
log('whatsapp', 'Sending reply...', 'cyan');
|
|
299
304
|
|
|
300
305
|
await sock.sendMessage(msg.key.remoteJid, { text: reply });
|
|
306
|
+
|
|
307
|
+
// Store last bot reply to prevent loop
|
|
308
|
+
lastBotReply = reply;
|
|
309
|
+
|
|
301
310
|
log('whatsapp', `Reply sent (${reply.length} chars)`, 'green');
|
|
302
311
|
} else {
|
|
303
312
|
log('whatsapp', 'No reply from API', 'yellow');
|