Class: TgVizor::Middleware::TelegramBotRuby
- Inherits:
-
Object
- Object
- TgVizor::Middleware::TelegramBotRuby
- Defined in:
- lib/tgvizor/middleware/telegram_bot_ruby.rb
Overview
Middleware for the telegram-bot-ruby gem (github.com/atipugin/telegram-bot-ruby).
Wraps your update-handling block with auto-tracking of:
- the update type (command / message / callback_query / inline_query)
- response time (handler duration in ms)
- $identify on first-seen-per-process for each user
- $error on any StandardError raised by the handler
- user_blocked when the handler triggers a 403 from Telegram
Re-raises every exception so the host bot’s existing error handling stays intact. TgVizor is an observer, not a control-flow modifier.
Constant Summary collapse
- MESSAGE_TYPES =
%i[ text photo voice sticker video document audio animation video_note location contact poll dice ].freeze
- SEEN_USERS_CAP =
Bound on the in-process “have we identified this user yet?” set. Uncapped, a long-running bot serving millions of users would grow this forever (~80 MB at 1M ids). When the cap is hit we drop a random half —those users will simply re-fire identify on their next interaction, which is harmless.
50_000- DEFAULT_MAX_MESSAGE_TEXT_LENGTH =
Short enough that a spammer can’t inflate the events table with a 10k-char wall of text; long enough for most natural-language prompts.
500
Instance Method Summary collapse
-
#initialize(client, capture_message_text: false, max_message_text_length: DEFAULT_MAX_MESSAGE_TEXT_LENGTH) ⇒ TelegramBotRuby
constructor
‘capture_message_text` is opt-in because plain messages can contain PII (emails, card numbers, personal info).
-
#track(update) { ... } ⇒ Object
Wrap a single update through the analytics layer.
Constructor Details
#initialize(client, capture_message_text: false, max_message_text_length: DEFAULT_MAX_MESSAGE_TEXT_LENGTH) ⇒ TelegramBotRuby
‘capture_message_text` is opt-in because plain messages can contain PII (emails, card numbers, personal info). Storing user-generated content carries GDPR obligations most bot owners will want to opt into explicitly. Safe to enable for bots whose plain-text traffic is the signal itself: AI chatbots, translators, URL extractors, search bots. Commands always capture `args` regardless of this setting.
57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/tgvizor/middleware/telegram_bot_ruby.rb', line 57 def initialize( client, capture_message_text: false, max_message_text_length: DEFAULT_MAX_MESSAGE_TEXT_LENGTH ) @client = client @seen_users = Set.new @seen_mutex = Mutex.new @capture_message_text = @max_message_text_length = end |
Instance Method Details
#track(update) { ... } ⇒ Object
Wrap a single update through the analytics layer.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/tgvizor/middleware/telegram_bot_ruby.rb', line 74 def track(update, &block) classification = classify(update) user_id = extract_user_id(update) started_at = monotonic_now identify_once(update, user_id) block.call rescue Telegram::Bot::Exceptions::ResponseError => e if e.error_code.to_i == 403 # Prefer the specific command text (e.g. "/promo_weekly") over the generic # event class — bot owners want to know which action drove the block. last_action = classification[:command] || classification[:event] || "unknown" @client.track( "user_blocked", user_id: user_id, properties: { last_action: last_action }, ) else @client.capture_error(e, user_id: user_id, command: classification[:command]) end raise rescue StandardError => e @client.capture_error(e, user_id: user_id, command: classification[:command]) raise ensure if classification && classification[:event] elapsed_ms = started_at ? ((monotonic_now - started_at) * 1000).round : nil emit_classified_event(classification, user_id, elapsed_ms) end end |