slashvibe-mcp 0.3.20 → 0.3.21
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 +47 -252
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /vibe Telegram Bridge
|
|
3
|
+
*
|
|
4
|
+
* Two-way bridge between /vibe and Telegram:
|
|
5
|
+
* - Receive messages/commands via Telegram bot
|
|
6
|
+
* - Send messages to individuals/groups via bot
|
|
7
|
+
* - Forward /vibe activity to Telegram channels
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const config = require('../config');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get Telegram bot credentials from config
|
|
14
|
+
*/
|
|
15
|
+
function getBotToken() {
|
|
16
|
+
const cfg = config.load();
|
|
17
|
+
return cfg.telegram_bot_token || process.env.TELEGRAM_BOT_TOKEN || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if Telegram bridge is configured
|
|
22
|
+
*/
|
|
23
|
+
function isConfigured() {
|
|
24
|
+
return !!getBotToken();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get bot info
|
|
29
|
+
*/
|
|
30
|
+
async function getBotInfo() {
|
|
31
|
+
const token = getBotToken();
|
|
32
|
+
if (!token) throw new Error('Telegram bot token not configured');
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
|
|
37
|
+
if (!data.ok) {
|
|
38
|
+
throw new Error(`Telegram API error: ${data.description}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return data.result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Send a message to a chat (user or group)
|
|
46
|
+
*/
|
|
47
|
+
async function sendMessage(chatId, text, options = {}) {
|
|
48
|
+
const token = getBotToken();
|
|
49
|
+
if (!token) throw new Error('Telegram bot token not configured');
|
|
50
|
+
|
|
51
|
+
const body = {
|
|
52
|
+
chat_id: chatId,
|
|
53
|
+
text: text,
|
|
54
|
+
parse_mode: options.markdown ? 'Markdown' : undefined,
|
|
55
|
+
reply_to_message_id: options.replyTo || undefined,
|
|
56
|
+
disable_notification: options.silent || false
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify(body)
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
|
|
67
|
+
if (!data.ok) {
|
|
68
|
+
throw new Error(`Telegram send error: ${data.description}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return data.result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get updates (messages) from Telegram
|
|
76
|
+
* For long polling or webhook verification
|
|
77
|
+
*/
|
|
78
|
+
async function getUpdates(offset = null, limit = 100) {
|
|
79
|
+
const token = getBotToken();
|
|
80
|
+
if (!token) throw new Error('Telegram bot token not configured');
|
|
81
|
+
|
|
82
|
+
const params = new URLSearchParams({ limit: limit.toString() });
|
|
83
|
+
if (offset) params.set('offset', offset.toString());
|
|
84
|
+
|
|
85
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/getUpdates?${params}`);
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
|
|
88
|
+
if (!data.ok) {
|
|
89
|
+
throw new Error(`Telegram getUpdates error: ${data.description}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return data.result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Set webhook URL for receiving updates
|
|
97
|
+
*/
|
|
98
|
+
async function setWebhook(url, secretToken = null) {
|
|
99
|
+
const token = getBotToken();
|
|
100
|
+
if (!token) throw new Error('Telegram bot token not configured');
|
|
101
|
+
|
|
102
|
+
const body = { url };
|
|
103
|
+
if (secretToken) body.secret_token = secretToken;
|
|
104
|
+
|
|
105
|
+
const response = await fetch(`https://api.telegram.org/bot${token}/setWebhook`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify(body)
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
|
|
113
|
+
if (!data.ok) {
|
|
114
|
+
throw new Error(`Telegram setWebhook error: ${data.description}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return data.result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Process incoming webhook update from Telegram
|
|
122
|
+
*/
|
|
123
|
+
function processUpdate(update) {
|
|
124
|
+
const message = update.message || update.edited_message;
|
|
125
|
+
if (!message) return null;
|
|
126
|
+
|
|
127
|
+
const from = message.from;
|
|
128
|
+
const chat = message.chat;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
id: `telegram:${message.message_id}`,
|
|
132
|
+
channel: 'telegram',
|
|
133
|
+
type: chat.type === 'private' ? 'dm' : 'group',
|
|
134
|
+
from: {
|
|
135
|
+
id: from.id.toString(),
|
|
136
|
+
handle: from.username || from.first_name,
|
|
137
|
+
name: [from.first_name, from.last_name].filter(Boolean).join(' ')
|
|
138
|
+
},
|
|
139
|
+
chat: {
|
|
140
|
+
id: chat.id.toString(),
|
|
141
|
+
title: chat.title || null,
|
|
142
|
+
type: chat.type
|
|
143
|
+
},
|
|
144
|
+
content: message.text || '[media]',
|
|
145
|
+
timestamp: new Date(message.date * 1000).toISOString(),
|
|
146
|
+
raw: update
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Send /vibe activity notification to Telegram
|
|
152
|
+
*/
|
|
153
|
+
async function notifyActivity(chatId, activity) {
|
|
154
|
+
const { handle, action, context } = activity;
|
|
155
|
+
|
|
156
|
+
let text = `🔔 *@${handle}* ${action}`;
|
|
157
|
+
if (context) {
|
|
158
|
+
text += `\n_${context}_`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return sendMessage(chatId, text, { markdown: true, silent: true });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Send /vibe status update to Telegram
|
|
166
|
+
*/
|
|
167
|
+
async function notifyStatus(chatId, handle, mood, note) {
|
|
168
|
+
const moodEmoji = {
|
|
169
|
+
'shipping': '🔥',
|
|
170
|
+
'debugging': '🐛',
|
|
171
|
+
'deep': '🧠',
|
|
172
|
+
'afk': '☕',
|
|
173
|
+
'celebrating': '🎉',
|
|
174
|
+
'pairing': '👯'
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const emoji = moodEmoji[mood] || '●';
|
|
178
|
+
let text = `${emoji} *@${handle}* is ${mood}`;
|
|
179
|
+
|
|
180
|
+
if (note) {
|
|
181
|
+
text += `\n"${note}"`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return sendMessage(chatId, text, { markdown: true });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Send message from /vibe to Telegram
|
|
189
|
+
*/
|
|
190
|
+
async function forwardFromVibe(chatId, handle, message, context = null) {
|
|
191
|
+
let text = `💭 *@${handle}*: ${message}`;
|
|
192
|
+
|
|
193
|
+
if (context) {
|
|
194
|
+
text += `\n_via ${context}_`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return sendMessage(chatId, text, { markdown: true });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handle /vibe commands from Telegram
|
|
202
|
+
*/
|
|
203
|
+
function parseVibeCommand(text) {
|
|
204
|
+
const trimmed = text.trim();
|
|
205
|
+
|
|
206
|
+
// /status mood [note]
|
|
207
|
+
const statusMatch = trimmed.match(/^\/status\s+(\w+)(?:\s+(.+))?$/);
|
|
208
|
+
if (statusMatch) {
|
|
209
|
+
return {
|
|
210
|
+
command: 'status',
|
|
211
|
+
params: {
|
|
212
|
+
mood: statusMatch[1],
|
|
213
|
+
note: statusMatch[2] || null
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// /who
|
|
219
|
+
if (trimmed === '/who') {
|
|
220
|
+
return { command: 'who' };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// /ship [message]
|
|
224
|
+
const shipMatch = trimmed.match(/^\/ship(?:\s+(.+))?$/);
|
|
225
|
+
if (shipMatch) {
|
|
226
|
+
return {
|
|
227
|
+
command: 'ship',
|
|
228
|
+
params: {
|
|
229
|
+
message: shipMatch[1] || null
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// /dm @handle message
|
|
235
|
+
const dmMatch = trimmed.match(/^\/dm\s+@?(\w+)\s+(.+)$/);
|
|
236
|
+
if (dmMatch) {
|
|
237
|
+
return {
|
|
238
|
+
command: 'dm',
|
|
239
|
+
params: {
|
|
240
|
+
handle: dmMatch[1],
|
|
241
|
+
message: dmMatch[2]
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
isConfigured,
|
|
251
|
+
getBotToken,
|
|
252
|
+
getBotInfo,
|
|
253
|
+
sendMessage,
|
|
254
|
+
getUpdates,
|
|
255
|
+
setWebhook,
|
|
256
|
+
processUpdate,
|
|
257
|
+
parseVibeCommand,
|
|
258
|
+
notifyActivity,
|
|
259
|
+
notifyStatus,
|
|
260
|
+
forwardFromVibe
|
|
261
|
+
};
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /vibe Webhook Health Monitor
|
|
3
|
+
*
|
|
4
|
+
* Monitors webhook endpoint health and provides diagnostics for bridge connectivity.
|
|
5
|
+
* Ensures external platforms can reach /vibe webhook endpoints reliably.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const { getConfig } = require('./webhook-server');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Test webhook endpoint connectivity
|
|
13
|
+
*/
|
|
14
|
+
async function testWebhookEndpoint(url) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
|
|
18
|
+
const options = {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
timeout: 5000,
|
|
21
|
+
headers: {
|
|
22
|
+
'User-Agent': 'vibe-webhook-health-check'
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const req = https.request(url, options, (res) => {
|
|
27
|
+
const responseTime = Date.now() - startTime;
|
|
28
|
+
let body = '';
|
|
29
|
+
|
|
30
|
+
res.on('data', chunk => body += chunk);
|
|
31
|
+
res.on('end', () => {
|
|
32
|
+
resolve({
|
|
33
|
+
success: true,
|
|
34
|
+
status: res.statusCode,
|
|
35
|
+
responseTime,
|
|
36
|
+
headers: res.headers,
|
|
37
|
+
body: body.slice(0, 500) // First 500 chars
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
req.on('error', (err) => {
|
|
43
|
+
resolve({
|
|
44
|
+
success: false,
|
|
45
|
+
error: err.message,
|
|
46
|
+
responseTime: Date.now() - startTime
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
req.on('timeout', () => {
|
|
51
|
+
req.destroy();
|
|
52
|
+
resolve({
|
|
53
|
+
success: false,
|
|
54
|
+
error: 'Request timeout',
|
|
55
|
+
responseTime: Date.now() - startTime
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
req.end();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Test webhook endpoint with mock payload
|
|
65
|
+
*/
|
|
66
|
+
async function testWebhookPost(url, platform, mockPayload) {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const payloadStr = JSON.stringify(mockPayload);
|
|
70
|
+
|
|
71
|
+
const options = {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
timeout: 10000,
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
'Content-Length': Buffer.byteLength(payloadStr),
|
|
77
|
+
'User-Agent': `vibe-webhook-test-${platform}`
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Add platform-specific headers
|
|
82
|
+
if (platform === 'telegram') {
|
|
83
|
+
options.headers['x-telegram-bot-api-secret-token'] = 'test-token';
|
|
84
|
+
} else if (platform === 'discord') {
|
|
85
|
+
options.headers['x-signature-ed25519'] = 'test-signature';
|
|
86
|
+
options.headers['x-signature-timestamp'] = Math.floor(Date.now() / 1000).toString();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const req = https.request(url, options, (res) => {
|
|
90
|
+
const responseTime = Date.now() - startTime;
|
|
91
|
+
let body = '';
|
|
92
|
+
|
|
93
|
+
res.on('data', chunk => body += chunk);
|
|
94
|
+
res.on('end', () => {
|
|
95
|
+
resolve({
|
|
96
|
+
success: res.statusCode >= 200 && res.statusCode < 300,
|
|
97
|
+
status: res.statusCode,
|
|
98
|
+
responseTime,
|
|
99
|
+
body: body.slice(0, 1000)
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
req.on('error', (err) => {
|
|
105
|
+
resolve({
|
|
106
|
+
success: false,
|
|
107
|
+
error: err.message,
|
|
108
|
+
responseTime: Date.now() - startTime
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
req.on('timeout', () => {
|
|
113
|
+
req.destroy();
|
|
114
|
+
resolve({
|
|
115
|
+
success: false,
|
|
116
|
+
error: 'Request timeout',
|
|
117
|
+
responseTime: Date.now() - startTime
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
req.write(payloadStr);
|
|
122
|
+
req.end();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generate mock payloads for testing
|
|
128
|
+
*/
|
|
129
|
+
function getMockPayloads() {
|
|
130
|
+
return {
|
|
131
|
+
telegram: {
|
|
132
|
+
update_id: 123456789,
|
|
133
|
+
message: {
|
|
134
|
+
message_id: 1,
|
|
135
|
+
date: Math.floor(Date.now() / 1000),
|
|
136
|
+
text: '/test webhook connectivity',
|
|
137
|
+
from: {
|
|
138
|
+
id: 987654321,
|
|
139
|
+
is_bot: false,
|
|
140
|
+
first_name: 'Test',
|
|
141
|
+
username: 'testuser'
|
|
142
|
+
},
|
|
143
|
+
chat: {
|
|
144
|
+
id: 987654321,
|
|
145
|
+
type: 'private',
|
|
146
|
+
first_name: 'Test',
|
|
147
|
+
username: 'testuser'
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
discord: {
|
|
153
|
+
type: 1, // PING
|
|
154
|
+
timestamp: new Date().toISOString(),
|
|
155
|
+
id: '123456789012345678',
|
|
156
|
+
token: 'test_interaction_token'
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Comprehensive webhook health check
|
|
163
|
+
*/
|
|
164
|
+
async function performWebhookHealthCheck(baseUrl) {
|
|
165
|
+
const results = {
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
baseUrl,
|
|
168
|
+
endpoints: {},
|
|
169
|
+
overall: { healthy: true, issues: [] }
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Test health endpoint
|
|
173
|
+
const healthUrl = `${baseUrl}/health`;
|
|
174
|
+
console.log(`Testing health endpoint: ${healthUrl}`);
|
|
175
|
+
|
|
176
|
+
const healthResult = await testWebhookEndpoint(healthUrl);
|
|
177
|
+
results.endpoints.health = {
|
|
178
|
+
url: healthUrl,
|
|
179
|
+
...healthResult
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
if (!healthResult.success) {
|
|
183
|
+
results.overall.healthy = false;
|
|
184
|
+
results.overall.issues.push('Health endpoint unreachable');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Test Telegram webhook endpoint
|
|
188
|
+
const telegramUrl = `${baseUrl}/webhook/telegram`;
|
|
189
|
+
console.log(`Testing Telegram webhook: ${telegramUrl}`);
|
|
190
|
+
|
|
191
|
+
const mockPayloads = getMockPayloads();
|
|
192
|
+
const telegramResult = await testWebhookPost(telegramUrl, 'telegram', mockPayloads.telegram);
|
|
193
|
+
results.endpoints.telegram = {
|
|
194
|
+
url: telegramUrl,
|
|
195
|
+
...telegramResult
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (!telegramResult.success && telegramResult.status !== 401) {
|
|
199
|
+
// 401 is expected for test payload, other errors are concerning
|
|
200
|
+
results.overall.healthy = false;
|
|
201
|
+
results.overall.issues.push(`Telegram webhook issue: ${telegramResult.error || 'HTTP ' + telegramResult.status}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Test Discord webhook endpoint
|
|
205
|
+
const discordUrl = `${baseUrl}/webhook/discord`;
|
|
206
|
+
console.log(`Testing Discord webhook: ${discordUrl}`);
|
|
207
|
+
|
|
208
|
+
const discordResult = await testWebhookPost(discordUrl, 'discord', mockPayloads.discord);
|
|
209
|
+
results.endpoints.discord = {
|
|
210
|
+
url: discordUrl,
|
|
211
|
+
...discordResult
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (!discordResult.success && discordResult.status !== 401) {
|
|
215
|
+
results.overall.healthy = false;
|
|
216
|
+
results.overall.issues.push(`Discord webhook issue: ${discordResult.error || 'HTTP ' + discordResult.status}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return results;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get webhook endpoint status for external platforms
|
|
224
|
+
*/
|
|
225
|
+
async function getWebhookStatus(publicUrl) {
|
|
226
|
+
if (!publicUrl) {
|
|
227
|
+
return {
|
|
228
|
+
configured: false,
|
|
229
|
+
error: 'No public webhook URL configured',
|
|
230
|
+
setup: 'Set WEBHOOK_PUBLIC_URL environment variable'
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const healthCheck = await performWebhookHealthCheck(publicUrl);
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
configured: true,
|
|
239
|
+
healthy: healthCheck.overall.healthy,
|
|
240
|
+
lastCheck: healthCheck.timestamp,
|
|
241
|
+
endpoints: {
|
|
242
|
+
health: healthCheck.endpoints.health?.success || false,
|
|
243
|
+
telegram: healthCheck.endpoints.telegram?.success || healthCheck.endpoints.telegram?.status === 401,
|
|
244
|
+
discord: healthCheck.endpoints.discord?.success || healthCheck.endpoints.discord?.status === 401
|
|
245
|
+
},
|
|
246
|
+
issues: healthCheck.overall.issues,
|
|
247
|
+
publicUrl
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
} catch (e) {
|
|
251
|
+
return {
|
|
252
|
+
configured: true,
|
|
253
|
+
healthy: false,
|
|
254
|
+
error: e.message,
|
|
255
|
+
publicUrl
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Generate webhook setup diagnostics
|
|
262
|
+
*/
|
|
263
|
+
function getWebhookDiagnostics() {
|
|
264
|
+
const config = getConfig();
|
|
265
|
+
|
|
266
|
+
const diagnostics = {
|
|
267
|
+
server: {
|
|
268
|
+
port: config.port,
|
|
269
|
+
configured: true
|
|
270
|
+
},
|
|
271
|
+
security: {
|
|
272
|
+
webhookSecret: !!config.secret,
|
|
273
|
+
telegramSecret: !!config.telegramSecret,
|
|
274
|
+
discordPublicKey: !!config.discordPublicKey
|
|
275
|
+
},
|
|
276
|
+
channels: {
|
|
277
|
+
telegram: {
|
|
278
|
+
botConfigured: !!config.telegramSecret,
|
|
279
|
+
chatId: !!config.telegramChatId
|
|
280
|
+
},
|
|
281
|
+
discord: {
|
|
282
|
+
publicKeyConfigured: !!config.discordPublicKey,
|
|
283
|
+
channelId: !!config.vibeChannelId
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
recommendations: []
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Add recommendations based on configuration gaps
|
|
290
|
+
if (!config.secret) {
|
|
291
|
+
diagnostics.recommendations.push('Set WEBHOOK_SECRET for signature verification');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!config.telegramSecret) {
|
|
295
|
+
diagnostics.recommendations.push('Set TELEGRAM_WEBHOOK_SECRET for Telegram webhook security');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!config.discordPublicKey) {
|
|
299
|
+
diagnostics.recommendations.push('Set DISCORD_PUBLIC_KEY for Discord interaction verification');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (!config.telegramChatId) {
|
|
303
|
+
diagnostics.recommendations.push('Set TELEGRAM_VIBE_CHAT_ID to forward messages to /vibe');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!config.vibeChannelId) {
|
|
307
|
+
diagnostics.recommendations.push('Set DISCORD_VIBE_CHANNEL_ID to forward messages to /vibe');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return diagnostics;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Monitor webhook endpoint availability over time
|
|
315
|
+
*/
|
|
316
|
+
class WebhookMonitor {
|
|
317
|
+
constructor(publicUrl, checkIntervalMs = 300000) { // 5 minutes
|
|
318
|
+
this.publicUrl = publicUrl;
|
|
319
|
+
this.checkIntervalMs = checkIntervalMs;
|
|
320
|
+
this.isRunning = false;
|
|
321
|
+
this.intervalId = null;
|
|
322
|
+
this.history = [];
|
|
323
|
+
this.maxHistorySize = 144; // 24 hours at 5-minute intervals
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async start() {
|
|
327
|
+
if (this.isRunning) return;
|
|
328
|
+
|
|
329
|
+
console.log(`🔍 Starting webhook endpoint monitoring (${this.publicUrl})`);
|
|
330
|
+
this.isRunning = true;
|
|
331
|
+
|
|
332
|
+
// Initial check
|
|
333
|
+
await this.performCheck();
|
|
334
|
+
|
|
335
|
+
// Schedule regular checks
|
|
336
|
+
this.intervalId = setInterval(() => {
|
|
337
|
+
this.performCheck().catch(err => {
|
|
338
|
+
console.error('Webhook monitor error:', err);
|
|
339
|
+
});
|
|
340
|
+
}, this.checkIntervalMs);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
stop() {
|
|
344
|
+
if (!this.isRunning) return;
|
|
345
|
+
|
|
346
|
+
console.log('🛑 Stopping webhook endpoint monitoring');
|
|
347
|
+
this.isRunning = false;
|
|
348
|
+
|
|
349
|
+
if (this.intervalId) {
|
|
350
|
+
clearInterval(this.intervalId);
|
|
351
|
+
this.intervalId = null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async performCheck() {
|
|
356
|
+
const checkResult = await getWebhookStatus(this.publicUrl);
|
|
357
|
+
|
|
358
|
+
const historyEntry = {
|
|
359
|
+
timestamp: new Date().toISOString(),
|
|
360
|
+
healthy: checkResult.healthy,
|
|
361
|
+
responseTime: checkResult.endpoints?.health ?
|
|
362
|
+
(checkResult.endpoints.health.responseTime || 0) : 0,
|
|
363
|
+
issues: checkResult.issues || []
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// Add to history
|
|
367
|
+
this.history.unshift(historyEntry);
|
|
368
|
+
|
|
369
|
+
// Trim history to max size
|
|
370
|
+
if (this.history.length > this.maxHistorySize) {
|
|
371
|
+
this.history = this.history.slice(0, this.maxHistorySize);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Log significant state changes
|
|
375
|
+
if (this.history.length > 1) {
|
|
376
|
+
const prevCheck = this.history[1];
|
|
377
|
+
if (checkResult.healthy !== prevCheck.healthy) {
|
|
378
|
+
const status = checkResult.healthy ? '✅ RECOVERED' : '❌ DEGRADED';
|
|
379
|
+
console.log(`Webhook health changed: ${status} - ${this.publicUrl}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return checkResult;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
getHealthStats() {
|
|
387
|
+
if (this.history.length === 0) return null;
|
|
388
|
+
|
|
389
|
+
const totalChecks = this.history.length;
|
|
390
|
+
const healthyChecks = this.history.filter(h => h.healthy).length;
|
|
391
|
+
const uptime = (healthyChecks / totalChecks) * 100;
|
|
392
|
+
|
|
393
|
+
const responseTimes = this.history
|
|
394
|
+
.filter(h => h.responseTime > 0)
|
|
395
|
+
.map(h => h.responseTime);
|
|
396
|
+
|
|
397
|
+
const avgResponseTime = responseTimes.length > 0
|
|
398
|
+
? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length
|
|
399
|
+
: 0;
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
uptime: `${uptime.toFixed(1)}%`,
|
|
403
|
+
totalChecks,
|
|
404
|
+
healthyChecks,
|
|
405
|
+
avgResponseTime: Math.round(avgResponseTime),
|
|
406
|
+
lastCheck: this.history[0]?.timestamp,
|
|
407
|
+
isCurrentlyHealthy: this.history[0]?.healthy || false
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
module.exports = {
|
|
413
|
+
testWebhookEndpoint,
|
|
414
|
+
testWebhookPost,
|
|
415
|
+
performWebhookHealthCheck,
|
|
416
|
+
getWebhookStatus,
|
|
417
|
+
getWebhookDiagnostics,
|
|
418
|
+
getMockPayloads,
|
|
419
|
+
WebhookMonitor
|
|
420
|
+
};
|