Skip to content

Commit 147298b

Browse files
committed
feat: add message logging middleware and model to record user and bot interactions, and update user context handling
1 parent e584191 commit 147298b

4 files changed

Lines changed: 160 additions & 1 deletion

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import Msg from "../models/msg-model.js";
2+
import logger from "../helpers/logger.js";
3+
4+
export const messageLoggerMiddleware = async (ctx, next) => {
5+
// We need the Mongo User ID. It should be in ctx.state.user from previous middleware.
6+
// If not, we might be in a context where user auth didn't run or failed (e.g. some public commands?), but usually it runs.
7+
const mongoUserId = ctx.state.user?._id;
8+
const chatId = ctx.chat?.id ? String(ctx.chat.id) : null;
9+
10+
// 1. Log Incoming User Action (Only real messages)
11+
try {
12+
if (mongoUserId && chatId) {
13+
if (ctx.message) {
14+
// Standard User Message
15+
const content =
16+
ctx.message.text || ctx.message.caption || "[Non-text message]";
17+
const type = ctx.message.text
18+
? ctx.message.text.startsWith("/")
19+
? "command"
20+
: "text"
21+
: "other";
22+
23+
await Msg.create({
24+
userId: mongoUserId,
25+
messageId: ctx.message.message_id,
26+
chatId,
27+
content,
28+
isBot: false,
29+
type,
30+
originalMessage: ctx.message,
31+
});
32+
}
33+
// Note: We are explicitly skipping ctx.callbackQuery logging here as requested
34+
// "handle callbacks clearly and like no need to lock them"
35+
}
36+
} catch (err) {
37+
logger.error("Error logging incoming message:", err);
38+
}
39+
40+
// 2. Wrap Bot Response Methods to Log Outgoing
41+
const originalReply = ctx.reply.bind(ctx);
42+
const originalEditMessageText = ctx.editMessageText.bind(ctx);
43+
44+
ctx.reply = async (...args) => {
45+
try {
46+
const message = await originalReply(...args);
47+
// content is arg[0] usually
48+
const content =
49+
typeof args[0] === "string" ? args[0] : "[Non-text reply]";
50+
51+
// Log Bot Message
52+
if (message && mongoUserId) {
53+
try {
54+
await Msg.create({
55+
userId: mongoUserId,
56+
messageId: message.message_id,
57+
chatId: String(message.chat.id),
58+
content,
59+
isBot: true,
60+
type: "text",
61+
originalMessage: message,
62+
});
63+
} catch (logErr) {
64+
logger.error("Failed to log outgoing message:", logErr);
65+
}
66+
}
67+
return message;
68+
} catch (err) {
69+
logger.error("Error in wrapped ctx.reply:", err);
70+
throw err;
71+
}
72+
};
73+
74+
ctx.editMessageText = async (...args) => {
75+
try {
76+
const message = await originalEditMessageText(...args);
77+
78+
// Update the existing message if we tracked it
79+
if (message && mongoUserId) {
80+
try {
81+
await Msg.findOneAndUpdate(
82+
{ messageId: message.message_id, chatId: String(message.chat.id) },
83+
{
84+
// For a bot edit, the 'userId' field on the Msg still refers to the *User* owner of the chat context
85+
// which we have as mongoUserId.
86+
userId: mongoUserId,
87+
content:
88+
typeof args[0] === "string"
89+
? args[0]
90+
: message.text || "[Edited Content]",
91+
isBot: true,
92+
originalMessage: message,
93+
},
94+
{ upsert: true, new: true }
95+
);
96+
} catch (logErr) {
97+
logger.error("Failed to log edited message:", logErr);
98+
}
99+
}
100+
return message;
101+
} catch (err) {
102+
if (
103+
err.description &&
104+
err.description.includes("message is not modified")
105+
) {
106+
return; // Ignore
107+
}
108+
logger.error("Error in wrapped ctx.editMessageText:", err);
109+
throw err;
110+
}
111+
};
112+
113+
await next();
114+
};

src/middlewares/userAuthAndSetupMiddleware.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ export const userAuthAndSetupMiddleware = async (ctx, next) => {
2323
}
2424

2525
// create user if not exists
26-
await createUserIfNotExists(ctx);
26+
const user = await createUserIfNotExists(ctx);
27+
ctx.state.user = user;
2728
next();
2829
};
2930

src/models/msg-model.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import mongoose from "mongoose";
2+
3+
const msgSchema = new mongoose.Schema(
4+
{
5+
userId: {
6+
type: mongoose.Schema.Types.ObjectId,
7+
ref: "TelegramUser",
8+
required: true,
9+
index: true,
10+
},
11+
messageId: {
12+
type: Number,
13+
required: true,
14+
},
15+
chatId: {
16+
type: String,
17+
required: true,
18+
},
19+
content: {
20+
type: String,
21+
default: "",
22+
},
23+
isBot: {
24+
type: Boolean,
25+
default: false,
26+
},
27+
type: {
28+
type: String,
29+
enum: ["text", "callback_query", "command", "photo", "other"],
30+
default: "text",
31+
},
32+
// Meta info
33+
originalMessage: {
34+
type: mongoose.Schema.Types.Mixed, // Store some raw data if needed
35+
},
36+
},
37+
{ timestamps: true }
38+
);
39+
40+
const Msg = mongoose.model("TelegramMsg", msgSchema);
41+
42+
export default Msg;

src/services/telegraf.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import {
1616
checkGroupChatMiddleware,
1717
userAuthAndSetupMiddleware,
1818
} from "../middlewares/userAuthAndSetupMiddleware.js";
19+
import { messageLoggerMiddleware } from "../middlewares/messageLoggerMiddleware.js";
1920
import logger from "../helpers/logger.js";
2021

2122
export const bot = new Telegraf(TELEGRAM_BOT_TOKEN);
2223

2324
bot.use(checkGroupChatMiddleware);
2425
bot.use(userAuthAndSetupMiddleware);
26+
bot.use(messageLoggerMiddleware);
2527

2628
bot.telegram.setMyCommands(commands);
2729

0 commit comments

Comments
 (0)