Skip to content

Commit 1fdfd9a

Browse files
committed
optimize the user experience of /feedspam
1 parent 775b51f commit 1fdfd9a

File tree

3 files changed

+113
-15
lines changed

3 files changed

+113
-15
lines changed

app/services/telegram_botter.rb

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ def handle_message(bot, message)
5959
@lang_code = (message.from&.language_code || "en").split("-").first
6060
# Remove @botname from the beginning if present
6161
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
6269

6370
# Route to appropriate command handler
6471
case message_text
@@ -73,6 +80,11 @@ def handle_message(bot, message)
7380
when %r{^/listspam}
7481
handle_listspam_command(bot, message)
7582
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
7688
handle_regular_message(bot, message)
7789
end
7890
rescue => e
@@ -176,32 +188,94 @@ def handle_markspam_command(bot, message)
176188
end
177189
end
178190

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
182193

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)
183219
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
190259
end
260+
end
261+
end
262+
191263

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}"
193267

194268
begin
195269
user_name = [ message.from.first_name, message.from.last_name ].compact.join(" ")
196270
chat_type = message.chat.type
197-
group_name = ""
271+
198272
if chat_type == "private"
199273
group_name = "Private: " + user_name
200274
else
201275
group_name = message.chat.title
202276
end
203277

204-
# Save the traineded message, which will invoke ActiveModel
278+
# Save the trained message, which will invoke ActiveModel
205279
# hook to train the model in the background job
206280
trained_message = TrainedMessage.create!(
207281
group_id: message.chat.id,
@@ -215,9 +289,10 @@ def handle_feedspam_command(bot, message, message_text)
215289
# Show a preview of what was learned (truncated if too long)
216290
preview = spam_text.length > 100 ? "#{spam_text[0..100]}..." : spam_text
217291
response_message = I18n.t("telegram_bot.feedspam.success_message", preview: preview)
292+
# Send a final confirmation message
218293
bot.api.send_message(chat_id: message.chat.id, text: response_message, parse_mode: "Markdown")
219294
rescue => e
220-
Rails.logger.error "Error in feedspam command: #{e.message}"
295+
Rails.logger.error "Error in feedspam training: #{e.message}"
221296
bot.api.send_message(chat_id: message.chat.id, text: "#{I18n.t('telegram_bot.feedspam.failure_message')}")
222297
end
223298
end
@@ -429,6 +504,7 @@ def handle_listbanuser_command(bot, message)
429504

430505
def handle_regular_message(bot, message)
431506
return if message.text.nil? || message.text.strip.empty?
507+
432508
if is_in_whitelist?(message)
433509
Rails.logger.info "Skipping inspecting message #{message.text} as sender is in whitelist"
434510
return
@@ -474,6 +550,8 @@ def is_group_chat?(bot, message)
474550
end
475551

476552
def is_admin_of_group?(user:, group_id:)
553+
return false if user.nil?
554+
477555
# Special case: Channel admins posting via "GroupAnonymousBot"
478556
return true if user.is_bot && user.username == "GroupAnonymousBot"
479557

@@ -697,9 +775,25 @@ def edit_message_text(bot, chat_id, message_id, new_text)
697775
end
698776

699777
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)
702780
# 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
703797
end
704798

705799
Signal.trap("TERM") do

config/locales/telegram_bot.en.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ en:
3838
help_message: "Please provide the spam message text after the command. Examples: `/feedspam FREE IPHONE`"
3939
success_message: "✅ Got it. I've learned from that spam message:\n\n`%{preview}`"
4040
failure_message: "❌ Failed to process the spam training request."
41+
reply_prompt: "/feedspam: Please reply to the this message with the spam you want to feed"
42+
input_field_placeholder: "Please type the spam message you want to feed"
4143
unban:
4244
already_unbanned_message: "✅User already has been unbanned"
4345
success_message: "✅ User @[%{user_name}](tg://user?id=%{user_id}) has been unbanned and their messages marked as ham."

config/locales/telegram_bot.zh.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ zh:
3838
help_message: "请在此命令后提供垃圾信息文本。示例:`/feedspam 看我简介,免费送U`"
3939
success_message: "✅ 收到,已把这条信息加入到训练数据:\n\n`%{preview}`"
4040
failure_message: "❌ 处理投喂数据异常."
41+
reply_prompt: "/feedspam: 请在此消息下回复你想要投喂的广告信息"
42+
input_field_placeholder: "请输入你想要投喂的广告信息"
4143
unban:
4244
already_unbanned_message: "✅ 用户已被解禁"
4345
success_message: "✅ 用户 @[%{user_name}](tg://user?id=%{user_id}) 已被解禁."

0 commit comments

Comments
 (0)