telegram-support-bot 0.1.10 → 0.1.12

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: 9263ca971ffb1e1bb24ae8bc23342c4596748573e7b694485173e14dd2d82d44
4
- data.tar.gz: fce5aa69e55343db503d12779a3b66b4f2caabd407f18b1e6668532d1abf9bbb
3
+ metadata.gz: f542e3e9a21dc2d7d9c45c9b65b19aec360bad95b60f0e4f999f3a9f4c532700
4
+ data.tar.gz: 0136afb1b7bea4ce86afcd4f28b3af38f56743f4a2a4a87c29bfd33287f792e4
5
5
  SHA512:
6
- metadata.gz: 60ab56248d3f245ab2aa8530f8b6c2254003e881c08f632d75e81648cafa0273d35c9cb35cd4d2aece9ec567de0e5effae6ef97205f578cc931695bfa01b1769
7
- data.tar.gz: 2e6f1fa2f22a606cc418a2f3f054cc38a7565093d4187c609333583ca22db00c77b9f313f3f45aa0dac6eb5877d1386de91f2ba72730e3bab3caf5fdb9d2294f
6
+ metadata.gz: 7599da9dab831b489bd6f687bf9d779fc5b3582a1a5546eea8d3c00e4febce46bddccc6aaa118177ad20e296f95fb1ff8f8bc54f013e30e41e462c9e3f8aa576
7
+ data.tar.gz: 00f33f8a9eed3902cbde9584d06a781880f6efe9a0cbd6f1405ac2450e82da1e0605a3c0eed25520e9c6c46e8291e40495276cd08cc59a0e92c7494023c35a13
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.12] - 2026-02-26
8
+
9
+ ### Fixed
10
+ - `my_chat_member` onboarding message with support chat ID is no longer sent for private chats.
11
+ - Support-chat onboarding message is now emitted only when the bot is newly added to a non-private chat.
12
+
13
+ ## [0.1.11] - 2026-02-25
14
+
15
+ ### Added
16
+ - Optional host callback `on_user_command` for user-chat commands other than `/start`.
17
+ Return `true` to mark a command as handled and skip forwarding to support chat.
18
+
19
+ ### Fixed
20
+ - User-chat `/start` detection now recognizes deep-link command forms and bot mentions:
21
+ `/start`, `/start <payload>`, `/start@botname`, `/start@botname <payload>`.
22
+ - Start commands in user chats continue to run welcome/contact onboarding and are not forwarded to the support chat.
23
+
7
24
  ## [0.1.10] - 2026-02-25
8
25
 
9
26
  ### Added
data/README.md CHANGED
@@ -54,6 +54,9 @@ TelegramSupportBot.configure do |config|
54
54
  config.require_contact_for_support = false
55
55
  # Optional callback to persist/lookup user profile in your app.
56
56
  config.on_contact_received = ->(profile) { YourUserMatcher.sync_from_telegram(profile) }
57
+ # Optional callback for user-chat commands other than /start.
58
+ # Return true to mark the command as handled (it will not be forwarded).
59
+ config.on_user_command = ->(command:, args:, chat_id:, message:, bot_username: nil) { false }
57
60
  # Recommended in Kubernetes/multi-pod setup:
58
61
  # config.state_store = :redis
59
62
  # config.state_store_options = { url: ENV.fetch('REDIS_URL'), namespace: 'telegram_support_bot' }
@@ -197,6 +200,29 @@ other user messages until contact is shared.
197
200
  Support replies are routed by internal message mapping, so users do not need to change Telegram
198
201
  forwarding privacy settings to receive replies.
199
202
 
203
+ ## Host Handling For User Commands
204
+
205
+ You can handle user-chat commands in your app before they are forwarded to support:
206
+
207
+ ```ruby
208
+ TelegramSupportBot.configure do |config|
209
+ config.on_user_command = lambda do |command:, args:, chat_id:, message:, bot_username: nil|
210
+ case command
211
+ when '/help'
212
+ TelegramSupportBot.adapter.send_message(chat_id: chat_id, text: 'How can we help?')
213
+ true
214
+ else
215
+ false
216
+ end
217
+ end
218
+ end
219
+ ```
220
+
221
+ Behavior:
222
+ - Triggered only for user-chat commands that start with `/` and are not `/start`.
223
+ - Receives `command` (normalized to lowercase), `bot_username` (if present), and `args` (text after command).
224
+ - Return `true` to stop forwarding to support chat; return `false`/`nil` to keep default forwarding.
225
+
200
226
  ## State Storage (Single Pod vs Multi-Pod)
201
227
 
202
228
  By default, runtime state is stored in-memory (`state_store = :memory`). This is fine for local
@@ -7,6 +7,7 @@ module TelegramSupportBot
7
7
  :ignore_non_command_messages, :non_command_message_response,
8
8
  :request_contact_on_start, :require_contact_for_support, :contact_request_message,
9
9
  :contact_received_message, :contact_invalid_message, :on_contact_received,
10
+ :on_user_command,
10
11
  :state_store, :state_store_options, :mapping_ttl_seconds,
11
12
  :reaction_count_ttl_seconds, :user_profile_ttl_seconds
12
13
 
@@ -25,6 +26,7 @@ module TelegramSupportBot
25
26
  @contact_received_message = 'Thanks! We have saved your phone number.'
26
27
  @contact_invalid_message = 'Please use the button below to share your own phone number.'
27
28
  @on_contact_received = nil
29
+ @on_user_command = nil
28
30
  @state_store = :memory
29
31
  @state_store_options = {}
30
32
  @mapping_ttl_seconds = 30 * 24 * 60 * 60
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TelegramSupportBot
4
- VERSION = "0.1.10"
4
+ VERSION = "0.1.12"
5
5
  end
@@ -173,12 +173,18 @@ module TelegramSupportBot
173
173
  return
174
174
  end
175
175
 
176
- if message['text'] == '/start'
176
+ command_data = parse_command(message['text'])
177
+
178
+ if command_data && command_data[:command] == '/start'
177
179
  adapter.send_message(chat_id: chat_id, text: configuration.welcome_message)
178
180
  request_contact_from_user(chat_id: chat_id) if should_request_contact?(chat_id)
179
181
  return
180
182
  end
181
183
 
184
+ if command_data && handle_user_command(command_data: command_data, message: message, chat_id: chat_id)
185
+ return
186
+ end
187
+
182
188
  if configuration.require_contact_for_support && !contact_known_for_user?(chat_id)
183
189
  request_contact_from_user(chat_id: chat_id)
184
190
  return
@@ -226,7 +232,9 @@ module TelegramSupportBot
226
232
  end
227
233
 
228
234
  def process_command(message, chat_id:)
229
- command = message['text'].split(/[ \@]/).first.downcase # Extract the command, normalize to lowercase
235
+ command_data = parse_command(message['text'])
236
+ command = command_data && command_data[:command]
237
+ return unless command
230
238
 
231
239
  case command
232
240
  when '/start'
@@ -240,6 +248,35 @@ module TelegramSupportBot
240
248
  end
241
249
  end
242
250
 
251
+ def handle_user_command(command_data:, message:, chat_id:)
252
+ callback = configuration.on_user_command
253
+ return false unless callback.respond_to?(:call)
254
+ return false if command_data[:command] == '/start'
255
+
256
+ callback.call(
257
+ command: command_data[:command],
258
+ bot_username: command_data[:bot_username],
259
+ args: command_data[:args],
260
+ message: message,
261
+ chat_id: chat_id
262
+ )
263
+ rescue StandardError => error
264
+ warn "Failed to run on_user_command callback: #{error.class}: #{error.message}"
265
+ false
266
+ end
267
+
268
+ def parse_command(text)
269
+ return nil unless text.is_a?(String)
270
+
271
+ token, args = text.strip.split(/\s+/, 2)
272
+ return nil unless token&.start_with?('/')
273
+
274
+ command, bot_username = token.split('@', 2)
275
+ return nil if command.nil? || command.empty?
276
+
277
+ { command: command.downcase, bot_username: bot_username, args: args&.strip }
278
+ end
279
+
243
280
  def process_reply_in_support_chat(message)
244
281
  reply_to_message = message['reply_to_message']
245
282
  reply_to_message_id = reply_to_message['message_id']
@@ -329,11 +366,18 @@ module TelegramSupportBot
329
366
  end
330
367
 
331
368
  def handle_my_chat_member_update(update)
332
- # Check if the bot has been added to the chat
333
- if update['new_chat_member']
334
- chat_id = update['chat']['id']
335
- send_welcome_message(chat_id: chat_id)
336
- end
369
+ new_status = update.dig('new_chat_member', 'status')
370
+ old_status = update.dig('old_chat_member', 'status')
371
+ return unless %w[member administrator].include?(new_status)
372
+ return if %w[member administrator].include?(old_status)
373
+
374
+ chat = update['chat'] || {}
375
+ return if chat['type'] == 'private'
376
+
377
+ chat_id = chat['id']
378
+ return if chat_id.nil?
379
+
380
+ send_welcome_message(chat_id: chat_id)
337
381
  end
338
382
 
339
383
  def process_message_reaction(message_reaction)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegram-support-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Buslaev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-25 00:00:00.000000000 Z
11
+ date: 2026-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -40,7 +40,6 @@ executables: []
40
40
  extensions: []
41
41
  extra_rdoc_files: []
42
42
  files:
43
- - ".idea/workspace.xml"
44
43
  - ".rspec"
45
44
  - CHANGELOG.md
46
45
  - CODE_OF_CONDUCT.md
data/.idea/workspace.xml DELETED
@@ -1,170 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="AutoImportSettings">
4
- <option name="autoReloadType" value="SELECTIVE" />
5
- </component>
6
- <component name="ChangeListManager">
7
- <list default="true" id="edf498b0-8552-42f1-846d-0c79d29ff991" name="Changes" comment="" />
8
- <option name="SHOW_DIALOG" value="false" />
9
- <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
- <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
11
- <option name="LAST_RESOLUTION" value="IGNORE" />
12
- </component>
13
- <component name="EmbeddingIndexingInfo">
14
- <option name="cachedIndexableFilesCount" value="26" />
15
- </component>
16
- <component name="FileTemplateManagerImpl">
17
- <option name="RECENT_TEMPLATES">
18
- <list>
19
- <option value="Minitest Spec" />
20
- </list>
21
- </option>
22
- </component>
23
- <component name="Git.Settings">
24
- <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
25
- </component>
26
- <component name="MarkdownSettingsMigration">
27
- <option name="stateVersion" value="1" />
28
- </component>
29
- <component name="McpProjectServerCommands">
30
- <commands />
31
- <urls />
32
- </component>
33
- <component name="ProjectColorInfo">{
34
- &quot;associatedIndex&quot;: 4
35
- }</component>
36
- <component name="ProjectId" id="2ciiVgDyGIIwTjG6xCPIunFNdqZ" />
37
- <component name="ProjectLevelVcsManager">
38
- <ConfirmationsSetting value="2" id="Add" />
39
- </component>
40
- <component name="ProjectViewState">
41
- <option name="hideEmptyMiddlePackages" value="true" />
42
- <option name="showLibraryContents" value="true" />
43
- </component>
44
- <component name="PropertiesComponent"><![CDATA[{
45
- "keyToString": {
46
- "DefaultRubyCreateTestTemplate": "Minitest Spec",
47
- "ModuleVcsDetector.initialDetectionPerformed": "true",
48
- "RSpec.Unnamed.executor": "Run",
49
- "Ruby.scratch_61.executor": "Run",
50
- "RunOnceActivity.MCP Project settings loaded": "true",
51
- "RunOnceActivity.OpenProjectViewOnStart": "true",
52
- "RunOnceActivity.ShowReadmeOnStart": "true",
53
- "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
54
- "RunOnceActivity.git.unshallow": "true",
55
- "RunOnceActivity.typescript.service.memoryLimit.init": "true",
56
- "com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
57
- "git-widget-placeholder": "main",
58
- "junie.onboarding.icon.badge.shown": "true",
59
- "last_opened_file_path": "/Users/max/code/gems/telegram_support_bot",
60
- "node.js.detected.package.eslint": "true",
61
- "node.js.detected.package.tslint": "true",
62
- "node.js.selected.package.eslint": "(autodetect)",
63
- "node.js.selected.package.tslint": "(autodetect)",
64
- "nodejs_package_manager_path": "npm",
65
- "ruby.structure.view.model.defaults.configured": "true",
66
- "settings.editor.selected.configurable": "org.jetbrains.plugins.ruby.settings.RubyActiveModuleSdkConfigurable",
67
- "to.speed.mode.migration.done": "true",
68
- "vue.rearranger.settings.migration": "true"
69
- },
70
- "keyToStringList": {
71
- "com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
72
- "ruby"
73
- ]
74
- }
75
- }]]></component>
76
- <component name="RecentsManager">
77
- <key name="CopyFile.RECENT_KEYS">
78
- <recent name="$PROJECT_DIR$/spec/telegram_support_bot/adapters" />
79
- <recent name="$PROJECT_DIR$/spec/support" />
80
- <recent name="$PROJECT_DIR$/spec" />
81
- <recent name="$PROJECT_DIR$/lib/telegram_support_bot" />
82
- <recent name="$PROJECT_DIR$/lib/telegram_support_bot/adapters" />
83
- </key>
84
- </component>
85
- <component name="RunManager" selected="Ruby.scratch_61">
86
- <configuration name="Unnamed" type="RSpecRunConfigurationType" factoryName="RSpec" nameIsGenerated="true">
87
- <module name="telegram_support_bot" />
88
- <predefined_log_file enabled="true" id="RUBY_RSPEC" />
89
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUBY_ARGS" VALUE="" />
90
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="WORK DIR" VALUE="" />
91
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SHOULD_USE_SDK" VALUE="false" />
92
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ALTERN_SDK_NAME" VALUE="" />
93
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="myPassParentEnvs" VALUE="true" />
94
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
95
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" runner="rcov" />
96
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
97
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TESTS_FOLDER_PATH" VALUE="$MODULE_DIR$/spec" />
98
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATH" VALUE="" />
99
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_SCRIPT_PATHS" VALUE="" />
100
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_RUNNER_PATH" VALUE="" />
101
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_FILE_MASK" VALUE="**/*_spec.rb" />
102
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_EXAMPLE_NAME" VALUE="" />
103
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_EXAMPLE_MATCHES" VALUE="false" />
104
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="LINE_NUMBER_EXAMPLE_IDS" VALUE="" />
105
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="TEST_TEST_TYPE" VALUE="ALL_IN_FOLDER" />
106
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPEC_ARGS" VALUE="" />
107
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="RUNNER_VERSION" VALUE="" />
108
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="USE_CUSTOM_SPEC_RUNNER" VALUE="false" />
109
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="DRB" VALUE="false" />
110
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="ZEUS" VALUE="false" />
111
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="SPRING" VALUE="false" />
112
- <RSPEC_RUN_CONFIG_SETTINGS_ID NAME="FULL_BACKTRACE" VALUE="false" />
113
- <method v="2" />
114
- </configuration>
115
- <configuration name="scratch_61" type="RubyRunConfigurationType" factoryName="Ruby" temporary="true">
116
- <RUBY_RUN_CONFIG NAME="RUBY_ARGS" VALUE="" />
117
- <RUBY_RUN_CONFIG NAME="WORK DIR" VALUE="$PROJECT_DIR$" />
118
- <RUBY_RUN_CONFIG NAME="SHOULD_USE_SDK" VALUE="false" />
119
- <RUBY_RUN_CONFIG NAME="ALTERN_SDK_NAME" VALUE="" />
120
- <RUBY_RUN_CONFIG NAME="myPassParentEnvs" VALUE="true" />
121
- <EXTENSION ID="BundlerRunConfigurationExtension" BUNDLE_MODE="AUTO" bundleExecEnabled="true" />
122
- <EXTENSION ID="RubyCoverageRunConfigurationExtension" runner="rcov" />
123
- <EXTENSION ID="org.jetbrains.plugins.ruby.rails.run.RailsRunConfigurationExtension" SCRATCH_USE_RAILS_RUNNER="false" />
124
- <RUBY_RUN_CONFIG NAME="SCRIPT_PATH" VALUE="$APPLICATION_CONFIG_DIR$/scratches/scratch_61.rb" />
125
- <RUBY_RUN_CONFIG NAME="SCRIPT_ARGS" VALUE="" />
126
- <method v="2" />
127
- </configuration>
128
- <list>
129
- <item itemvalue="RSpec.Unnamed" />
130
- <item itemvalue="Ruby.scratch_61" />
131
- </list>
132
- <recent_temporary>
133
- <list>
134
- <item itemvalue="Ruby.scratch_61" />
135
- </list>
136
- </recent_temporary>
137
- </component>
138
- <component name="SharedIndexes">
139
- <attachedChunks>
140
- <set>
141
- <option value="bundled-js-predefined-d6986cc7102b-9b0f141eb926-JavaScript-RM-253.30387.79" />
142
- </set>
143
- </attachedChunks>
144
- </component>
145
- <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
146
- <component name="SpringUtil" SPRING_PRE_LOADER_OPTION="true" RAKE_SPRING_PRE_LOADER_OPTION="true" RAILS_SPRING_PRE_LOADER_OPTION="true" />
147
- <component name="TaskManager">
148
- <task active="true" id="Default" summary="Default task">
149
- <changelist id="edf498b0-8552-42f1-846d-0c79d29ff991" name="Changes" comment="" />
150
- <created>1708600824931</created>
151
- <option name="number" value="Default" />
152
- <option name="presentableId" value="Default" />
153
- <updated>1708600824931</updated>
154
- <workItem from="1708600827139" duration="10157000" />
155
- <workItem from="1708784534887" duration="682000" />
156
- <workItem from="1708944130416" duration="99000" />
157
- <workItem from="1708944249622" duration="627000" />
158
- <workItem from="1708949675518" duration="80000" />
159
- <workItem from="1708951183744" duration="310000" />
160
- <workItem from="1770972800781" duration="13916000" />
161
- </task>
162
- <servers />
163
- </component>
164
- <component name="TypeScriptGeneratedFilesManager">
165
- <option name="version" value="3" />
166
- </component>
167
- <component name="com.intellij.coverage.CoverageDataManagerImpl">
168
- <SUITE FILE_PATH="coverage/telegram_support_bot@Unnamed.rcov" NAME="Unnamed Coverage Results" MODIFIED="1708610177774" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="rcov" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" MODULE_NAME="telegram_support_bot" />
169
- </component>
170
- </project>