Skip to content

Commit b50cd6a

Browse files
committed
Add new user handler for new member events and update message handlers to support replies. Introduce configuration for new member sampling and enhance message templates accordingly.
1 parent 476acdc commit b50cd6a

File tree

7 files changed

+102
-4
lines changed

7 files changed

+102
-4
lines changed

.changeset/real-jobs-fold.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@iqai/mcp-telegram": patch
3+
---
4+
5+
added new user handler & updated message handlers to user reply_to on messages

src/config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ const envSchema = z.object({
8484
SAMPLING_ENABLE_LOCATION: z.coerce.boolean().default(false),
8585
SAMPLING_ENABLE_CONTACT: z.coerce.boolean().default(false),
8686
SAMPLING_ENABLE_POLL: z.coerce.boolean().default(false),
87+
SAMPLING_ENABLE_NEW_MEMBER: z.coerce.boolean().default(true),
8788

8889
// Response behavior
8990
SAMPLING_MAX_TOKENS: z.coerce.number().default(1000),
@@ -133,6 +134,7 @@ export const samplingConfig = {
133134
[MessageType.LOCATION]: env.SAMPLING_ENABLE_LOCATION,
134135
[MessageType.CONTACT]: env.SAMPLING_ENABLE_CONTACT,
135136
[MessageType.POLL]: env.SAMPLING_ENABLE_POLL,
137+
[MessageType.NEW_MEMBER]: env.SAMPLING_ENABLE_NEW_MEMBER,
136138
},
137139

138140
// Response behavior
@@ -249,5 +251,15 @@ message_id: {messageId}
249251
topic_id: {topicId}
250252
message_type: {messageType}
251253
content: {content}`,
254+
255+
[TemplateType.NEW_MEMBER]: `NEW MEMBER JOINED:
256+
chat_id: {chatId}
257+
message_id: {messageId}
258+
number_of_new_members: {numberOfNewMembers}
259+
new_member_id: {newMemberId}
260+
new_member_username: {newMemberUsername}
261+
new_member_first_name: {newMemberFirstName}
262+
new_member_last_name: {newMemberLastName}
263+
added_by_user_id: {addedByUserId}`,
252264
},
253265
} as const;

src/sampling/handler.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
handleLocationMessage,
1919
handleContactMessage,
2020
handlePollMessage,
21+
handleNewMemberMessage,
2122
} from "./message-handlers.js";
2223

2324
export class SamplingHandler {
@@ -127,6 +128,22 @@ export class SamplingHandler {
127128
this.bot.on(message("poll"), async (ctx: Context) => {
128129
await this.processMessage(ctx, MessageType.POLL, handlePollMessage);
129130
});
131+
132+
// New members joined
133+
this.bot.on("new_chat_members", async (ctx: Context) => {
134+
// Directly handle without text validation
135+
if (!this.validator.shouldProcessMessage(ctx, MessageType.NEW_MEMBER))
136+
return;
137+
if (
138+
!this.rateLimiter.checkRateLimit(ctx.from?.id || 0, ctx.chat?.id || 0)
139+
)
140+
return;
141+
142+
const templateData = handleNewMemberMessage(ctx);
143+
if (!templateData) return;
144+
145+
await this.handleMessage(ctx, MessageType.NEW_MEMBER, templateData);
146+
});
130147
}
131148

132149
private async handleMessage(
@@ -217,6 +234,7 @@ export class SamplingHandler {
217234
request.chatId,
218235
responseText,
219236
request.templateData.topicId,
237+
request.messageId,
220238
);
221239
}
222240

@@ -231,6 +249,7 @@ export class SamplingHandler {
231249
request.chatId,
232250
"Sorry, I encountered an error while processing your request. Please try again.",
233251
request.templateData.topicId,
252+
request.messageId,
234253
);
235254
}
236255
}

src/sampling/message-handlers.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,31 @@ export function handlePollMessage(ctx: Context): MessageTemplateData | null {
172172
messageType: MessageType.POLL,
173173
};
174174
}
175+
176+
export function handleNewMemberMessage(
177+
ctx: Context,
178+
): MessageTemplateData | null {
179+
const msg = ctx.message;
180+
if (!msg || !("new_chat_members" in msg) || !ctx.chat) return null;
181+
182+
const newMembers = msg.new_chat_members;
183+
if (!newMembers || newMembers.length === 0) return null;
184+
185+
const firstNewMember = newMembers[0];
186+
187+
return {
188+
content: `[New member joined] ${firstNewMember.username || firstNewMember.first_name}`,
189+
userId: msg.from?.id ?? firstNewMember.id,
190+
chatId: ctx.chat.id,
191+
isDM: false,
192+
messageId: msg.message_id,
193+
topicId: msg.message_thread_id,
194+
messageType: MessageType.NEW_MEMBER,
195+
newMemberId: firstNewMember.id,
196+
newMemberUsername: firstNewMember.username,
197+
newMemberFirstName: firstNewMember.first_name,
198+
newMemberLastName: firstNewMember.last_name,
199+
addedByUserId: msg.from?.id,
200+
numberOfNewMembers: newMembers.length,
201+
};
202+
}

src/sampling/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum MessageType {
1010
LOCATION = "location",
1111
CONTACT = "contact",
1212
POLL = "poll",
13+
NEW_MEMBER = "new_member",
1314
}
1415

1516
export enum TemplateType {
@@ -23,6 +24,7 @@ export enum TemplateType {
2324
CONTACT = "contact",
2425
POLL = "poll",
2526
FALLBACK = "fallback",
27+
NEW_MEMBER = "new_member",
2628
}
2729

2830
export interface SamplingRequest {
@@ -67,6 +69,12 @@ export interface MessageTemplateData {
6769
phoneNumber?: string;
6870
pollQuestion?: string;
6971
pollOptions?: string;
72+
newMemberId?: number;
73+
newMemberUsername?: string;
74+
newMemberFirstName?: string;
75+
newMemberLastName?: string;
76+
addedByUserId?: number;
77+
numberOfNewMembers?: number;
7078
[key: string]: string | number | boolean | undefined;
7179
}
7280

src/sampling/validators.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,12 @@ export class MessageValidator {
6666
// Check if it's a DM
6767
const isDM = chatId === userId;
6868

69-
// For groups: check mention requirement
69+
// For groups: check mention requirement except for system events like NEW_MEMBER
7070
if (!isDM && samplingConfig.mentionOnly) {
71+
// Allow NEW_MEMBER without mention
72+
if ((messageType as string) === "new_member") {
73+
return true;
74+
}
7175
if ("text" in msg && msg.text) {
7276
return this.isMentioned(msg.text, msg.entities);
7377
}

src/services/telegram-service.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,33 @@ export class TelegramService {
3131
chatId: string | number,
3232
text: string,
3333
topicId?: number,
34+
replyToMessageId?: number,
3435
): Promise<MessageInfo> {
3536
try {
36-
const message = await this.bot.telegram.sendMessage(chatId, text, {
37-
message_thread_id: topicId,
38-
});
37+
const options: {
38+
message_thread_id?: number;
39+
reply_parameters?: {
40+
message_id: number;
41+
allow_sending_without_reply?: boolean;
42+
};
43+
} = {};
44+
45+
if (typeof topicId === "number") {
46+
options.message_thread_id = topicId;
47+
}
48+
49+
if (typeof replyToMessageId === "number") {
50+
options.reply_parameters = {
51+
message_id: replyToMessageId,
52+
allow_sending_without_reply: true,
53+
};
54+
}
55+
56+
const message = await this.bot.telegram.sendMessage(
57+
chatId,
58+
text,
59+
options,
60+
);
3961

4062
return {
4163
messageId: message.message_id,

0 commit comments

Comments
 (0)