Skip to content

Commit 9c0b454

Browse files
committed
Channel join notification
1 parent 3178bcb commit 9c0b454

8 files changed

Lines changed: 666 additions & 16 deletions

File tree

app/jobs/channels/bot_join_job.rb

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,31 @@ def perform(channel_id)
1414
channel.start_joining!
1515

1616
# Пытаемся вступить в канал
17-
success = attempt_to_join_channel(channel)
17+
join_result = attempt_to_join_channel(channel)
1818

19-
if success
19+
if join_result == true
2020
channel.mark_as_joined!
2121
notify_admins_success(channel)
2222
Rails.logger.info "Bot successfully joined channel #{channel.username}"
2323
else
24-
error_message = extract_error_message(success)
25-
channel.mark_as_join_failed!(error_message)
26-
notify_admins_failure(channel, error_message)
27-
Rails.logger.error "Bot failed to join channel #{channel.username}: #{error_message}"
24+
error_info = Channels::BotJoinErrorHandler.classify_error(join_result[:error_description])
25+
error_context = Channels::BotJoinErrorHandler.get_error_context(error_info, channel)
26+
27+
channel.mark_as_join_failed!(join_result[:error_description])
28+
notify_admins_failure(channel, join_result[:error_description])
29+
30+
# Логируем с детальным контекстом
31+
Rails.logger.error "Bot failed to join channel #{channel.username} - #{error_info[:type]}: #{join_result[:error_description]}"
32+
Rails.logger.error "Error context: #{error_context.to_json}"
33+
34+
# Отправляем уведомление в Bugsnag с полным контекстом
35+
Bugsnag.notify(
36+
StandardError.new("Bot join failed: #{error_info[:type]}"),
37+
error_info[:admin_message]
38+
) do |b|
39+
b.metadata = error_context
40+
b.severity = Channels::BotJoinErrorHandler.get_severity_level(error_info)
41+
end
2842
end
2943
end
3044
end
@@ -70,12 +84,10 @@ def extract_error_message(result)
7084
end
7185

7286
def notify_admins_success(channel)
73-
# TODO: Implement admin notifications in stage 4
74-
Rails.logger.info "Bot successfully joined channel: #{channel.username} (#{channel.title})"
87+
Channels::BotJoinNotificationService.new.notify_success(channel)
7588
end
7689

7790
def notify_admins_failure(channel, error_message)
78-
# TODO: Implement admin notifications in stage 4
79-
Rails.logger.error "Bot failed to join channel #{channel.username}: #{error_message}"
91+
Channels::BotJoinNotificationService.new.notify_failure(channel, error_message)
8092
end
8193
end

app/models/channel.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,7 @@ class Channel < ApplicationRecord
1010
validates :username, presence: true, uniqueness: true
1111

1212
# Enums
13-
enum bot_join_status: {
14-
not_joined: 0,
15-
joining: 1,
16-
joined: 2,
17-
join_failed: 3
18-
}
13+
enum :bot_join_status, %w[not_joined joining joined join_failed]
1914

2015
# Scopes
2116
scope :active, -> { where(deactivated_at: nil) }
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Сервис для детальной обработки ошибок вступления бота в каналы
2+
module Channels
3+
class BotJoinErrorHandler
4+
ERROR_TYPES = {
5+
'bad request: chat not found' => {
6+
type: :channel_not_found,
7+
user_message: 'Канал не найден или был удален',
8+
admin_message: 'Канал не существует в Telegram',
9+
severity: :high,
10+
retry_possible: false
11+
},
12+
'forbidden: bot was kicked from the channel' => {
13+
type: :bot_kicked,
14+
user_message: 'Бот был удален из канала',
15+
admin_message: 'Бота исключили из канала',
16+
severity: :high,
17+
retry_possible: false
18+
},
19+
'forbidden: bot is not a member' => {
20+
type: :bot_not_member,
21+
user_message: 'Бот не является участником канала',
22+
admin_message: 'Бот не добавлен в канал',
23+
severity: :high,
24+
retry_possible: false
25+
},
26+
'forbidden: user is deactivated' => {
27+
type: :user_deactivated,
28+
user_message: 'Пользователь деактивирован',
29+
admin_message: 'Аккаунт пользователя деактивирован',
30+
severity: :high,
31+
retry_possible: false
32+
},
33+
'too many requests: retry after' => {
34+
type: :rate_limit,
35+
user_message: 'Слишком много запросов. Попробуйте позже.',
36+
admin_message: 'Превышен лимит запросов к Telegram API',
37+
severity: :medium,
38+
retry_possible: true
39+
},
40+
'timeout' => {
41+
type: :timeout,
42+
user_message: 'Время ожидания истекло',
43+
admin_message: 'Тайм-аут при подключении к Telegram API',
44+
severity: :medium,
45+
retry_possible: true
46+
},
47+
'network error' => {
48+
type: :network_error,
49+
user_message: 'Ошибка сети',
50+
admin_message: 'Проблемы с сетевым подключением',
51+
severity: :medium,
52+
retry_possible: true
53+
},
54+
'invalid token' => {
55+
type: :invalid_token,
56+
user_message: 'Ошибка аутентификации бота',
57+
admin_message: 'Неверный токен бота',
58+
severity: :critical,
59+
retry_possible: false
60+
}
61+
}.freeze
62+
63+
def self.classify_error(error_message)
64+
error_lower = error_message.to_s.downcase
65+
66+
ERROR_TYPES.each do |pattern, info|
67+
return info if error_lower.include?(pattern)
68+
end
69+
70+
# Если не найдено совпадение, возвращаем ошибку по умолчанию
71+
{
72+
type: :unknown,
73+
user_message: 'Неизвестная ошибка',
74+
admin_message: error_message,
75+
severity: :medium,
76+
retry_possible: false
77+
}
78+
end
79+
80+
def self.get_error_context(error_info, channel)
81+
{
82+
channel: {
83+
id: channel.id,
84+
username: channel.username,
85+
title: channel.title,
86+
telegram_id: channel.telegram_id
87+
},
88+
error: error_info,
89+
timestamp: Time.current,
90+
environment: Rails.env,
91+
bot_info: {
92+
username: ApplicationConfig.bot_username
93+
}
94+
}
95+
end
96+
97+
def self.should_retry?(error_info)
98+
error_info[:retry_possible]
99+
end
100+
101+
def self.get_severity_level(error_info)
102+
error_info[:severity]
103+
end
104+
end
105+
end
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Сервис для отправки уведомлений администраторам о результатах вступления бота в каналы
2+
module Channels
3+
class BotJoinNotificationService
4+
def initialize
5+
@bot = Telegram.bots[:default]
6+
end
7+
8+
# Отправляет уведомление об успешном вступлении бота в канал
9+
def notify_success(channel)
10+
return unless should_notify?
11+
12+
admins.each do |admin|
13+
send_message_to_admin(
14+
admin.telegram_id,
15+
success_message(channel)
16+
)
17+
end
18+
19+
Rails.logger.info "Sent bot join success notifications to #{admins.count} admins for channel #{channel.username}"
20+
end
21+
22+
# Отправляет уведомление о неудачном вступлении бота в канал
23+
def notify_failure(channel, error)
24+
return unless should_notify?
25+
26+
admins.each do |admin|
27+
send_message_to_admin(
28+
admin.telegram_id,
29+
failure_message(channel, error)
30+
)
31+
end
32+
33+
Rails.logger.info "Sent bot join failure notifications to #{admins.count} admins for channel #{channel.username}"
34+
end
35+
36+
private
37+
38+
def admins
39+
@admins ||= TelegramUser.where(is_admin: true)
40+
end
41+
42+
def should_notify?
43+
admins.any?
44+
end
45+
46+
def send_message_to_admin(telegram_id, text)
47+
begin
48+
@bot.send_message(
49+
chat_id: telegram_id,
50+
text: text,
51+
parse_mode: 'Markdown'
52+
)
53+
rescue StandardError => e
54+
Rails.logger.error "Failed to send notification to admin #{telegram_id}: #{e.message}"
55+
Bugsnag.notify(e) do |b|
56+
b.metadata = {
57+
service: self.class.name,
58+
admin_telegram_id: telegram_id,
59+
action: 'send_notification'
60+
}
61+
end
62+
end
63+
end
64+
65+
def success_message(channel)
66+
I18n.t(
67+
'telegram_bot.channels.bot_join.success',
68+
channel_title: channel.title,
69+
channel_username: channel.username,
70+
channel_id: channel.telegram_id,
71+
subscribers_count: channel.subscribers_count || 0,
72+
joined_at: I18n.l(channel.bot_join_at, format: :short)
73+
)
74+
end
75+
76+
def failure_message(channel, error)
77+
I18n.t(
78+
'telegram_bot.channels.bot_join.failure',
79+
channel_title: channel.title,
80+
channel_username: channel.username,
81+
channel_id: channel.telegram_id,
82+
error_message: error,
83+
failed_at: I18n.l(Time.current, format: :short)
84+
)
85+
end
86+
end
87+
end

config/locales/ru.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,24 @@ ru:
306306
📤 Отправлено: %{sent_count}
307307
❌ Ошибок: %{error_count}
308308
👥 Всего подписчиков: %{total_subscribers}
309+
310+
bot_join:
311+
success: |
312+
✅ Бот успешно вступил в канал!
313+
314+
📺 *Канал:* %{channel_title} (@%{channel_username})
315+
🆔 *ID:* %{channel_id}
316+
👥 *Подписчиков:* %{subscribers_count}
317+
⏰ *Время вступления:* %{joined_at}
318+
319+
Теперь бот будет получать посты из этого канала.
320+
failure: |
321+
❌ Не удалось вступить в канал!
322+
323+
📺 *Канал:* %{channel_title} (@%{channel_username})
324+
🆔 *ID:* %{channel_id}
325+
❌ *Ошибка:* %{error_message}
326+
⏰ *Время ошибки:* %{failed_at}
327+
328+
Бот не сможет получать посты из этого канала.
329+
Проверьте настройки приватности канала или права бота.

0 commit comments

Comments
 (0)