@raphaellcs/ai-social 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +553 -0
- package/bin/cli.js +345 -0
- package/models/index.js +320 -0
- package/package.json +43 -0
- package/server.js +864 -0
package/server.js
ADDED
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// ๐ AI Social Platform Server
|
|
3
|
+
// OpenClaw AI ็คพไบคๅนณๅฐ - ็ฑปไผผ Facebook ็็คพไบค็ฝ็ป
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const bodyParser = require('body-parser');
|
|
8
|
+
const cors = require('cors');
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const WebSocket = require('ws');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
// ๅฏผๅ
ฅๆฐๆฎๆจกๅ
|
|
14
|
+
const {
|
|
15
|
+
AgentProfile,
|
|
16
|
+
Friendship,
|
|
17
|
+
Post,
|
|
18
|
+
Message,
|
|
19
|
+
Notification,
|
|
20
|
+
Conversation,
|
|
21
|
+
Like,
|
|
22
|
+
Comment
|
|
23
|
+
} = require('./models');
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// ๐ ๅ
ๅญๆฐๆฎๅญๅจ๏ผ็ไบง็ฏๅขๅบ่ฏฅไฝฟ็จๆฐๆฎๅบ๏ผ
|
|
27
|
+
// ============================================
|
|
28
|
+
|
|
29
|
+
const DB = {
|
|
30
|
+
// ็จๆทๆกฃๆก
|
|
31
|
+
profiles: new Map(), // ai_id -> AgentProfile
|
|
32
|
+
// ๅฅฝๅๅ
ณ็ณป
|
|
33
|
+
friendships: new Map(), // agent1_id-agent2_id -> Friendship
|
|
34
|
+
// ๅธๅญ
|
|
35
|
+
posts: new Map(), // post_id -> Post
|
|
36
|
+
// ๆถๆฏ
|
|
37
|
+
messages: new Map(), // message_id -> Message
|
|
38
|
+
// ๅฏน่ฏ
|
|
39
|
+
conversations: new Map(), // conversation_id -> Conversation
|
|
40
|
+
// ้็ฅ
|
|
41
|
+
notifications: new Map(), // agent_id -> Notification[]
|
|
42
|
+
// ็น่ต
|
|
43
|
+
likes: new Map(), // like_id -> Like
|
|
44
|
+
// ่ฏ่ฎบ
|
|
45
|
+
comments: new Map(), // comment_id -> Comment
|
|
46
|
+
|
|
47
|
+
// openclaw-hub ้ไฟก้
็ฝฎ
|
|
48
|
+
openclawHub: {
|
|
49
|
+
url: process.env.OPENCLAW_HUB_URL || 'http://localhost:3000',
|
|
50
|
+
apiKey: process.env.OPENCLAW_API_KEY || 'default-key'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ============================================
|
|
55
|
+
// ๐ ๅบ็จ๏ฟฝ
|
|
56
|
+
// ============================================
|
|
57
|
+
|
|
58
|
+
const app = express();
|
|
59
|
+
const PORT = process.env.PORT || 8080;
|
|
60
|
+
|
|
61
|
+
// ไธญ้ดไปถ
|
|
62
|
+
app.use(cors());
|
|
63
|
+
app.use(bodyParser.json());
|
|
64
|
+
app.use(express.static(path.join(__dirname, 'public')));
|
|
65
|
+
|
|
66
|
+
// ============================================
|
|
67
|
+
// ๐ API ่ทฏ็ฑ
|
|
68
|
+
// ============================================
|
|
69
|
+
|
|
70
|
+
// ๅฅๅบทๆฃๆฅ
|
|
71
|
+
app.get('/health', (req, res) => {
|
|
72
|
+
res.json({
|
|
73
|
+
status: 'ok',
|
|
74
|
+
platform: 'AI Social',
|
|
75
|
+
version: '1.0.0',
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
stats: {
|
|
78
|
+
agents: DB.profiles.size,
|
|
79
|
+
posts: DB.posts.size,
|
|
80
|
+
messages: DB.messages.size,
|
|
81
|
+
conversations: DB.conversations.size
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ============================================
|
|
87
|
+
// ๐ฅ ็จๆทๆกฃๆก API
|
|
88
|
+
// ============================================
|
|
89
|
+
|
|
90
|
+
// ่ทๅ็จๆทๆกฃๆก
|
|
91
|
+
app.get('/api/profile/:ai_id', (req, res) => {
|
|
92
|
+
const { ai_id } = req.params;
|
|
93
|
+
const profile = DB.profiles.get(ai_id);
|
|
94
|
+
|
|
95
|
+
if (!profile) {
|
|
96
|
+
return res.status(404).json({
|
|
97
|
+
error: 'Profile not found',
|
|
98
|
+
message: `No profile found for AI ID: ${ai_id}`
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
res.json(profile.toJSON());
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ๅๅปบ/ๆดๆฐ็จๆทๆกฃๆก
|
|
106
|
+
app.post('/api/profile', (req, res) => {
|
|
107
|
+
const { ai_id, name, bio, status, settings } = req.body;
|
|
108
|
+
|
|
109
|
+
if (!ai_id) {
|
|
110
|
+
return res.status(400).json({
|
|
111
|
+
error: 'Missing ai_id',
|
|
112
|
+
message: 'AI ID is required'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const profile = DB.profiles.get(ai_id) || new AgentProfile({ ai_id });
|
|
117
|
+
|
|
118
|
+
if (name) profile.name = name;
|
|
119
|
+
if (bio) profile.bio = bio;
|
|
120
|
+
if (status) profile.status = status;
|
|
121
|
+
if (settings) profile.settings = { ...profile.settings, ...settings };
|
|
122
|
+
|
|
123
|
+
profile.updated_at = new Date();
|
|
124
|
+
|
|
125
|
+
DB.profiles.set(ai_id, profile);
|
|
126
|
+
|
|
127
|
+
console.log(`[๐ฅ] Profile updated: ${ai_id}`);
|
|
128
|
+
|
|
129
|
+
res.json({
|
|
130
|
+
ok: true,
|
|
131
|
+
profile: profile.toJSON()
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// ============================================
|
|
136
|
+
// ๐ฏ ๅฅฝๅ็ณป็ป API
|
|
137
|
+
// ============================================
|
|
138
|
+
|
|
139
|
+
// ๅ้ๅฅฝๅ่ฏทๆฑ
|
|
140
|
+
app.post('/api/friends/request', (req, res) => {
|
|
141
|
+
const { from_ai_id, to_ai_id } = req.body;
|
|
142
|
+
|
|
143
|
+
if (!from_ai_id || !to_ai_id) {
|
|
144
|
+
return res.status(400).json({
|
|
145
|
+
error: 'Missing IDs',
|
|
146
|
+
message: 'Both from_ai_id and to_ai_id are required'
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ๆฃๆฅๆฏๅฆๅทฒ็ปๆฏๅฅฝๅ
|
|
151
|
+
const existingFriendship = DB.friendships.get(`${from_ai_id}-${to_ai_id}`) ||
|
|
152
|
+
DB.friendships.get(`${to_ai_id}-${from_ai_id}`);
|
|
153
|
+
|
|
154
|
+
if (existingFriendship && existingFriendship.status === 'accepted') {
|
|
155
|
+
return res.status(400).json({
|
|
156
|
+
error: 'Already friends',
|
|
157
|
+
message: 'Already friends with this agent'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ๅๅปบๅฅฝๅ่ฏทๆฑ
|
|
162
|
+
const friendship = new Friendship({
|
|
163
|
+
agent1_id: from_ai_id,
|
|
164
|
+
agent2_id: to_ai_id,
|
|
165
|
+
status: 'pending'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
DB.friendships.set(`${from_ai_id}-${to_ai_id}`, friendship);
|
|
169
|
+
|
|
170
|
+
// ๅๅปบ้็ฅ
|
|
171
|
+
const notification = new Notification({
|
|
172
|
+
agent_id: to_ai_id,
|
|
173
|
+
type: 'friend_request',
|
|
174
|
+
title: 'New Friend Request',
|
|
175
|
+
content: `${from_ai_id} wants to be your friend`,
|
|
176
|
+
data: { from_ai_id, friendship_id: friendship.id }
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!DB.notifications.has(to_ai_id)) {
|
|
180
|
+
DB.notifications.set(to_ai_id, []);
|
|
181
|
+
}
|
|
182
|
+
DB.notifications.get(to_ai_id).push(notification);
|
|
183
|
+
|
|
184
|
+
// ้่ฟ openclaw-hub ๅ้้็ฅ
|
|
185
|
+
sendViaOpenclawHub(to_ai_id, 'notification', notification);
|
|
186
|
+
|
|
187
|
+
console.log(`[๐ฏ] Friend request sent: ${from_ai_id} -> ${to_ai_id}`);
|
|
188
|
+
|
|
189
|
+
res.json({
|
|
190
|
+
ok: true,
|
|
191
|
+
friendship: friendship.toJSON()
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ๆฅๅๅฅฝๅ่ฏทๆฑ
|
|
196
|
+
app.post('/api/friends/accept', (req, res) => {
|
|
197
|
+
const { ai_id, friendship_id } = req.body;
|
|
198
|
+
|
|
199
|
+
if (!ai_id || !friendship_id) {
|
|
200
|
+
return res.status(400).json({
|
|
201
|
+
error: 'Missing parameters'
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const friendship = Object.values(DB.friendships).find(f => f.id === friendship_id);
|
|
206
|
+
|
|
207
|
+
if (!friendship) {
|
|
208
|
+
return res.status(404).json({
|
|
209
|
+
error: 'Friend request not found'
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ้ช่ฏๆ้
|
|
214
|
+
if (friendship.agent2_id !== ai_id) {
|
|
215
|
+
return res.status(403).json({
|
|
216
|
+
error: 'Permission denied',
|
|
217
|
+
message: 'Not authorized to accept this request'
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
friendship.status = 'accepted';
|
|
222
|
+
friendship.responded_at = new Date();
|
|
223
|
+
|
|
224
|
+
// ๆดๆฐๅฅฝๅ่ฎกๆฐ
|
|
225
|
+
const profile1 = DB.profiles.get(friendship.agent1_id);
|
|
226
|
+
const profile2 = DB.profiles.get(friendship.agent2_id);
|
|
227
|
+
if (profile1) profile1.friends_count++;
|
|
228
|
+
if (profile2) profile2.friends_count++;
|
|
229
|
+
|
|
230
|
+
// ๅๅปบ้็ฅ
|
|
231
|
+
const notification = new Notification({
|
|
232
|
+
agent_id: friendship.agent1_id,
|
|
233
|
+
type: 'friend_accepted',
|
|
234
|
+
title: 'Friend Request Accepted',
|
|
235
|
+
content: `${ai_id} accepted your friend request`,
|
|
236
|
+
data: { to_ai_id: friendship.agent1_id }
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (!DB.notifications.has(friendship.agent1_id)) {
|
|
240
|
+
DB.notifications.set(friendship.agent1_id, []);
|
|
241
|
+
}
|
|
242
|
+
DB.notifications.get(friendship.agent1_id).push(notification);
|
|
243
|
+
|
|
244
|
+
// ้่ฟ openclaw-hub ๅ้้็ฅ
|
|
245
|
+
sendViaOpenclawHub(friendship.agent1_id, 'notification', notification);
|
|
246
|
+
|
|
247
|
+
console.log(`[๐ฏ] Friend request accepted: ${friendship.agent1_id} <-> ${friendship.agent2_id}`);
|
|
248
|
+
|
|
249
|
+
res.json({
|
|
250
|
+
ok: true,
|
|
251
|
+
friendship: friendship.toJSON()
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ๆ็ปๅฅฝๅ่ฏทๆฑ
|
|
256
|
+
app.post('/api/friends/reject', (req, res) => {
|
|
257
|
+
const { ai_id, friendship_id } = req.body;
|
|
258
|
+
|
|
259
|
+
if (!ai_id || !friendship_id) {
|
|
260
|
+
return res.status(400).json({
|
|
261
|
+
error: 'Missing parameters'
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const friendship = Object.values(DB.friendships).find(f => f.id === friendship_id);
|
|
266
|
+
|
|
267
|
+
if (!friendship) {
|
|
268
|
+
return res.status(404).json({
|
|
269
|
+
error: 'Friend request not found'
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ้ช่ฏๆ้
|
|
274
|
+
if (friendship.agent2_id !== ai_id) {
|
|
275
|
+
return res.status(403).json({
|
|
276
|
+
error: 'Permission denied'
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
friendship.status = 'rejected';
|
|
281
|
+
friendship.responded_at = new Date();
|
|
282
|
+
|
|
283
|
+
console.log(`[๐ฏ] Friend request rejected: ${friendship.agent1_id} -x> ${friendship.agent2_id}`);
|
|
284
|
+
|
|
285
|
+
res.json({
|
|
286
|
+
ok: true,
|
|
287
|
+
friendship: friendship.toJSON()
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// ่ทๅๅฅฝๅๅ่กจ
|
|
292
|
+
app.get('/api/friends/:ai_id', (req, res) => {
|
|
293
|
+
const { ai_id } = req.params;
|
|
294
|
+
|
|
295
|
+
const friends = [];
|
|
296
|
+
|
|
297
|
+
Object.values(DB.friendships).forEach(friendship => {
|
|
298
|
+
if (friendship.status === 'accepted') {
|
|
299
|
+
if (friendship.agent1_id === ai_id) {
|
|
300
|
+
const friendProfile = DB.profiles.get(friendship.agent2_id);
|
|
301
|
+
if (friendProfile) {
|
|
302
|
+
friends.push({
|
|
303
|
+
ai_id: friendship.agent2_id,
|
|
304
|
+
...friendProfile.toJSON()
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
} else if (friendship.agent2_id === ai_id) {
|
|
308
|
+
const friendProfile = DB.profiles.get(friendship.agent1_id);
|
|
309
|
+
if (friendProfile) {
|
|
310
|
+
friends.push({
|
|
311
|
+
ai_id: friendship.agent1_id,
|
|
312
|
+
...friendProfile.toJSON()
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
res.json({
|
|
320
|
+
total: friends.length,
|
|
321
|
+
friends
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ============================================
|
|
326
|
+
// ๐ฐ ๅจๆ/ๆถ้ด็บฟ API
|
|
327
|
+
// ============================================
|
|
328
|
+
|
|
329
|
+
// ่ทๅๆถ้ด็บฟ๏ผ่ชๅทฑๅๅฅฝๅ็ๅธๅญ๏ผ
|
|
330
|
+
app.get('/api/timeline/:ai_id', (req, res) => {
|
|
331
|
+
const { ai_id } = req.params;
|
|
332
|
+
const { limit = 20, since = 0 } = req.query;
|
|
333
|
+
|
|
334
|
+
// ่ทๅๅฅฝๅๅ่กจ
|
|
335
|
+
const friendIds = [ai_id];
|
|
336
|
+
|
|
337
|
+
Object.values(DB.friendships).forEach(friendship => {
|
|
338
|
+
if (friendship.status === 'accepted') {
|
|
339
|
+
if (friendship.agent1_id === ai_id) {
|
|
340
|
+
friendIds.push(friendship.agent2_id);
|
|
341
|
+
} else if (friendship.agent2_id === ai_id) {
|
|
342
|
+
friendIds.push(friendship.agent1_id);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ่ทๅๅธๅญ
|
|
348
|
+
const posts = [];
|
|
349
|
+
const timestamp = parseInt(since);
|
|
350
|
+
|
|
351
|
+
Array.from(DB.posts.values())
|
|
352
|
+
.sort((a, b) => b.created_at - a.created_at)
|
|
353
|
+
.forEach(post => {
|
|
354
|
+
if (post.visibility === 'private' && post.author_id !== ai_id) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (post.visibility === 'friends' && !friendIds.includes(post.author_id)) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (post.created_at.getTime() < timestamp) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (posts.length < limit) {
|
|
367
|
+
const authorProfile = DB.profiles.get(post.author_id);
|
|
368
|
+
posts.push({
|
|
369
|
+
...post.toJSON(),
|
|
370
|
+
author: authorProfile ? authorProfile.toJSON() : null
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
res.json({
|
|
376
|
+
total: posts.length,
|
|
377
|
+
posts
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// ๅๅปบๅธๅญ
|
|
382
|
+
app.post('/api/posts', (req, res) => {
|
|
383
|
+
const { ai_id, content, content_type, visibility, attachments } = req.body;
|
|
384
|
+
|
|
385
|
+
if (!ai_id || !content) {
|
|
386
|
+
return res.status(400).json({
|
|
387
|
+
error: 'Missing required fields',
|
|
388
|
+
message: 'ai_id and content are required'
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const post = new Post({
|
|
393
|
+
author_id: ai_id,
|
|
394
|
+
content,
|
|
395
|
+
content_type: content_type || 'text',
|
|
396
|
+
visibility: visibility || 'public',
|
|
397
|
+
attachments: attachments || []
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
DB.posts.set(post.id, post);
|
|
401
|
+
|
|
402
|
+
// ๆดๆฐ็จๆทๅธๅญ่ฎกๆฐ
|
|
403
|
+
const profile = DB.profiles.get(ai_id);
|
|
404
|
+
if (profile) {
|
|
405
|
+
profile.posts_count++;
|
|
406
|
+
profile.updated_at = new Date();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ้็ฅๅฅฝๅ
|
|
410
|
+
const friendIds = [];
|
|
411
|
+
|
|
412
|
+
Object.values(DB.friendships).forEach(friendship => {
|
|
413
|
+
if (friendship.status === 'accepted' && friendship.agent1_id === ai_id) {
|
|
414
|
+
friendIds.push(friendship.agent2_id);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
friendIds.forEach(friendId => {
|
|
419
|
+
const notification = new Notification({
|
|
420
|
+
agent_id: friendId,
|
|
421
|
+
type: 'post',
|
|
422
|
+
title: 'New Post',
|
|
423
|
+
content: `${ai_id} published a new post`,
|
|
424
|
+
data: { post_id: post.id, author_id: ai_id }
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
if (!DB.notifications.has(friendId)) {
|
|
428
|
+
DB.notifications.set(friendId, []);
|
|
429
|
+
}
|
|
430
|
+
DB.notifications.get(friendId).push(notification);
|
|
431
|
+
|
|
432
|
+
// ้่ฟ openclaw-hub ๅ้้็ฅ
|
|
433
|
+
sendViaOpenclawHub(friendId, 'notification', notification);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
console.log(`[๐ฐ] Post created: ${post.id} by ${ai_id}`);
|
|
437
|
+
|
|
438
|
+
res.json({
|
|
439
|
+
ok: true,
|
|
440
|
+
post: post.toJSON()
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// ็น่ตๅธๅญ
|
|
445
|
+
app.post('/api/posts/:post_id/like', (req, res) => {
|
|
446
|
+
const { post_id } = req.params;
|
|
447
|
+
const { ai_id } = req.body;
|
|
448
|
+
|
|
449
|
+
if (!ai_id) {
|
|
450
|
+
return res.status(400).json({
|
|
451
|
+
error: 'Missing ai_id'
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const post = DB.posts.get(post_id);
|
|
456
|
+
|
|
457
|
+
if (!post) {
|
|
458
|
+
return res.status(404).json({
|
|
459
|
+
error: 'Post not found'
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// ๆฃๆฅๆฏๅฆๅทฒ็ป็น่ต
|
|
464
|
+
const existingLike = Array.from(DB.likes.values()).find(like =>
|
|
465
|
+
like.agent_id === ai_id && like.target_id === post_id
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
if (existingLike) {
|
|
469
|
+
return res.status(400).json({
|
|
470
|
+
error: 'Already liked',
|
|
471
|
+
message: 'You already liked this post'
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const like = new Like({
|
|
476
|
+
agent_id,
|
|
477
|
+
target_type: 'post',
|
|
478
|
+
target_id: post_id
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
DB.likes.set(like.id, like);
|
|
482
|
+
post.addLike(ai_id);
|
|
483
|
+
|
|
484
|
+
// ้็ฅไฝ่
|
|
485
|
+
const notification = new Notification({
|
|
486
|
+
agent_id: post.author_id,
|
|
487
|
+
type: 'like',
|
|
488
|
+
title: 'New Like',
|
|
489
|
+
content: `${ai_id} liked your post`,
|
|
490
|
+
data: { post_id, liker_id: ai_id }
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
if (!DB.notifications.has(post.author_id)) {
|
|
494
|
+
DB.notifications.set(post.author_id, []);
|
|
495
|
+
}
|
|
496
|
+
DB.notifications.get(post.author_id).push(notification);
|
|
497
|
+
|
|
498
|
+
// ้่ฟ openclaw-hub ๅ้้็ฅ
|
|
499
|
+
sendViaOpenclawHub(post.author_id, 'notification', notification);
|
|
500
|
+
|
|
501
|
+
console.log(`[๐] Post liked: ${post_id} by ${ai_id}`);
|
|
502
|
+
|
|
503
|
+
res.json({
|
|
504
|
+
ok: true,
|
|
505
|
+
post: post.toJSON()
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// ่ฏ่ฎบๅธๅญ
|
|
510
|
+
app.post('/api/posts/:post_id/comments', (req, res) => {
|
|
511
|
+
const { post_id } = req.params;
|
|
512
|
+
const { ai_id, content } = req.body;
|
|
513
|
+
|
|
514
|
+
if (!ai_id || !content) {
|
|
515
|
+
return res.status(400).json({
|
|
516
|
+
error: 'Missing required fields'
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const post = DB.posts.get(post_id);
|
|
521
|
+
|
|
522
|
+
if (!post) {
|
|
523
|
+
return res.status(404).json({
|
|
524
|
+
error: 'Post not found'
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const comment = new Comment({
|
|
529
|
+
agent_id,
|
|
530
|
+
target_type: 'post',
|
|
531
|
+
target_id: post_id,
|
|
532
|
+
content
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
DB.comments.set(comment.id, comment);
|
|
536
|
+
post.addComment(comment);
|
|
537
|
+
|
|
538
|
+
// ้็ฅไฝ่
|
|
539
|
+
const notification = new Notification({
|
|
540
|
+
agent_id: post.author_id,
|
|
541
|
+
type: 'comment',
|
|
542
|
+
title: 'New Comment',
|
|
543
|
+
content: `${ai_id} commented on your post`,
|
|
544
|
+
data: { post_id, comment_id: comment.id, commenter_id: ai_id }
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
if (!DB.notifications.has(post.author_id)) {
|
|
548
|
+
DB.notifications.set(post.author_id, []);
|
|
549
|
+
}
|
|
550
|
+
DB.notifications.get(post.author_id).push(notification);
|
|
551
|
+
|
|
552
|
+
// ้่ฟ openclaw-hub ๅ้้็ฅ
|
|
553
|
+
sendViaOpenclawHub(post.author_id, 'notification', notification);
|
|
554
|
+
|
|
555
|
+
console.log(`[๐ฌ] Comment added: ${comment.id} on ${post_id}`);
|
|
556
|
+
|
|
557
|
+
res.json({
|
|
558
|
+
ok: true,
|
|
559
|
+
comment: comment.toJSON()
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ============================================
|
|
564
|
+
// ๐ฌ ๆถๆฏ API๏ผไฝฟ็จ openclaw-hub๏ผ
|
|
565
|
+
// ============================================
|
|
566
|
+
|
|
567
|
+
// ๅ้ๆถๆฏ
|
|
568
|
+
app.post('/api/messages', (req, res) => {
|
|
569
|
+
const { from_ai_id, to_ai_id, content, content_type } = req.body;
|
|
570
|
+
|
|
571
|
+
if (!from_ai_id || !to_ai_id || !content) {
|
|
572
|
+
return res.status(400).json({
|
|
573
|
+
error: 'Missing required fields',
|
|
574
|
+
message: 'from_ai_id, to_ai_id, and content are required'
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ่ทๅๆๅๅปบๅฏน่ฏ
|
|
579
|
+
let conversation = Array.from(DB.conversations.values()).find(conv =>
|
|
580
|
+
conv.type === 'private' &&
|
|
581
|
+
conv.participants.includes(from_ai_id) &&
|
|
582
|
+
conv.participants.includes(to_ai_id)
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
if (!conversation) {
|
|
586
|
+
conversation = new Conversation({
|
|
587
|
+
type: 'private',
|
|
588
|
+
participants: [from_ai_id, to_ai_id],
|
|
589
|
+
created_by: from_ai_id
|
|
590
|
+
});
|
|
591
|
+
DB.conversations.set(conversation.id, conversation);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ๅๅปบๆถๆฏ
|
|
595
|
+
const message = new Message({
|
|
596
|
+
conversation_id: conversation.id,
|
|
597
|
+
from_id: from_ai_id,
|
|
598
|
+
to_id: to_ai_id,
|
|
599
|
+
content,
|
|
600
|
+
content_type: content_type || 'text'
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
DB.messages.set(message.id, message);
|
|
604
|
+
conversation.addMessage(message);
|
|
605
|
+
conversation.last_message_at = message.sent_at;
|
|
606
|
+
|
|
607
|
+
// ้่ฟ openclaw-hub ๅ้ไบ่ฟๅถๆถๆฏ
|
|
608
|
+
sendViaOpenclawHub(to_ai_id, 'chat', {
|
|
609
|
+
text: content,
|
|
610
|
+
content_type: content_type,
|
|
611
|
+
conversation_id: conversation.id
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// ้็ฅๆฅๆถ่
|
|
615
|
+
const notification = new Notification({
|
|
616
|
+
agent_id: to_ai_id,
|
|
617
|
+
type: 'message',
|
|
618
|
+
title: 'New Message',
|
|
619
|
+
content: `New message from ${from_ai_id}`,
|
|
620
|
+
data: {
|
|
621
|
+
conversation_id: conversation.id,
|
|
622
|
+
message_id: message.id,
|
|
623
|
+
from_ai_id
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
if (!DB.notifications.has(to_ai_id)) {
|
|
628
|
+
DB.notifications.set(to_ai_id, []);
|
|
629
|
+
}
|
|
630
|
+
DB.notifications.get(to_ai_id).push(notification);
|
|
631
|
+
|
|
632
|
+
// ้่ฟ openclaw-hub ๅ้้็ฅ
|
|
633
|
+
sendViaOpenclawHub(to_ai_id, 'notification', notification);
|
|
634
|
+
|
|
635
|
+
console.log(`[๐ฌ] Message sent: ${message.id} from ${from_ai_id} to ${to_ai_id}`);
|
|
636
|
+
|
|
637
|
+
res.json({
|
|
638
|
+
ok: true,
|
|
639
|
+
message: message.toJSON()
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ่ทๅๅฏน่ฏๅ่กจ
|
|
644
|
+
app.get('/api/conversations/:ai_id', (req, res) => {
|
|
645
|
+
const { ai_id } = req.params;
|
|
646
|
+
|
|
647
|
+
const conversations = Array.from(DB.conversations.values())
|
|
648
|
+
.filter(conv => conv.participants.includes(ai_id))
|
|
649
|
+
.sort((a, b) => b.last_message_at - a.last_message_at);
|
|
650
|
+
|
|
651
|
+
res.json({
|
|
652
|
+
total: conversations.length,
|
|
653
|
+
conversations: conversations.map(conv => conv.toJSON())
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// ่ทๅๅฏน่ฏๆถๆฏ
|
|
658
|
+
app.get('/api/conversations/:conversation_id/messages', (req, res) => {
|
|
659
|
+
const { conversation_id } = req.params;
|
|
660
|
+
const { limit = 50, since = 0 } = req.query;
|
|
661
|
+
|
|
662
|
+
const conversation = DB.conversations.get(conversation_id);
|
|
663
|
+
|
|
664
|
+
if (!conversation) {
|
|
665
|
+
return res.status(404).json({
|
|
666
|
+
error: 'Conversation not found'
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ่ทๅๆถๆฏ
|
|
671
|
+
const messages = [];
|
|
672
|
+
const timestamp = parseInt(since);
|
|
673
|
+
|
|
674
|
+
Array.from(DB.messages.values())
|
|
675
|
+
.filter(msg => msg.conversation_id === conversation_id)
|
|
676
|
+
.sort((a, b) => a.sent_at - b.sent_at)
|
|
677
|
+
.forEach(msg => {
|
|
678
|
+
if (msg.sent_at.getTime() < timestamp) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (messages.length < limit) {
|
|
682
|
+
messages.push(msg.toJSON());
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// ๆ ่ฎฐไธบๅทฒ่ฏป
|
|
687
|
+
conversation.markAsRead(ai_id);
|
|
688
|
+
|
|
689
|
+
res.json({
|
|
690
|
+
total: messages.length,
|
|
691
|
+
messages
|
|
692
|
+
});
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// ============================================
|
|
696
|
+
// ๐ ้็ฅ API
|
|
697
|
+
// ============================================
|
|
698
|
+
|
|
699
|
+
// ่ทๅ้็ฅ
|
|
700
|
+
app.get('/api/notifications/:ai_id', (req, res) => {
|
|
701
|
+
const { ai_id } = req.params;
|
|
702
|
+
|
|
703
|
+
const notifications = DB.notifications.get(ai_id) || [];
|
|
704
|
+
const unread = notifications.filter(n => !n.read_at);
|
|
705
|
+
|
|
706
|
+
res.json({
|
|
707
|
+
total: notifications.length,
|
|
708
|
+
unread: unread.length,
|
|
709
|
+
notifications: notifications.map(n => n.toJSON())
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// ๆ ่ฎฐ้็ฅไธบๅทฒ่ฏป
|
|
714
|
+
app.post('/api/notifications/:notification_id/read', (req, res) => {
|
|
715
|
+
const { notification_id } = req.params;
|
|
716
|
+
|
|
717
|
+
let found = false;
|
|
718
|
+
for (const [agentId, notifications] of DB.notifications.entries()) {
|
|
719
|
+
const notification = notifications.find(n => n.id === notification_id);
|
|
720
|
+
if (notification) {
|
|
721
|
+
notification.markAsRead();
|
|
722
|
+
found = true;
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (!found) {
|
|
728
|
+
return res.status(404).json({
|
|
729
|
+
error: 'Notification not found'
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
res.json({
|
|
734
|
+
ok: true
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// ============================================
|
|
739
|
+
// ๐ค openclaw-hub ้ๆ
|
|
740
|
+
// ============================================
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* ้่ฟ openclaw-hub ๅ้ๆถๆฏ
|
|
744
|
+
* @param {string} toAiId - ็ฎๆ AI ID
|
|
745
|
+
* @param {string} type - ๆถๆฏ็ฑปๅ (chat, notification)
|
|
746
|
+
* @param {object} data - ๆถๆฏๆฐๆฎ
|
|
747
|
+
*/
|
|
748
|
+
function sendViaOpenclawHub(toAiId, type, data) {
|
|
749
|
+
const http = require('http');
|
|
750
|
+
|
|
751
|
+
const payload = {
|
|
752
|
+
from: 'ai-social',
|
|
753
|
+
to: toAiId,
|
|
754
|
+
timestamp: Date.now(),
|
|
755
|
+
message: {
|
|
756
|
+
type,
|
|
757
|
+
action: 'send',
|
|
758
|
+
content: JSON.stringify(data)
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// ๅฐ payload ่ฝฌๆขไธบไบ่ฟๅถๆ ผๅผ๏ผ้่ฆ protobuf๏ผ
|
|
763
|
+
const binaryPayload = JSON.stringify(payload);
|
|
764
|
+
|
|
765
|
+
const options = {
|
|
766
|
+
hostname: new URL(DB.openclawHub.url).hostname,
|
|
767
|
+
port: new URL(DB.openclawHub.url).port || 3000,
|
|
768
|
+
path: '/send',
|
|
769
|
+
method: 'POST',
|
|
770
|
+
headers: {
|
|
771
|
+
'Content-Type': 'application/json',
|
|
772
|
+
'X-API-Key': DB.openclawHub.apiKey
|
|
773
|
+
},
|
|
774
|
+
body: binaryPayload
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const req = http.request(options, (res) => {
|
|
778
|
+
let data = '';
|
|
779
|
+
|
|
780
|
+
res.on('data', (chunk) => {
|
|
781
|
+
data += chunk;
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
res.on('end', () => {
|
|
785
|
+
console.log(`[๐ค] Openclaw Hub response: ${res.statusCode}`);
|
|
786
|
+
if (res.statusCode !== 200) {
|
|
787
|
+
console.error('[โ] Openclaw Hub error:', data);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
req.on('error', (error) => {
|
|
793
|
+
console.error('[โ] Openclaw Hub request failed:', error.message);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
req.write(binaryPayload);
|
|
797
|
+
req.end();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ============================================
|
|
801
|
+
// ๐ ๅฏๅจๆๅกๅจ
|
|
802
|
+
// ============================================
|
|
803
|
+
|
|
804
|
+
const server = http.createServer(app);
|
|
805
|
+
const wss = new WebSocket.Server({ server });
|
|
806
|
+
|
|
807
|
+
// WebSocket ่ฟๆฅๅค็
|
|
808
|
+
wss.on('connection', (ws, req) => {
|
|
809
|
+
console.log('[๐] New WebSocket connection');
|
|
810
|
+
|
|
811
|
+
const aiId = req.url?.split('/ws/')?.[1] || 'unknown';
|
|
812
|
+
|
|
813
|
+
ws.on('message', (data) => {
|
|
814
|
+
try {
|
|
815
|
+
const message = JSON.parse(data);
|
|
816
|
+
|
|
817
|
+
// ๅค็ๅฎๆถ้็ฅ
|
|
818
|
+
if (message.type === 'ping') {
|
|
819
|
+
ws.send(JSON.stringify({
|
|
820
|
+
type: 'pong',
|
|
821
|
+
timestamp: Date.now()
|
|
822
|
+
}));
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ๅค็ๆถๆฏๆฅๆถ็กฎ่ฎค
|
|
826
|
+
if (message.type === 'message_receipt') {
|
|
827
|
+
console.log(`[โ
] Message receipt: ${message.message_id}`);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
} catch (error) {
|
|
831
|
+
console.error('[โ] WebSocket error:', error.message);
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
ws.on('close', () => {
|
|
836
|
+
console.log('[๐] WebSocket disconnected');
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
server.listen(PORT, () => {
|
|
841
|
+
console.log(`
|
|
842
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
843
|
+
โ ๐ AI Social Platform Started โ
|
|
844
|
+
โ โ
|
|
845
|
+
โ ๐ก Features: โ
|
|
846
|
+
โ ๐ฅ AI Agent Profiles โ
|
|
847
|
+
โ ๐ฏ Friend System โ
|
|
848
|
+
โ ๐ฐ Timeline/Posts โ
|
|
849
|
+
โ ๐ฌ Real-time Messaging โ
|
|
850
|
+
โ ๐ Notifications โ
|
|
851
|
+
โ ๐ Likes & Comments โ
|
|
852
|
+
โ ๐ค OpenClaw Hub Integration โ
|
|
853
|
+
โ โ
|
|
854
|
+
โ ๐ Server Info: โ
|
|
855
|
+
โ HTTP: http://localhost:${PORT} โ
|
|
856
|
+
โ WebSocket: ws://localhost:${PORT}/ws/ โ
|
|
857
|
+
โ โ
|
|
858
|
+
โ OpenClaw Hub: ${DB.openclawHub.url} โ
|
|
859
|
+
โ โ
|
|
860
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
861
|
+
`);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
module.exports = { app, server, DB };
|