telegram-support-bot 0.1.06 → 0.1.08
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 +4 -4
- data/.idea/workspace.xml +26 -24
- data/CHANGELOG.md +36 -0
- data/README.md +104 -1
- data/lib/telegram_support_bot/adapter_factory.rb +12 -3
- data/lib/telegram_support_bot/adapters/base.rb +4 -0
- data/lib/telegram_support_bot/adapters/telegram_bot.rb +9 -0
- data/lib/telegram_support_bot/adapters/telegram_bot_ruby.rb +4 -0
- data/lib/telegram_support_bot/configuration.rb +16 -1
- data/lib/telegram_support_bot/state_store.rb +57 -0
- data/lib/telegram_support_bot/state_stores/memory.rb +127 -0
- data/lib/telegram_support_bot/state_stores/redis.rb +202 -0
- data/lib/telegram_support_bot/version.rb +1 -1
- data/lib/telegram_support_bot.rb +383 -44
- data/script/dev_poll.rb +109 -0
- metadata +26 -8
- data/telegram_support_bot.gemspec +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d522d11e8602df8c2e6ab13df25de60eb2a376bb4983299092b277ad8ca81c15
|
|
4
|
+
data.tar.gz: 99739fa0654123c2b2052f9cdd09c78e2336c7cf1bfc4ac45983486e69a47c40
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 799182e98806ac1d0e550a89f37f81d867879b00221bf919f1c3665f20d3bb3a6e0c092dad0c1717d1dce3bc0aa10869be56589b7478e967c58a9dea40885018
|
|
7
|
+
data.tar.gz: f39e7efa2db2221af245b8641477a03936aa0ef285bcaf40cc21d3de79ece871fbc79c03321aef58602afbf701133b42b310279968f8cb6d07a0768a4de80388
|
data/.idea/workspace.xml
CHANGED
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
<component name="ChangeListManager">
|
|
7
7
|
<list default="true" id="edf498b0-8552-42f1-846d-0c79d29ff991" name="Changes" comment="">
|
|
8
8
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
|
9
|
-
<change beforePath="$PROJECT_DIR$/
|
|
10
|
-
<change beforePath="$PROJECT_DIR$/lib/telegram_support_bot/adapters/telegram_bot_adapter.rb" beforeDir="false" afterPath="$PROJECT_DIR$/lib/telegram_support_bot/adapters/telegram_bot_adapter.rb" afterDir="false" />
|
|
11
|
-
<change beforePath="$PROJECT_DIR$/spec/telegram_support_bot/adapters/telegram_bot_adapter_spec.rb" beforeDir="false" afterPath="$PROJECT_DIR$/spec/telegram_support_bot/adapters/telegram_bot_adapter_spec.rb" afterDir="false" />
|
|
9
|
+
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
|
12
10
|
</list>
|
|
13
11
|
<option name="SHOW_DIALOG" value="false" />
|
|
14
12
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
@@ -39,30 +37,30 @@
|
|
|
39
37
|
<option name="hideEmptyMiddlePackages" value="true" />
|
|
40
38
|
<option name="showLibraryContents" value="true" />
|
|
41
39
|
</component>
|
|
42
|
-
<component name="PropertiesComponent"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
<component name="PropertiesComponent">{
|
|
41
|
+
"keyToString": {
|
|
42
|
+
"DefaultRubyCreateTestTemplate": "Minitest Spec",
|
|
43
|
+
"RSpec.Unnamed.executor": "Run",
|
|
44
|
+
"Ruby.scratch_61.executor": "Run",
|
|
45
|
+
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
|
46
|
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
47
|
+
"git-widget-placeholder": "main",
|
|
48
|
+
"last_opened_file_path": "/home/max/code/tg-sample",
|
|
49
|
+
"node.js.detected.package.eslint": "true",
|
|
50
|
+
"node.js.detected.package.tslint": "true",
|
|
51
|
+
"node.js.selected.package.eslint": "(autodetect)",
|
|
52
|
+
"node.js.selected.package.tslint": "(autodetect)",
|
|
53
|
+
"nodejs_package_manager_path": "npm",
|
|
54
|
+
"ruby.structure.view.model.defaults.configured": "true",
|
|
55
|
+
"settings.editor.selected.configurable": "org.jetbrains.plugins.ruby.settings.RubyActiveModuleSdkConfigurable",
|
|
56
|
+
"vue.rearranger.settings.migration": "true"
|
|
59
57
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
"keyToStringList": {
|
|
59
|
+
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
|
60
|
+
"ruby"
|
|
63
61
|
]
|
|
64
62
|
}
|
|
65
|
-
}
|
|
63
|
+
}</component>
|
|
66
64
|
<component name="RecentsManager">
|
|
67
65
|
<key name="CopyFile.RECENT_KEYS">
|
|
68
66
|
<recent name="$PROJECT_DIR$/spec/telegram_support_bot/adapters" />
|
|
@@ -139,6 +137,10 @@
|
|
|
139
137
|
<updated>1708600824931</updated>
|
|
140
138
|
<workItem from="1708600827139" duration="10157000" />
|
|
141
139
|
<workItem from="1708784534887" duration="682000" />
|
|
140
|
+
<workItem from="1708944130416" duration="99000" />
|
|
141
|
+
<workItem from="1708944249622" duration="627000" />
|
|
142
|
+
<workItem from="1708949675518" duration="80000" />
|
|
143
|
+
<workItem from="1708951183744" duration="310000" />
|
|
142
144
|
</task>
|
|
143
145
|
<servers />
|
|
144
146
|
</component>
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.1.08] - 2026-02-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Optional one-time contact sharing flow for user identification:
|
|
9
|
+
`request_contact_on_start`, `require_contact_for_support`, custom contact messages, and
|
|
10
|
+
`on_contact_received` callback.
|
|
11
|
+
- In-memory user profile storage and lookup via `TelegramSupportBot.user_profile(chat_id)`.
|
|
12
|
+
- Configurable state-store backend with Redis support for multi-pod deployments
|
|
13
|
+
(`state_store`, `state_store_options`, and state TTL settings).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Support reply routing now uses internal message mapping first, with `forward_from` as fallback.
|
|
17
|
+
This removes the dependency on user forwarding privacy settings for normal reply flows.
|
|
18
|
+
- Message processing no longer relies on shared `@message_chat_id`, reducing thread-safety risks.
|
|
19
|
+
|
|
20
|
+
## [0.1.07] - 2026-02-13
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Mirroring of `message_reaction` updates between support chat and user chats.
|
|
24
|
+
- Message mapping storage (`message_map` and `reverse_message_map`) to correlate forwarded/replied messages for reaction sync.
|
|
25
|
+
- Adapter API method `set_message_reaction` with implementations for both supported adapters.
|
|
26
|
+
- Test coverage for reaction handling and adapter reaction calls.
|
|
27
|
+
- Local polling helper script for development testing without Rails (`script/dev_poll.rb`).
|
|
28
|
+
- README documentation for local development testing workflow.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- Local polling script now uses the proper polling call for `telegram-bot` gem (`get_updates`) and supports multiple client styles.
|
|
32
|
+
- Reaction mapping now correctly handles wrapped Telegram API responses (`{ "ok": true, "result": ... }`).
|
|
33
|
+
- Reaction mirroring now gracefully handles `REACTIONS_TOO_MANY` by retrying with a single reaction instead of crashing.
|
|
34
|
+
- Reaction mapping lookup now tolerates chat/message ID type mismatches (string vs integer).
|
|
35
|
+
- Added optional reaction-flow debug logs via `TSB_DEBUG=1`.
|
|
36
|
+
- Support-chat reaction mirroring now also handles `message_reaction_count` updates (anonymous reaction counts).
|
data/README.md
CHANGED
|
@@ -43,6 +43,15 @@ TelegramSupportBot.configure do |config|
|
|
|
43
43
|
config.adapter_options = { token: 'YOUR_TELEGRAM_BOT_TOKEN' }
|
|
44
44
|
config.support_chat_id = 'YOUR_SUPPORT_CHAT_ID'
|
|
45
45
|
config.welcome_message = 'Hi! How can we help you?'
|
|
46
|
+
# Optional: ask users to share their phone once for account lookup.
|
|
47
|
+
config.request_contact_on_start = true
|
|
48
|
+
# Optional: block forwarding until contact is shared.
|
|
49
|
+
config.require_contact_for_support = false
|
|
50
|
+
# Optional callback to persist/lookup user profile in your app.
|
|
51
|
+
config.on_contact_received = ->(profile) { YourUserMatcher.sync_from_telegram(profile) }
|
|
52
|
+
# Recommended in Kubernetes/multi-pod setup:
|
|
53
|
+
# config.state_store = :redis
|
|
54
|
+
# config.state_store_options = { url: ENV.fetch('REDIS_URL'), namespace: 'telegram_support_bot' }
|
|
46
55
|
end
|
|
47
56
|
```
|
|
48
57
|
|
|
@@ -115,13 +124,107 @@ end
|
|
|
115
124
|
Implement custom adapters by inheriting from `TelegramSupportBot::Adapter::Base` and defining
|
|
116
125
|
message sending and forwarding methods.
|
|
117
126
|
|
|
127
|
+
## User Identification With Phone Sharing
|
|
128
|
+
|
|
129
|
+
If you want support agents to identify users quickly in your CRM, you can request phone sharing once:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
TelegramSupportBot.configure do |config|
|
|
133
|
+
config.request_contact_on_start = true
|
|
134
|
+
config.require_contact_for_support = false
|
|
135
|
+
config.contact_request_message = 'Please share your phone number so we can identify your account.'
|
|
136
|
+
config.contact_received_message = 'Thanks! We have saved your phone number.'
|
|
137
|
+
config.on_contact_received = ->(profile) do
|
|
138
|
+
# profile keys:
|
|
139
|
+
# :chat_id, :user_id, :phone_number, :first_name, :last_name, :username, :language_code
|
|
140
|
+
UserIdentitySync.call(profile)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If you set `require_contact_for_support = true`, the bot will ask for contact and will not forward
|
|
146
|
+
other user messages until contact is shared.
|
|
147
|
+
|
|
148
|
+
Support replies are routed by internal message mapping, so users do not need to change Telegram
|
|
149
|
+
forwarding privacy settings to receive replies.
|
|
150
|
+
|
|
151
|
+
## State Storage (Single Pod vs Multi-Pod)
|
|
152
|
+
|
|
153
|
+
By default, runtime state is stored in-memory (`state_store = :memory`). This is fine for local
|
|
154
|
+
development or a single process.
|
|
155
|
+
|
|
156
|
+
For Kubernetes / multiple pods, configure Redis so message mappings, reaction state, and user
|
|
157
|
+
profiles are shared:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
TelegramSupportBot.configure do |config|
|
|
161
|
+
config.state_store = :redis
|
|
162
|
+
config.state_store_options = {
|
|
163
|
+
url: ENV.fetch('REDIS_URL'),
|
|
164
|
+
namespace: 'telegram_support_bot'
|
|
165
|
+
}
|
|
166
|
+
# Optional TTL tuning:
|
|
167
|
+
# config.mapping_ttl_seconds = 30 * 24 * 60 * 60
|
|
168
|
+
# config.reaction_count_ttl_seconds = 7 * 24 * 60 * 60
|
|
169
|
+
# config.user_profile_ttl_seconds = nil
|
|
170
|
+
end
|
|
171
|
+
```
|
|
118
172
|
|
|
119
173
|
## Development
|
|
120
174
|
|
|
121
175
|
- Run `bin/setup` to install dependencies.
|
|
122
176
|
- Use `rake spec` for tests and `bin/console` for an interactive prompt.
|
|
123
177
|
- To install locally, use `bundle exec rake install`.
|
|
124
|
-
- For releases, update `version.rb
|
|
178
|
+
- For releases, update `lib/telegram_support_bot/version.rb` and `CHANGELOG.md`, then run `bundle exec rake release`.
|
|
179
|
+
|
|
180
|
+
### Local Testing Without Rails (Polling)
|
|
181
|
+
|
|
182
|
+
You can run the bot locally without a Rails app by using the included script:
|
|
183
|
+
|
|
184
|
+
Prerequisites:
|
|
185
|
+
- Add the bot as an **administrator** in the support chat if you want support-side reactions to be delivered as updates.
|
|
186
|
+
- Bots can set only one reaction per message via Bot API, so only one mirrored reaction is applied when multiple are present.
|
|
187
|
+
|
|
188
|
+
1. Export required environment variables:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
export TELEGRAM_BOT_TOKEN=your_bot_token
|
|
192
|
+
export SUPPORT_CHAT_ID=your_support_chat_id
|
|
193
|
+
# optional; defaults to telegram_bot
|
|
194
|
+
export TSB_ADAPTER=telegram_bot
|
|
195
|
+
# optional; used by telegram_bot adapter
|
|
196
|
+
export TELEGRAM_BOT_USERNAME=your_bot_username
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
2. Disable webhook mode for that bot token (polling and webhooks cannot be used together):
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
curl -s "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/deleteWebhook" > /dev/null
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
3. Start the local poller:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
bundle exec ruby script/dev_poll.rb
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
4. In Telegram, verify:
|
|
212
|
+
- user message is forwarded to support chat
|
|
213
|
+
- support reply is sent back to user
|
|
214
|
+
- reactions are mirrored in both directions
|
|
215
|
+
|
|
216
|
+
If you want to test with `telegram_bot_ruby` adapter, set `TSB_ADAPTER=telegram_bot_ruby` and add
|
|
217
|
+
the `telegram-bot-ruby` gem in your environment.
|
|
218
|
+
|
|
219
|
+
### Switch Back To Webhook Mode
|
|
220
|
+
|
|
221
|
+
After polling tests, set your webhook again:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
|
|
225
|
+
-H "Content-Type: application/json" \
|
|
226
|
+
-d '{"url":"https://YOUR_PUBLIC_HOST/telegram/webhook","allowed_updates":["message","message_reaction","message_reaction_count","my_chat_member"]}'
|
|
227
|
+
```
|
|
125
228
|
|
|
126
229
|
## Contributing
|
|
127
230
|
|
|
@@ -8,15 +8,24 @@ module TelegramSupportBot
|
|
|
8
8
|
}.freeze
|
|
9
9
|
|
|
10
10
|
def self.build(adapter_specification, adapter_options = {})
|
|
11
|
+
adapter_options ||= {}
|
|
11
12
|
case adapter_specification
|
|
12
13
|
when Symbol
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
class_name = ADAPTERS[adapter_specification]
|
|
15
|
+
raise ArgumentError, "Unsupported adapter specification: #{adapter_specification}" unless class_name
|
|
16
|
+
|
|
17
|
+
adapter_class = constantize(class_name)
|
|
18
|
+
adapter_class.new(**adapter_options)
|
|
15
19
|
when Class
|
|
16
|
-
adapter_specification.new(adapter_options)
|
|
20
|
+
adapter_specification.new(**adapter_options)
|
|
17
21
|
else
|
|
18
22
|
raise ArgumentError, "Unsupported adapter specification: #{adapter_specification}"
|
|
19
23
|
end
|
|
20
24
|
end
|
|
25
|
+
|
|
26
|
+
def self.constantize(class_name)
|
|
27
|
+
class_name.split('::').reject(&:empty?).inject(Object) { |namespace, constant| namespace.const_get(constant) }
|
|
28
|
+
end
|
|
29
|
+
private_class_method :constantize
|
|
21
30
|
end
|
|
22
31
|
end
|
|
@@ -35,6 +35,10 @@ module TelegramSupportBot
|
|
|
35
35
|
# forward messages to the support chat
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def set_message_reaction(chat_id:, message_id:, reaction:, **options)
|
|
39
|
+
# set reaction to a message
|
|
40
|
+
end
|
|
41
|
+
|
|
38
42
|
def on_message(&block)
|
|
39
43
|
# Implementation to register a block to be called on new messages
|
|
40
44
|
end
|
|
@@ -50,6 +50,15 @@ module TelegramSupportBot
|
|
|
50
50
|
message_id: message_id
|
|
51
51
|
)
|
|
52
52
|
end
|
|
53
|
+
|
|
54
|
+
def set_message_reaction(chat_id:, message_id:, reaction:, **options)
|
|
55
|
+
@bot.set_message_reaction(
|
|
56
|
+
chat_id: chat_id,
|
|
57
|
+
message_id: message_id,
|
|
58
|
+
reaction: reaction,
|
|
59
|
+
**options
|
|
60
|
+
)
|
|
61
|
+
end
|
|
53
62
|
end
|
|
54
63
|
end
|
|
55
64
|
end
|
|
@@ -43,6 +43,10 @@ module TelegramSupportBot
|
|
|
43
43
|
def forward_message(from_chat_id:, chat_id:, message_id:)
|
|
44
44
|
bot.api.forward_message(chat_id: chat_id, from_chat_id: from_chat_id, message_id: message_id)
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
def set_message_reaction(chat_id:, message_id:, reaction:, **options)
|
|
48
|
+
bot.api.set_message_reaction(chat_id: chat_id, message_id: message_id, reaction: reaction, **options)
|
|
49
|
+
end
|
|
46
50
|
end
|
|
47
51
|
end
|
|
48
52
|
end
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
module TelegramSupportBot
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :adapter, :adapter_options, :support_chat_id, :welcome_message,
|
|
6
|
-
:auto_away_message, :auto_away_interval, :ignore_unknown_commands
|
|
6
|
+
:auto_away_message, :auto_away_interval, :ignore_unknown_commands,
|
|
7
|
+
:request_contact_on_start, :require_contact_for_support, :contact_request_message,
|
|
8
|
+
:contact_received_message, :contact_invalid_message, :on_contact_received,
|
|
9
|
+
:state_store, :state_store_options, :mapping_ttl_seconds,
|
|
10
|
+
:reaction_count_ttl_seconds, :user_profile_ttl_seconds
|
|
7
11
|
|
|
8
12
|
def initialize
|
|
9
13
|
@adapter = :telegram_bot
|
|
@@ -12,6 +16,17 @@ module TelegramSupportBot
|
|
|
12
16
|
@ignore_unknown_commands = true
|
|
13
17
|
@auto_away_interval = 10 # seconds
|
|
14
18
|
@auto_away_message = 'We are sorry, all operators are busy at the moment. Please wait'
|
|
19
|
+
@request_contact_on_start = false
|
|
20
|
+
@require_contact_for_support = false
|
|
21
|
+
@contact_request_message = 'Please share your phone number so we can quickly identify your account.'
|
|
22
|
+
@contact_received_message = 'Thanks! We have saved your phone number.'
|
|
23
|
+
@contact_invalid_message = 'Please use the button below to share your own phone number.'
|
|
24
|
+
@on_contact_received = nil
|
|
25
|
+
@state_store = :memory
|
|
26
|
+
@state_store_options = {}
|
|
27
|
+
@mapping_ttl_seconds = 30 * 24 * 60 * 60
|
|
28
|
+
@reaction_count_ttl_seconds = 7 * 24 * 60 * 60
|
|
29
|
+
@user_profile_ttl_seconds = nil
|
|
15
30
|
end
|
|
16
31
|
end
|
|
17
32
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TelegramSupportBot
|
|
4
|
+
module StateStore
|
|
5
|
+
class MapProxy
|
|
6
|
+
def initialize(get_proc:, set_proc:, clear_proc:, size_proc:)
|
|
7
|
+
@get_proc = get_proc
|
|
8
|
+
@set_proc = set_proc
|
|
9
|
+
@clear_proc = clear_proc
|
|
10
|
+
@size_proc = size_proc
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def [](key)
|
|
14
|
+
@get_proc.call(key)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def []=(key, value)
|
|
18
|
+
@set_proc.call(key, value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clear
|
|
22
|
+
@clear_proc.call
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def size
|
|
26
|
+
@size_proc.call
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.build(configuration)
|
|
31
|
+
backend = configuration.state_store.to_sym
|
|
32
|
+
options = configuration.state_store_options || {}
|
|
33
|
+
|
|
34
|
+
case backend
|
|
35
|
+
when :memory
|
|
36
|
+
StateStores::Memory.new(
|
|
37
|
+
mapping_ttl_seconds: configuration.mapping_ttl_seconds,
|
|
38
|
+
reaction_count_ttl_seconds: configuration.reaction_count_ttl_seconds,
|
|
39
|
+
user_profile_ttl_seconds: configuration.user_profile_ttl_seconds,
|
|
40
|
+
**options
|
|
41
|
+
)
|
|
42
|
+
when :redis
|
|
43
|
+
require_relative 'state_stores/redis'
|
|
44
|
+
StateStores::Redis.new(
|
|
45
|
+
mapping_ttl_seconds: configuration.mapping_ttl_seconds,
|
|
46
|
+
reaction_count_ttl_seconds: configuration.reaction_count_ttl_seconds,
|
|
47
|
+
user_profile_ttl_seconds: configuration.user_profile_ttl_seconds,
|
|
48
|
+
**options
|
|
49
|
+
)
|
|
50
|
+
else
|
|
51
|
+
raise ArgumentError, "Unsupported state store backend: #{backend}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
require_relative 'state_stores/memory'
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'monitor'
|
|
4
|
+
|
|
5
|
+
module TelegramSupportBot
|
|
6
|
+
module StateStores
|
|
7
|
+
class Memory
|
|
8
|
+
def initialize(**_options)
|
|
9
|
+
@monitor = Monitor.new
|
|
10
|
+
@message_map = {}
|
|
11
|
+
@reverse_message_map = {}
|
|
12
|
+
@reaction_count_state = {}
|
|
13
|
+
@user_profiles = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def message_map
|
|
17
|
+
@message_map_proxy ||= StateStore::MapProxy.new(
|
|
18
|
+
get_proc: ->(key) { get_message_mapping(key) },
|
|
19
|
+
set_proc: ->(key, value) { set_message_mapping(key, value) },
|
|
20
|
+
clear_proc: -> { clear_message_mappings },
|
|
21
|
+
size_proc: -> { message_mappings_size }
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reverse_message_map
|
|
26
|
+
@reverse_map_proxy ||= StateStore::MapProxy.new(
|
|
27
|
+
get_proc: ->(key) { get_reverse_mapping(key) },
|
|
28
|
+
set_proc: ->(key, value) { set_reverse_mapping(key, value) },
|
|
29
|
+
clear_proc: -> { clear_reverse_mappings },
|
|
30
|
+
size_proc: -> { reverse_mappings_size }
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reaction_count_state
|
|
35
|
+
@reaction_state_proxy ||= StateStore::MapProxy.new(
|
|
36
|
+
get_proc: ->(key) { get_reaction_count(key) },
|
|
37
|
+
set_proc: ->(key, value) { set_reaction_count(key, value) },
|
|
38
|
+
clear_proc: -> { clear_reaction_counts },
|
|
39
|
+
size_proc: -> { reaction_counts_size }
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def user_profiles
|
|
44
|
+
@user_profiles_proxy ||= StateStore::MapProxy.new(
|
|
45
|
+
get_proc: ->(key) { get_user_profile(key) },
|
|
46
|
+
set_proc: ->(key, value) { set_user_profile(key, value) },
|
|
47
|
+
clear_proc: -> { clear_user_profiles },
|
|
48
|
+
size_proc: -> { user_profiles_size }
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_message_mapping(key)
|
|
53
|
+
synchronize { @message_map[normalize_key(key)] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set_message_mapping(key, value)
|
|
57
|
+
synchronize { @message_map[normalize_key(key)] = value }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def clear_message_mappings
|
|
61
|
+
synchronize { @message_map.clear }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def message_mappings_size
|
|
65
|
+
synchronize { @message_map.size }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_reverse_mapping(key)
|
|
69
|
+
synchronize { @reverse_message_map[normalize_key(key)] }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def set_reverse_mapping(key, value)
|
|
73
|
+
synchronize { @reverse_message_map[normalize_key(key)] = value }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clear_reverse_mappings
|
|
77
|
+
synchronize { @reverse_message_map.clear }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reverse_mappings_size
|
|
81
|
+
synchronize { @reverse_message_map.size }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def get_reaction_count(key)
|
|
85
|
+
synchronize { @reaction_count_state[normalize_key(key)] }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def set_reaction_count(key, value)
|
|
89
|
+
synchronize { @reaction_count_state[normalize_key(key)] = value }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def clear_reaction_counts
|
|
93
|
+
synchronize { @reaction_count_state.clear }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def reaction_counts_size
|
|
97
|
+
synchronize { @reaction_count_state.size }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def get_user_profile(key)
|
|
101
|
+
synchronize { @user_profiles[normalize_key(key)] }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def set_user_profile(key, value)
|
|
105
|
+
synchronize { @user_profiles[normalize_key(key)] = value }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def clear_user_profiles
|
|
109
|
+
synchronize { @user_profiles.clear }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def user_profiles_size
|
|
113
|
+
synchronize { @user_profiles.size }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def synchronize(&block)
|
|
119
|
+
@monitor.synchronize(&block)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def normalize_key(key)
|
|
123
|
+
key.to_s
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|