telegram-support-bot 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e2776425f88d7b3255ed8130fbc382325c5c50234963b4cc4d626092e16defd
4
- data.tar.gz: 7529852a1df10a48cf1b1381fb0d04822a9fc70875340e0cae3afb36da0430d2
3
+ metadata.gz: 1aa04c608a30e8eb69bd222ab37e804224d4850472bc183d52dee74b397557c1
4
+ data.tar.gz: a84baa26e47cb70191bc6b5736e86de0187efc799f1735b47b31114c03d7066d
5
5
  SHA512:
6
- metadata.gz: cc195cf56e0bdd9299d65342ab7619d9bc091727a09c66f3d4e56a4dde5689b3083cf0cc0531ab7784f85bb600b9b097c5ab056f62f617b2a420b47b980f54b5
7
- data.tar.gz: d931f2c70b662d7167cc670a3e2b8eb6f88cfb8b3036dbdcd2f95f50b5c643762f1309921e0c593ce1b68ce38ff579e648bdbd751ad825074690a82a50705a76
6
+ metadata.gz: a4e14a5cc0d07a7f05764eaf0577eb965ef9db5e86e276d74196ed3e08c9f0956315e0bec3e9b5b5b2ec1b94f7429186e299b16a80473fff24731122ba481263
7
+ data.tar.gz: b875c18abaef4ad4dc5ce57d2e94635f354ca3e69d055fc2e9a0f8bdf626527dcd3ba16a021decd58d30746d0dfc7900f21aff707223955ccba8661d051ddb36
data/CHANGELOG.md CHANGED
@@ -4,11 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ## [0.1.13] - 2026-02-26
7
+ ## [0.1.14] - 2026-02-26
8
8
 
9
9
  ### Added
10
10
  - Optional `forward_start_to_support` configuration to forward the first user `/start`
11
11
  message to support chat, so the team can proactively start the conversation.
12
+ - Update-level deduplication by Telegram `update_id` to prevent repeated replies/forwards
13
+ when webhook deliveries are retried.
14
+
15
+ ### Fixed
16
+ - Initial `/start` forwarding is now fail-safe: errors in forwarding/persisting first-message
17
+ marker no longer crash update processing and block subsequent updates.
12
18
 
13
19
  ## [0.1.12] - 2026-02-26
14
20
 
data/README.md CHANGED
@@ -54,6 +54,9 @@ TelegramSupportBot.configure do |config|
54
54
  config.forward_start_to_support = false
55
55
  # Optional: block forwarding until contact is shared.
56
56
  config.require_contact_for_support = false
57
+ # Optional: deduplicate repeated Telegram deliveries by update_id.
58
+ # Keep > 0 (default: 24h) to avoid repeated /start replies/forwards on retries.
59
+ config.processed_update_ttl_seconds = 24 * 60 * 60
57
60
  # Optional callback to persist/lookup user profile in your app.
58
61
  config.on_contact_received = ->(profile) { YourUserMatcher.sync_from_telegram(profile) }
59
62
  # Optional callback for user-chat commands other than /start.
@@ -245,6 +248,7 @@ TelegramSupportBot.configure do |config|
245
248
  # config.mapping_ttl_seconds = 30 * 24 * 60 * 60
246
249
  # config.reaction_count_ttl_seconds = 7 * 24 * 60 * 60
247
250
  # config.user_profile_ttl_seconds = nil
251
+ # config.processed_update_ttl_seconds = 24 * 60 * 60
248
252
  end
249
253
  ```
250
254
 
@@ -9,7 +9,7 @@ module TelegramSupportBot
9
9
  :contact_received_message, :contact_invalid_message, :forward_start_to_support, :on_contact_received,
10
10
  :on_user_command,
11
11
  :state_store, :state_store_options, :mapping_ttl_seconds,
12
- :reaction_count_ttl_seconds, :user_profile_ttl_seconds
12
+ :reaction_count_ttl_seconds, :user_profile_ttl_seconds, :processed_update_ttl_seconds
13
13
 
14
14
  def initialize
15
15
  @adapter = :telegram_bot
@@ -33,6 +33,7 @@ module TelegramSupportBot
33
33
  @mapping_ttl_seconds = 30 * 24 * 60 * 60
34
34
  @reaction_count_ttl_seconds = 7 * 24 * 60 * 60
35
35
  @user_profile_ttl_seconds = nil
36
+ @processed_update_ttl_seconds = 24 * 60 * 60
36
37
  end
37
38
  end
38
39
  end
@@ -37,6 +37,7 @@ module TelegramSupportBot
37
37
  mapping_ttl_seconds: configuration.mapping_ttl_seconds,
38
38
  reaction_count_ttl_seconds: configuration.reaction_count_ttl_seconds,
39
39
  user_profile_ttl_seconds: configuration.user_profile_ttl_seconds,
40
+ processed_update_ttl_seconds: configuration.processed_update_ttl_seconds,
40
41
  **options
41
42
  )
42
43
  when :redis
@@ -46,6 +47,7 @@ module TelegramSupportBot
46
47
  mapping_ttl_seconds: configuration.mapping_ttl_seconds,
47
48
  reaction_count_ttl_seconds: configuration.reaction_count_ttl_seconds,
48
49
  user_profile_ttl_seconds: configuration.user_profile_ttl_seconds,
50
+ processed_update_ttl_seconds: configuration.processed_update_ttl_seconds,
49
51
  **options
50
52
  )
51
53
  else
@@ -12,6 +12,7 @@ module TelegramSupportBot
12
12
  @reaction_count_state = {}
13
13
  @user_profiles = {}
14
14
  @start_forwarded_users = {}
15
+ @processed_updates = {}
15
16
  end
16
17
 
17
18
  def message_map
@@ -59,6 +60,15 @@ module TelegramSupportBot
59
60
  )
60
61
  end
61
62
 
63
+ def processed_updates
64
+ @processed_updates_proxy ||= StateStore::MapProxy.new(
65
+ get_proc: ->(key) { get_processed_update(key) },
66
+ set_proc: ->(key, value) { set_processed_update(key, value) },
67
+ clear_proc: -> { clear_processed_updates },
68
+ size_proc: -> { processed_updates_size }
69
+ )
70
+ end
71
+
62
72
  def get_message_mapping(key)
63
73
  synchronize { @message_map[normalize_key(key)] }
64
74
  end
@@ -139,6 +149,22 @@ module TelegramSupportBot
139
149
  synchronize { @start_forwarded_users.size }
140
150
  end
141
151
 
152
+ def get_processed_update(key)
153
+ synchronize { @processed_updates[normalize_key(key)] }
154
+ end
155
+
156
+ def set_processed_update(key, value)
157
+ synchronize { @processed_updates[normalize_key(key)] = value }
158
+ end
159
+
160
+ def clear_processed_updates
161
+ synchronize { @processed_updates.clear }
162
+ end
163
+
164
+ def processed_updates_size
165
+ synchronize { @processed_updates.size }
166
+ end
167
+
142
168
  private
143
169
 
144
170
  def synchronize(&block)
@@ -8,7 +8,8 @@ module TelegramSupportBot
8
8
  DEFAULT_NAMESPACE = 'telegram_support_bot'
9
9
 
10
10
  def initialize(url: nil, redis: nil, namespace: DEFAULT_NAMESPACE,
11
- mapping_ttl_seconds: nil, reaction_count_ttl_seconds: nil, user_profile_ttl_seconds: nil, **_options)
11
+ mapping_ttl_seconds: nil, reaction_count_ttl_seconds: nil, user_profile_ttl_seconds: nil,
12
+ processed_update_ttl_seconds: nil, **_options)
12
13
  @redis = redis || begin
13
14
  require 'redis'
14
15
  ::Redis.new(url: url)
@@ -19,6 +20,7 @@ module TelegramSupportBot
19
20
  @mapping_ttl_seconds = mapping_ttl_seconds
20
21
  @reaction_count_ttl_seconds = reaction_count_ttl_seconds
21
22
  @user_profile_ttl_seconds = user_profile_ttl_seconds
23
+ @processed_update_ttl_seconds = processed_update_ttl_seconds
22
24
  end
23
25
 
24
26
  def message_map
@@ -66,6 +68,15 @@ module TelegramSupportBot
66
68
  )
67
69
  end
68
70
 
71
+ def processed_updates
72
+ @processed_updates_proxy ||= StateStore::MapProxy.new(
73
+ get_proc: ->(key) { get_processed_update(key) },
74
+ set_proc: ->(key, value) { set_processed_update(key, value) },
75
+ clear_proc: -> { clear_processed_updates },
76
+ size_proc: -> { processed_updates_size }
77
+ )
78
+ end
79
+
69
80
  def get_message_mapping(key)
70
81
  payload = parse_json(@redis.get(map_key(:message_map, key)))
71
82
  symbolize_hash(payload)
@@ -149,6 +160,22 @@ module TelegramSupportBot
149
160
  count_by_prefix(prefix(:start_forwarded_users))
150
161
  end
151
162
 
163
+ def get_processed_update(key)
164
+ parse_json(@redis.get(map_key(:processed_updates, key)))
165
+ end
166
+
167
+ def set_processed_update(key, value)
168
+ write_json(map_key(:processed_updates, key), value, @processed_update_ttl_seconds)
169
+ end
170
+
171
+ def clear_processed_updates
172
+ delete_by_prefix(prefix(:processed_updates))
173
+ end
174
+
175
+ def processed_updates_size
176
+ count_by_prefix(prefix(:processed_updates))
177
+ end
178
+
152
179
  private
153
180
 
154
181
  def prefix(name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TelegramSupportBot
4
- VERSION = "0.1.13"
4
+ VERSION = "0.1.14"
5
5
  end
@@ -63,6 +63,10 @@ module TelegramSupportBot
63
63
  state_store(bot_key).start_forwarded_users
64
64
  end
65
65
 
66
+ def processed_updates(bot_key = nil)
67
+ state_store(bot_key).processed_updates
68
+ end
69
+
66
70
  def user_profile(chat_id, bot: nil)
67
71
  profiles = user_profiles(bot)
68
72
  profiles[chat_id] || profiles[chat_id.to_s] || profiles[chat_id.to_i]
@@ -75,6 +79,9 @@ module TelegramSupportBot
75
79
 
76
80
  def process_update(update, bot: DEFAULT_BOT_KEY)
77
81
  with_bot_context(bot) do
82
+ update_id = update['update_id'] || update[:update_id]
83
+ return if duplicate_update?(update_id)
84
+
78
85
  # Handle different types of updates
79
86
  if update['message']
80
87
  # Process standard messages
@@ -91,6 +98,8 @@ module TelegramSupportBot
91
98
  # Log or handle unknown update types
92
99
  puts "Received an unknown type of update: #{update}"
93
100
  end
101
+
102
+ mark_update_as_processed(update_id)
94
103
  end
95
104
  end
96
105
 
@@ -518,12 +527,30 @@ module TelegramSupportBot
518
527
  return unless forward_message_to_support_chat(message, chat_id: chat_id)
519
528
 
520
529
  start_forwarded_users[chat_id] = true
530
+ rescue StandardError => error
531
+ warn_start_forwarding_failure(chat_id: chat_id, message_id: message['message_id'], error: error)
521
532
  end
522
533
 
523
534
  def start_forwarded_to_support?(chat_id)
524
535
  !start_forwarded_users[chat_id].nil?
525
536
  end
526
537
 
538
+ def duplicate_update?(update_id)
539
+ return false if update_id.nil?
540
+
541
+ !processed_updates[update_id].nil?
542
+ end
543
+
544
+ def mark_update_as_processed(update_id)
545
+ return if update_id.nil?
546
+
547
+ processed_updates[update_id] = true
548
+ end
549
+
550
+ def warn_start_forwarding_failure(chat_id:, message_id:, error:)
551
+ warn "Failed to forward initial /start for chat_id=#{chat_id} message_id=#{message_id}: #{error.class}: #{error.message}"
552
+ end
553
+
527
554
  def contact_known_for_user?(chat_id)
528
555
  !user_profile(chat_id).nil?
529
556
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegram-support-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Buslaev