Skip to content

Commit d3deb9f

Browse files
committed
feat: implement formatErrorForClubs utility for structured error reporting
1 parent cf8c423 commit d3deb9f

2 files changed

Lines changed: 153 additions & 14 deletions

File tree

bot/handlers/messages/groupHandler.js

Lines changed: 107 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { ERROR_RESPONSES } = require("../../constants/errorResponses");
99
const { sendToPerplexity } = require("../../services/perplexity");
1010
const { convertENtoFA } = require("../../utils/convertENtoFA");
1111
const { escapeHtml } = require("../../utils/escapeHtml");
12+
const { formatErrorForClubs } = require("../../utils/formatErrorForClubs");
1213
const {
1314
formatGroupMessage,
1415
formatGroupMessageChunks,
@@ -180,22 +181,44 @@ const processMessage = async (
180181
ctx,
181182
options = {}
182183
) => {
183-
const { replyChunksTo = "original", fallbackReplyMode = "telegram" } =
184-
options;
184+
// console.log("IncomeData >>", {
185+
// text,
186+
// photoUrls,
187+
// chatId,
188+
// replyToMessageId,
189+
// chatType,
190+
// options,
191+
// });
192+
// console.log("------------------------------------");
193+
194+
const {
195+
replyChunksTo = "original",
196+
fallbackReplyMode = "telegram",
197+
// - "always": always send details
198+
// - "never": never send details (only generic)
199+
// - "adminOnly": send details only if chatId is in adminChatIds
200+
sendErrorDetails = "always",
201+
adminChatIds = [],
202+
} = options;
203+
204+
const shouldSendDetails =
205+
sendErrorDetails === "always" ||
206+
(sendErrorDetails === "adminOnly" &&
207+
(adminChatIds.includes(chatId) ||
208+
adminChatIds.includes(String(chatId))));
185209

186210
if (await groupMessageValidator(chatType, chatId, text, ctx)) {
187211
try {
188212
const processingMessage = await telegramClient.sendMessage(
189213
chatId,
190214
"🕒 در حال پردازش...",
191-
{
192-
reply_to_message_id: replyToMessageId,
193-
}
215+
{ reply_to_message_id: replyToMessageId }
194216
);
195217

196218
const rawResponse = await sendToPerplexity(text, photoUrls);
197-
// let rawResponse = "test";
198-
// const rawResponse = "<b>test</b> 📊" + "w".repeat(50);
219+
220+
// console.log("RawResponse >>", rawResponse);
221+
// console.log("------------------------------------");
199222

200223
const response = convertENtoFA(rawResponse);
201224

@@ -218,9 +241,13 @@ const processMessage = async (
218241
return;
219242
}
220243

244+
// console.log("Before Chunks >>", response);
245+
// console.log("------------------------------------");
246+
221247
const chunks = buildChunks(response, 4000);
222248

223-
console.log("Response Chunks >>", chunks);
249+
// console.log("Response Chunks >>", chunks);
250+
// console.log("------------------------------------");
224251

225252
await telegramClient.editMessageText(
226253
chatId,
@@ -233,6 +260,9 @@ const processMessage = async (
233260
}
234261
);
235262

263+
// console.log("First chunk sent.", chunks[0]);
264+
// console.log("------------------------------------");
265+
236266
const replyToForExtraChunks =
237267
replyChunksTo === "processing"
238268
? processingMessage.message_id
@@ -244,30 +274,93 @@ const processMessage = async (
244274
disable_web_page_preview: true,
245275
reply_to_message_id: replyToForExtraChunks,
246276
});
277+
278+
// console.log(`Chunk ${i + 1} sent.`, chunks[i]);
279+
// console.log("------------------------------------");
247280
}
248281
} catch (error) {
249282
console.error(
250283
"❌ Error processing message:",
251284
error && error.stack ? error.stack : error
252285
);
253286

287+
const context = {
288+
chatId,
289+
chatType,
290+
replyToMessageId,
291+
replyChunksTo,
292+
fallbackReplyMode,
293+
hasPhotos: Array.isArray(photoUrls) && photoUrls.length > 0,
294+
photoCount: Array.isArray(photoUrls) ? photoUrls.length : 0,
295+
textPreview: String(text ?? "").slice(0, 500),
296+
};
297+
254298
try {
299+
if (!shouldSendDetails) {
300+
// Generic message only
301+
if (fallbackReplyMode === "ctx" && ctx?.reply) {
302+
await ctx.reply("❌ مشکلی پیش اومد.", {
303+
disable_web_page_preview: true,
304+
});
305+
} else {
306+
await telegramClient.sendMessage(
307+
chatId,
308+
"❌ مشکلی پیش اومد.",
309+
{ reply_to_message_id: replyToMessageId }
310+
);
311+
}
312+
return;
313+
}
314+
315+
const errorHtml = formatErrorForClubs(error, context);
316+
const errChunks = safeChunkText(errorHtml, 4000);
317+
255318
if (fallbackReplyMode === "ctx" && ctx?.reply) {
256-
await ctx.reply("❌ مشکلی پیش اومد.");
319+
for (const ch of errChunks) {
320+
await ctx.reply(ch, {
321+
parse_mode: "HTML",
322+
disable_web_page_preview: true,
323+
});
324+
}
325+
// console.log("Fallback error details sent via ctx.");
326+
// console.log("------------------------------------");
257327
} else {
328+
// First one replies to original message
329+
await telegramClient.sendMessage(chatId, errChunks[0], {
330+
parse_mode: "HTML",
331+
disable_web_page_preview: true,
332+
reply_to_message_id: replyToMessageId,
333+
});
334+
335+
for (let i = 1; i < errChunks.length; i++) {
336+
await telegramClient.sendMessage(chatId, errChunks[i], {
337+
parse_mode: "HTML",
338+
disable_web_page_preview: true,
339+
reply_to_message_id: replyToMessageId,
340+
});
341+
}
342+
}
343+
} catch (e) {
344+
console.error(
345+
"❌ Error sending fallback reply:",
346+
e && e.stack ? e.stack : e
347+
);
348+
349+
// Last-resort generic message
350+
try {
258351
await telegramClient.sendMessage(
259352
chatId,
260353
"❌ مشکلی پیش اومد.",
261354
{
262355
reply_to_message_id: replyToMessageId,
263356
}
264357
);
358+
} catch (_) {
359+
console.error(
360+
"❌ Final fallback message failed.",
361+
_ && _.stack ? _.stack : _
362+
);
265363
}
266-
} catch (e) {
267-
console.error(
268-
"❌ Error sending fallback reply:",
269-
e && e.stack ? e.stack : e
270-
);
271364
}
272365
}
273366
}

bot/utils/formatErrorForClubs.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const { escapeHtml } = require("./escapeHtml");
2+
3+
const redactSecrets = (income) => {
4+
if (!income) return income;
5+
6+
return String(income)
7+
.replace(/(api[_-]?key\s*[:=]\s*)([^\s"'`]+)/gi, "$1[REDACTED]")
8+
.replace(
9+
/(authorization\s*[:=]\s*bearer\s+)([^\s"'`]+)/gi,
10+
"$1[REDACTED]"
11+
)
12+
.replace(/(token\s*[:=]\s*)([^\s"'`]+)/gi, "$1[REDACTED]");
13+
};
14+
15+
const safeJson = (obj) => {
16+
try {
17+
return JSON.stringify(
18+
obj,
19+
(k, v) => {
20+
if (typeof v === "string") return redactSecrets(v);
21+
return v;
22+
},
23+
2
24+
);
25+
} catch {
26+
return String(obj);
27+
}
28+
};
29+
30+
const formatErrorForClubs = (err, context = {}) => {
31+
const name = err?.name || "Error";
32+
const message = err?.message || String(err || "");
33+
const stack = err?.stack || "";
34+
35+
return (
36+
`❌ <b>خطا رخ داد</b>\n\n` +
37+
`<b>نوع:</b> ${escapeHtml(name)}\n` +
38+
`<b>پیام:</b> ${escapeHtml(redactSecrets(message))}\n\n` +
39+
`<b>Stack:</b>\n<pre>${escapeHtml(
40+
redactSecrets(stack || "(no stack)")
41+
)}</pre>\n\n` +
42+
`<b>Context:</b>\n<pre>${escapeHtml(safeJson(context))}</pre>`
43+
);
44+
};
45+
46+
module.exports = { formatErrorForClubs };

0 commit comments

Comments
 (0)