@@ -59,6 +59,13 @@ def handle_message(bot, message)
59
59
@lang_code = ( message . from &.language_code || "en" ) . split ( "-" ) . first
60
60
# Remove @botname from the beginning if present
61
61
message_text = message_text . gsub ( /^@#{ @bot_username } \s +/ , "" ) if @bot_username
62
+ if @bot_username
63
+ bot_mention_regex = /@#{ @bot_username } /i
64
+
65
+ # Command with bot name appended (e.g., /feedspam@BotName)
66
+ # This strips the @BotName from the end of the command.
67
+ message_text = message_text . gsub ( bot_mention_regex , "" )
68
+ end
62
69
63
70
# Route to appropriate command handler
64
71
case message_text
@@ -73,6 +80,11 @@ def handle_message(bot, message)
73
80
when %r{^/listspam}
74
81
handle_listspam_command ( bot , message )
75
82
else
83
+ if message . reply_to_message
84
+ # If it's a reply, check if it's meant for the bot's training prompt
85
+ handle_forced_reply ( bot , message )
86
+ return
87
+ end
76
88
handle_regular_message ( bot , message )
77
89
end
78
90
rescue => e
@@ -176,32 +188,94 @@ def handle_markspam_command(bot, message)
176
188
end
177
189
end
178
190
179
- def handle_feedspam_command ( bot , message , message_text )
180
- # Extract everything after /feedspam command, preserving multiline content
181
- spam_text = message_text . sub ( %r{^/feedspam\s *} , "" ) . strip
191
+ def handle_forced_reply ( bot , message )
192
+ replied_to = message . reply_to_message
182
193
194
+ # 1. Ensure it's a reply to a message sent by the bot
195
+ return unless replied_to && replied_to . from &.id == @bot_id
196
+
197
+ # 2. Check if the bot's message was the /feedspam prompt.
198
+ # defined in I18n.t('telegram_bot.feedspam.reply_prompt', locale: @lang_code)
199
+ feedspam_expected_prefix = "/feedspam:"
200
+
201
+ Rails . logger . info "handle_forced_reply: #{ replied_to . text } "
202
+ if replied_to . text &.start_with? ( feedspam_expected_prefix )
203
+ spam_text = message . text &.strip
204
+ if spam_text . nil? || spam_text . empty?
205
+ # User replied with an empty message
206
+ bot . api . send_message (
207
+ chat_id : message . chat . id ,
208
+ text : I18n . t ( "telegram_bot.feedspam.empty_reply_error" , locale : @lang_code ) ,
209
+ reply_to_message_id : message . message_id # Reply to the empty reply to show who made the mistake
210
+ )
211
+ else
212
+ # Reroute to the actual training logic
213
+ execute_spam_training ( bot , message , spam_text )
214
+ end
215
+ end
216
+ end
217
+
218
+ def handle_feedspam_command ( bot , message , message_text )
183
219
I18n . with_locale ( @lang_code ) do
184
- if spam_text . empty?
185
- help_message = <<~TEXT
186
- #{ I18n . t ( 'telegram_bot.feedspam.help_message' ) }
187
- TEXT
188
- bot . api . send_message ( chat_id : message . chat . id , text : help_message , parse_mode : "Markdown" )
189
- return
220
+ # 1. Check if the command was used as a reply to another message
221
+ replied_message = message . reply_to_message
222
+
223
+ if replied_message && !replied_message . text . to_s . strip . empty?
224
+ Rails . logger . info "It's replied_message in feedspam"
225
+ # Use the text of the replied-to message for training
226
+ spam_text = replied_message . text
227
+
228
+ # Execute training logic
229
+ execute_spam_training ( bot , message , spam_text )
230
+
231
+ else
232
+ Rails . logger . info "It's not replied_message in feedspam"
233
+ # 2. Check for text arguments directly after the command
234
+ # Extract everything after /feedspam command, preserving multiline content
235
+ spam_text_argument = message_text . sub ( %r{^/feedspam\s *} , "" ) . strip
236
+
237
+ Rails . logger . info "feedspam #{ spam_text_argument } "
238
+ if spam_text_argument . empty?
239
+ # 3. No text and no reply found: Send a user-friendly prompt with force_reply
240
+ help_message = I18n . t ( "telegram_bot.feedspam.reply_prompt" )
241
+
242
+ force_reply_markup = {
243
+ force_reply : true ,
244
+ input_field_placeholder : I18n . t ( "telegram_bot.feedspam.input_field_placeholder" ) ,
245
+ selective : true
246
+ } . to_json # Manual JSON conversion
247
+ bot . api . send_message (
248
+ chat_id : message . chat . id ,
249
+ text : help_message ,
250
+ parse_mode : "Markdown" ,
251
+ reply_to_message_id : message . message_id , # Reply to the user's /feedspam command
252
+ reply_markup : force_reply_markup
253
+ )
254
+ return
255
+ else
256
+ # Use the text provided as argument
257
+ execute_spam_training ( bot , message , spam_text_argument )
258
+ end
190
259
end
260
+ end
261
+ end
262
+
191
263
192
- Rails . logger . info "spam message: #{ message_text } "
264
+ def execute_spam_training ( bot , message , spam_text )
265
+ I18n . with_locale ( @lang_code ) do
266
+ Rails . logger . info "Spam message to train: #{ spam_text } "
193
267
194
268
begin
195
269
user_name = [ message . from . first_name , message . from . last_name ] . compact . join ( " " )
196
270
chat_type = message . chat . type
197
- group_name = ""
271
+
198
272
if chat_type == "private"
199
273
group_name = "Private: " + user_name
200
274
else
201
275
group_name = message . chat . title
202
276
end
203
277
204
- # Save the traineded message, which will invoke ActiveModel
278
+ # Save the trained message, which will invoke ActiveModel
205
279
# hook to train the model in the background job
206
280
trained_message = TrainedMessage . create! (
207
281
group_id : message . chat . id ,
@@ -215,9 +289,10 @@ def handle_feedspam_command(bot, message, message_text)
215
289
# Show a preview of what was learned (truncated if too long)
216
290
preview = spam_text . length > 100 ? "#{ spam_text [ 0 ..100 ] } ..." : spam_text
217
291
response_message = I18n . t ( "telegram_bot.feedspam.success_message" , preview : preview )
292
+ # Send a final confirmation message
218
293
bot . api . send_message ( chat_id : message . chat . id , text : response_message , parse_mode : "Markdown" )
219
294
rescue => e
220
- Rails . logger . error "Error in feedspam command : #{ e . message } "
295
+ Rails . logger . error "Error in feedspam training : #{ e . message } "
221
296
bot . api . send_message ( chat_id : message . chat . id , text : "#{ I18n . t ( 'telegram_bot.feedspam.failure_message' ) } " )
222
297
end
223
298
end
@@ -429,6 +504,7 @@ def handle_listbanuser_command(bot, message)
429
504
430
505
def handle_regular_message ( bot , message )
431
506
return if message . text . nil? || message . text . strip . empty?
507
+
432
508
if is_in_whitelist? ( message )
433
509
Rails . logger . info "Skipping inspecting message #{ message . text } as sender is in whitelist"
434
510
return
@@ -474,6 +550,8 @@ def is_group_chat?(bot, message)
474
550
end
475
551
476
552
def is_admin_of_group? ( user :, group_id :)
553
+ return false if user . nil?
554
+
477
555
# Special case: Channel admins posting via "GroupAnonymousBot"
478
556
return true if user . is_bot && user . username == "GroupAnonymousBot"
479
557
@@ -697,9 +775,25 @@ def edit_message_text(bot, chat_id, message_id, new_text)
697
775
end
698
776
699
777
def is_in_whitelist? ( message )
700
- # don't inspect message from administrator
701
- is_admin_of_group? ( user : message . from , group_id : message . chat . id )
778
+ # 1. don't inspect message from administrator
779
+ return true if is_admin_of_group? ( user : message . from , group_id : message . chat . id )
702
780
# add more rules
781
+
782
+ # 2. Whitelist replies to the bot's own messages
783
+ # This specifically handles the user replying to the /feedspam prompt.
784
+ replied_to_message = message . reply_to_message
785
+ if replied_to_message && replied_to_message . from &.id == @bot_id
786
+ Rails . logger . info "Skipping inspection for user reply to bot message."
787
+ return true
788
+ end
789
+
790
+ # 3. This prevents the bot's instructional and alert messages from being deleted.
791
+ if message . from &.id == @bot_id
792
+ Rails . logger . info "Skipping spam inspection for a message sent by the bot (ID: #{ @bot_id } )"
793
+ return true
794
+ end
795
+
796
+ false
703
797
end
704
798
705
799
Signal . trap ( "TERM" ) do
0 commit comments