samarthya-bot 1.0.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/README.md +92 -0
- package/backend/.env.example +23 -0
- package/backend/bin/samarthya.js +384 -0
- package/backend/config/constants.js +71 -0
- package/backend/config/db.js +13 -0
- package/backend/controllers/auditController.js +86 -0
- package/backend/controllers/authController.js +154 -0
- package/backend/controllers/chatController.js +158 -0
- package/backend/controllers/fileController.js +268 -0
- package/backend/controllers/platformController.js +54 -0
- package/backend/controllers/screenController.js +91 -0
- package/backend/controllers/telegramController.js +120 -0
- package/backend/controllers/toolsController.js +56 -0
- package/backend/controllers/whatsappController.js +214 -0
- package/backend/fix_toolRegistry.js +25 -0
- package/backend/middleware/auth.js +28 -0
- package/backend/models/AuditLog.js +28 -0
- package/backend/models/BackgroundJob.js +13 -0
- package/backend/models/Conversation.js +40 -0
- package/backend/models/Memory.js +17 -0
- package/backend/models/User.js +24 -0
- package/backend/package-lock.json +3766 -0
- package/backend/package.json +41 -0
- package/backend/public/assets/index-Ckf0GO1B.css +1 -0
- package/backend/public/assets/index-Do4jNsZS.js +19 -0
- package/backend/public/assets/index-Ui-pyZvK.js +25 -0
- package/backend/public/favicon.svg +17 -0
- package/backend/public/index.html +18 -0
- package/backend/public/manifest.json +16 -0
- package/backend/routes/audit.js +9 -0
- package/backend/routes/auth.js +11 -0
- package/backend/routes/chat.js +11 -0
- package/backend/routes/files.js +14 -0
- package/backend/routes/platform.js +18 -0
- package/backend/routes/screen.js +10 -0
- package/backend/routes/telegram.js +8 -0
- package/backend/routes/tools.js +9 -0
- package/backend/routes/whatsapp.js +11 -0
- package/backend/server.js +134 -0
- package/backend/services/background/backgroundService.js +81 -0
- package/backend/services/llm/llmService.js +444 -0
- package/backend/services/memory/memoryService.js +159 -0
- package/backend/services/planner/plannerService.js +182 -0
- package/backend/services/security/securityService.js +166 -0
- package/backend/services/telegram/telegramService.js +49 -0
- package/backend/services/tools/toolRegistry.js +879 -0
- package/backend/services/whatsapp/whatsappService.js +254 -0
- package/backend/test_email.js +29 -0
- package/backend/test_parser.js +10 -0
- package/package.json +49 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WhatsApp Business Cloud API Service
|
|
3
|
+
* Handles sending/receiving WhatsApp messages via Meta's API
|
|
4
|
+
*
|
|
5
|
+
* Setup: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started
|
|
6
|
+
* 1. Create Meta Business account
|
|
7
|
+
* 2. Set up WhatsApp Business App
|
|
8
|
+
* 3. Get API token and phone number ID
|
|
9
|
+
* 4. Set webhook URL to: https://yourdomain.com/api/whatsapp/webhook
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
class WhatsAppService {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.apiToken = process.env.WHATSAPP_API_TOKEN;
|
|
15
|
+
this.phoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID;
|
|
16
|
+
this.verifyToken = process.env.WHATSAPP_VERIFY_TOKEN || 'samarthya_whatsapp_verify_2025';
|
|
17
|
+
this.apiUrl = 'https://graph.facebook.com/v18.0';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Send a text message via WhatsApp
|
|
22
|
+
*/
|
|
23
|
+
async sendMessage(to, text) {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(
|
|
26
|
+
`${this.apiUrl}/${this.phoneNumberId}/messages`,
|
|
27
|
+
{
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
31
|
+
'Content-Type': 'application/json'
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
messaging_product: 'whatsapp',
|
|
35
|
+
to: to,
|
|
36
|
+
type: 'text',
|
|
37
|
+
text: { body: text }
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
const error = await response.text();
|
|
44
|
+
console.error('WhatsApp send error:', error);
|
|
45
|
+
return { success: false, error };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const data = await response.json();
|
|
49
|
+
return { success: true, messageId: data.messages?.[0]?.id };
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('WhatsApp Service Error:', error);
|
|
52
|
+
return { success: false, error: error.message };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Send interactive buttons
|
|
58
|
+
*/
|
|
59
|
+
async sendButtons(to, bodyText, buttons) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await fetch(
|
|
62
|
+
`${this.apiUrl}/${this.phoneNumberId}/messages`,
|
|
63
|
+
{
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
67
|
+
'Content-Type': 'application/json'
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
messaging_product: 'whatsapp',
|
|
71
|
+
to: to,
|
|
72
|
+
type: 'interactive',
|
|
73
|
+
interactive: {
|
|
74
|
+
type: 'button',
|
|
75
|
+
body: { text: bodyText },
|
|
76
|
+
action: {
|
|
77
|
+
buttons: buttons.map((btn, i) => ({
|
|
78
|
+
type: 'reply',
|
|
79
|
+
reply: {
|
|
80
|
+
id: `btn_${i}`,
|
|
81
|
+
title: btn.substring(0, 20) // WhatsApp 20 char limit
|
|
82
|
+
}
|
|
83
|
+
}))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
return { success: response.ok, data };
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('WhatsApp buttons error:', error);
|
|
94
|
+
return { success: false, error: error.message };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Send a template message (for notifications/reminders)
|
|
100
|
+
*/
|
|
101
|
+
async sendTemplate(to, templateName, language = 'en', parameters = []) {
|
|
102
|
+
try {
|
|
103
|
+
const components = parameters.length > 0 ? [{
|
|
104
|
+
type: 'body',
|
|
105
|
+
parameters: parameters.map(p => ({
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: String(p)
|
|
108
|
+
}))
|
|
109
|
+
}] : [];
|
|
110
|
+
|
|
111
|
+
const response = await fetch(
|
|
112
|
+
`${this.apiUrl}/${this.phoneNumberId}/messages`,
|
|
113
|
+
{
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
117
|
+
'Content-Type': 'application/json'
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify({
|
|
120
|
+
messaging_product: 'whatsapp',
|
|
121
|
+
to: to,
|
|
122
|
+
type: 'template',
|
|
123
|
+
template: {
|
|
124
|
+
name: templateName,
|
|
125
|
+
language: { code: language },
|
|
126
|
+
components
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
return { success: response.ok, data };
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error('WhatsApp template error:', error);
|
|
136
|
+
return { success: false, error: error.message };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Mark message as read
|
|
142
|
+
*/
|
|
143
|
+
async markAsRead(messageId) {
|
|
144
|
+
try {
|
|
145
|
+
await fetch(
|
|
146
|
+
`${this.apiUrl}/${this.phoneNumberId}/messages`,
|
|
147
|
+
{
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
'Authorization': `Bearer ${this.apiToken}`,
|
|
151
|
+
'Content-Type': 'application/json'
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
154
|
+
messaging_product: 'whatsapp',
|
|
155
|
+
status: 'read',
|
|
156
|
+
message_id: messageId
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error('Mark as read error:', error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Download media (images, voice notes) from WhatsApp
|
|
167
|
+
* Returns buffer for processing
|
|
168
|
+
*/
|
|
169
|
+
async downloadMedia(mediaId) {
|
|
170
|
+
try {
|
|
171
|
+
// Step 1: Get media URL
|
|
172
|
+
const urlRes = await fetch(
|
|
173
|
+
`${this.apiUrl}/${mediaId}`,
|
|
174
|
+
{
|
|
175
|
+
headers: { 'Authorization': `Bearer ${this.apiToken}` }
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
const urlData = await urlRes.json();
|
|
179
|
+
|
|
180
|
+
if (!urlData.url) return null;
|
|
181
|
+
|
|
182
|
+
// Step 2: Download actual media
|
|
183
|
+
const mediaRes = await fetch(urlData.url, {
|
|
184
|
+
headers: { 'Authorization': `Bearer ${this.apiToken}` }
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const buffer = await mediaRes.arrayBuffer();
|
|
188
|
+
return {
|
|
189
|
+
buffer: Buffer.from(buffer),
|
|
190
|
+
mimeType: urlData.mime_type,
|
|
191
|
+
size: urlData.file_size
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Media download error:', error);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Parse incoming webhook message
|
|
201
|
+
*/
|
|
202
|
+
parseWebhookMessage(body) {
|
|
203
|
+
try {
|
|
204
|
+
const entry = body.entry?.[0];
|
|
205
|
+
const changes = entry?.changes?.[0];
|
|
206
|
+
const value = changes?.value;
|
|
207
|
+
|
|
208
|
+
if (!value?.messages?.[0]) return null;
|
|
209
|
+
|
|
210
|
+
const msg = value.messages[0];
|
|
211
|
+
const contact = value.contacts?.[0];
|
|
212
|
+
|
|
213
|
+
const parsed = {
|
|
214
|
+
messageId: msg.id,
|
|
215
|
+
from: msg.from, // phone number
|
|
216
|
+
timestamp: new Date(parseInt(msg.timestamp) * 1000),
|
|
217
|
+
contactName: contact?.profile?.name || 'Unknown',
|
|
218
|
+
type: msg.type, // text, image, audio, document, interactive
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
switch (msg.type) {
|
|
222
|
+
case 'text':
|
|
223
|
+
parsed.text = msg.text.body;
|
|
224
|
+
break;
|
|
225
|
+
case 'image':
|
|
226
|
+
parsed.mediaId = msg.image.id;
|
|
227
|
+
parsed.mimeType = msg.image.mime_type;
|
|
228
|
+
parsed.caption = msg.image.caption;
|
|
229
|
+
break;
|
|
230
|
+
case 'audio':
|
|
231
|
+
parsed.mediaId = msg.audio.id;
|
|
232
|
+
parsed.mimeType = msg.audio.mime_type;
|
|
233
|
+
break;
|
|
234
|
+
case 'document':
|
|
235
|
+
parsed.mediaId = msg.document.id;
|
|
236
|
+
parsed.mimeType = msg.document.mime_type;
|
|
237
|
+
parsed.filename = msg.document.filename;
|
|
238
|
+
break;
|
|
239
|
+
case 'interactive':
|
|
240
|
+
parsed.text = msg.interactive?.button_reply?.title ||
|
|
241
|
+
msg.interactive?.list_reply?.title || '';
|
|
242
|
+
parsed.buttonId = msg.interactive?.button_reply?.id;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return parsed;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('Webhook parse error:', error);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = new WhatsAppService();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const nodemailer = require('nodemailer');
|
|
2
|
+
|
|
3
|
+
const transporter = nodemailer.createTransport({
|
|
4
|
+
service: 'gmail',
|
|
5
|
+
auth: {
|
|
6
|
+
user: 'mebishnusahu0595@gmail.com',
|
|
7
|
+
pass: 'srdhoxxykgitcrph'
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const mailOptions = {
|
|
12
|
+
from: 'mebishnusahu0595@gmail.com',
|
|
13
|
+
to: 'datascienceforus05@gmail.com',
|
|
14
|
+
subject: 'Direct SMTP Test',
|
|
15
|
+
text: 'If you see this, the terminal nodemailer is successful.'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function send() {
|
|
19
|
+
try {
|
|
20
|
+
console.log("Attempting to send...");
|
|
21
|
+
const info = await transporter.sendMail(mailOptions);
|
|
22
|
+
console.log("✅ Success! ID: " + info.messageId);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error("❌ Failed!");
|
|
25
|
+
console.error(err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
send();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const llmService = require('./services/llm/llmService.js');
|
|
2
|
+
const response = `Here is the data:
|
|
3
|
+
\`\`\`tool_call
|
|
4
|
+
[
|
|
5
|
+
{ "tool": "file_write", "args": { "filename": "test.txt", "content": "hello" } },
|
|
6
|
+
{ "tool": "send_email", "args": { "to": "a@a.com", "subject": "a", "body": "a" } }
|
|
7
|
+
]
|
|
8
|
+
\`\`\`
|
|
9
|
+
`;
|
|
10
|
+
console.log(llmService.parseToolCalls(response));
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "samarthya-bot",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Privacy-First Local Agentic OS & Command Center",
|
|
5
|
+
"main": "backend/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"samarthya": "backend/bin/samarthya.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node backend/server.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@google/genai": "^1.43.0",
|
|
14
|
+
"bcryptjs": "^3.0.3",
|
|
15
|
+
"cors": "^2.8.6",
|
|
16
|
+
"dotenv": "^17.3.1",
|
|
17
|
+
"express": "^5.2.1",
|
|
18
|
+
"helmet": "^8.1.0",
|
|
19
|
+
"jsonwebtoken": "^9.0.3",
|
|
20
|
+
"mongoose": "^9.2.3",
|
|
21
|
+
"morgan": "^1.10.1",
|
|
22
|
+
"node-cron": "^4.2.1",
|
|
23
|
+
"node-crypto": "^1.0.0",
|
|
24
|
+
"node-os-utils": "^2.0.1",
|
|
25
|
+
"nodemailer": "^8.0.1",
|
|
26
|
+
"os-utils": "^0.0.14",
|
|
27
|
+
"puppeteer-core": "^24.37.5",
|
|
28
|
+
"screenshot-desktop": "^1.15.3",
|
|
29
|
+
"socket.io": "^4.8.3",
|
|
30
|
+
"uuid": "^13.0.0"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"ai",
|
|
34
|
+
"agent",
|
|
35
|
+
"os",
|
|
36
|
+
"local",
|
|
37
|
+
"samarthya-bot",
|
|
38
|
+
"ollama"
|
|
39
|
+
],
|
|
40
|
+
"author": "Bishnu Sahu",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.0.0"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/mebishnusahu0595/SamarthyaBot.git"
|
|
48
|
+
}
|
|
49
|
+
}
|