flow_chat 0.8.2 → 0.9.0
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/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +11 -11
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/ussd_controller.rb +9 -9
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +10 -30
- data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +81 -33
- data/lib/flow_chat/http/renderer.rb +3 -3
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +23 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/middleware.rb +25 -9
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +84 -25
- data/docs/flows.md +0 -320
- data/docs/http-gateway-protocol.md +0 -432
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -146
- data/lib/flow_chat/http/app.rb +0 -6
- data/lib/flow_chat/http/middleware/executor.rb +0 -24
- data/lib/flow_chat/http/processor.rb +0 -33
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
# Telegram Development Guide
|
|
2
|
+
|
|
3
|
+
FlowChat provides comprehensive Telegram Bot API integration with support for inline keyboards, rich media, secure webhook validation, and flexible deployment modes.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Inline Keyboards**: Interactive buttons with callback data
|
|
8
|
+
- **Rich Media Support**: Photos, documents, videos, audio, and voice messages
|
|
9
|
+
- **Secure Webhooks**: Secret token validation via X-Telegram-Bot-Api-Secret-Token
|
|
10
|
+
- **Multiple Processing Modes**: Inline, background, and async
|
|
11
|
+
- **Callback Query Handling**: Automatic acknowledgment and processing
|
|
12
|
+
- **Multi-Tenant Support**: Named configurations for different bots
|
|
13
|
+
- **Location and Contact Sharing**: Handle user-shared locations and contacts
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Create Controller
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# app/controllers/telegram_controller.rb
|
|
21
|
+
class TelegramController < ApplicationController
|
|
22
|
+
skip_forgery_protection
|
|
23
|
+
|
|
24
|
+
def webhook
|
|
25
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
26
|
+
config.use_gateway FlowChat::Telegram::Gateway::BotApi
|
|
27
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
processor.run WelcomeFlow, :main_page
|
|
31
|
+
rescue => e
|
|
32
|
+
Rails.logger.error { "Telegram webhook error: #{e.message}" }
|
|
33
|
+
head :internal_server_error
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Add Routes
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# config/routes.rb
|
|
42
|
+
post '/telegram/webhook', to: 'telegram#webhook'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Create a Flow
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# app/flow_chat/welcome_flow.rb
|
|
49
|
+
class WelcomeFlow < FlowChat::Flow
|
|
50
|
+
def main_page
|
|
51
|
+
name = app.screen(:name) do |prompt|
|
|
52
|
+
prompt.ask "Hello! What's your name?",
|
|
53
|
+
transform: ->(input) { input.strip.titleize }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
choice = app.screen(:main_menu) do |prompt|
|
|
57
|
+
prompt.select "Hi #{name}! How can I help?", {
|
|
58
|
+
"info" => "Get Information",
|
|
59
|
+
"support" => "Contact Support",
|
|
60
|
+
"feedback" => "Give Feedback"
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
case choice
|
|
65
|
+
when "info"
|
|
66
|
+
show_info
|
|
67
|
+
when "support"
|
|
68
|
+
contact_support
|
|
69
|
+
when "feedback"
|
|
70
|
+
collect_feedback
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def show_info
|
|
77
|
+
app.say "Located at 123 Main Street\nHours: Mon-Fri 9AM-6PM"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def contact_support
|
|
81
|
+
app.say "Call us at (555) 123-4567\nEmail: support@example.com"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def collect_feedback
|
|
85
|
+
rating = app.screen(:rating) do |prompt|
|
|
86
|
+
prompt.select "Rate our service:", {
|
|
87
|
+
"1" => "1 Star",
|
|
88
|
+
"2" => "2 Stars",
|
|
89
|
+
"3" => "3 Stars",
|
|
90
|
+
"4" => "4 Stars",
|
|
91
|
+
"5" => "5 Stars"
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
app.say "Thank you for your #{rating}-star rating!"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
### Option 1: Rails Credentials (Recommended)
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
rails credentials:edit
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
telegram:
|
|
110
|
+
bot_token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
111
|
+
secret_token: "your_webhook_secret_token"
|
|
112
|
+
skip_signature_validation: false # Set to true only for development
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Option 2: Environment Variables
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
export TELEGRAM_BOT_TOKEN="123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
119
|
+
export TELEGRAM_SECRET_TOKEN="your_webhook_secret_token"
|
|
120
|
+
export TELEGRAM_SKIP_SIGNATURE_VALIDATION="false"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Option 3: Programmatic Configuration
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
telegram_config = FlowChat::Telegram::Configuration.new(:my_bot)
|
|
127
|
+
telegram_config.bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
128
|
+
telegram_config.secret_token = "your_webhook_secret_token"
|
|
129
|
+
telegram_config.skip_signature_validation = false
|
|
130
|
+
|
|
131
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
132
|
+
config.use_gateway FlowChat::Telegram::Gateway::BotApi, telegram_config
|
|
133
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Configuration Options
|
|
138
|
+
|
|
139
|
+
| Option | Description | Required |
|
|
140
|
+
|--------|-------------|----------|
|
|
141
|
+
| `bot_token` | Your Telegram bot token from @BotFather | Yes |
|
|
142
|
+
| `secret_token` | Secret token for webhook validation | No (recommended) |
|
|
143
|
+
| `skip_signature_validation` | Disable webhook signature checks | No (default: false) |
|
|
144
|
+
|
|
145
|
+
### Configuration Helper Methods
|
|
146
|
+
|
|
147
|
+
The configuration object provides several helper methods:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
151
|
+
|
|
152
|
+
# Check if configuration is valid
|
|
153
|
+
config.valid? # => true if bot_token is present
|
|
154
|
+
|
|
155
|
+
# Get API base URL
|
|
156
|
+
config.api_base_url # => "https://api.telegram.org/bot123456789:ABC..."
|
|
157
|
+
|
|
158
|
+
# Get bot ID from token
|
|
159
|
+
config.bot_id # => "123456789"
|
|
160
|
+
|
|
161
|
+
# API endpoint URLs
|
|
162
|
+
config.send_message_url
|
|
163
|
+
config.set_webhook_url
|
|
164
|
+
config.get_webhook_info_url
|
|
165
|
+
config.delete_webhook_url
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Webhook Setup
|
|
169
|
+
|
|
170
|
+
Before your bot can receive messages, you must register your webhook URL with Telegram.
|
|
171
|
+
|
|
172
|
+
### Using the Client
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
# In a Rails console or initializer
|
|
176
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
177
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
178
|
+
|
|
179
|
+
# Set webhook with secret token for validation
|
|
180
|
+
result = client.set_webhook(
|
|
181
|
+
"https://yourdomain.com/telegram/webhook",
|
|
182
|
+
secret_token: config.secret_token,
|
|
183
|
+
allowed_updates: ["message", "callback_query"]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if result["ok"]
|
|
187
|
+
puts "Webhook set successfully!"
|
|
188
|
+
else
|
|
189
|
+
puts "Error: #{result["description"]}"
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Using a Rake Task
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
# lib/tasks/telegram.rake
|
|
197
|
+
namespace :telegram do
|
|
198
|
+
desc "Set up Telegram webhook"
|
|
199
|
+
task setup_webhook: :environment do
|
|
200
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
201
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
202
|
+
|
|
203
|
+
webhook_url = ENV["TELEGRAM_WEBHOOK_URL"] || "https://yourdomain.com/telegram/webhook"
|
|
204
|
+
|
|
205
|
+
result = client.set_webhook(
|
|
206
|
+
webhook_url,
|
|
207
|
+
secret_token: config.secret_token
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if result["ok"]
|
|
211
|
+
puts "Webhook configured: #{webhook_url}"
|
|
212
|
+
else
|
|
213
|
+
puts "Failed to set webhook: #{result["description"]}"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
desc "Get webhook info"
|
|
218
|
+
task webhook_info: :environment do
|
|
219
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
220
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
221
|
+
|
|
222
|
+
info = client.get_webhook_info
|
|
223
|
+
puts JSON.pretty_generate(info)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
desc "Delete webhook"
|
|
227
|
+
task delete_webhook: :environment do
|
|
228
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
229
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
230
|
+
|
|
231
|
+
result = client.delete_webhook
|
|
232
|
+
puts result["ok"] ? "Webhook deleted" : "Error: #{result["description"]}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Webhook Requirements
|
|
238
|
+
|
|
239
|
+
- **HTTPS**: Telegram requires a valid SSL certificate
|
|
240
|
+
- **Port**: Must be on port 443, 80, 88, or 8443
|
|
241
|
+
- **Response Time**: Respond within 60 seconds (use background processing for slow flows)
|
|
242
|
+
|
|
243
|
+
## Inline Keyboards
|
|
244
|
+
|
|
245
|
+
FlowChat automatically renders choices as inline keyboards:
|
|
246
|
+
|
|
247
|
+
### Basic Keyboard
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
def main_menu
|
|
251
|
+
choice = app.screen(:menu) do |prompt|
|
|
252
|
+
prompt.select "What would you like to do?", {
|
|
253
|
+
"balance" => "Check Balance",
|
|
254
|
+
"transfer" => "Transfer Money",
|
|
255
|
+
"history" => "View History"
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
handle_choice(choice)
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Keyboard Layout
|
|
264
|
+
|
|
265
|
+
The renderer automatically arranges buttons based on the number of choices:
|
|
266
|
+
|
|
267
|
+
- **1-4 choices**: 2 buttons per row
|
|
268
|
+
- **5+ choices**: 1 button per row
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
# 4 choices = 2x2 grid
|
|
272
|
+
prompt.select "Pick one:", {
|
|
273
|
+
"a" => "Option A",
|
|
274
|
+
"b" => "Option B",
|
|
275
|
+
"c" => "Option C",
|
|
276
|
+
"d" => "Option D"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# 5+ choices = vertical list
|
|
280
|
+
prompt.select "Select category:", {
|
|
281
|
+
"electronics" => "Electronics",
|
|
282
|
+
"clothing" => "Clothing",
|
|
283
|
+
"books" => "Books",
|
|
284
|
+
"home" => "Home & Garden",
|
|
285
|
+
"sports" => "Sports & Outdoors"
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Button Limits
|
|
290
|
+
|
|
291
|
+
- **Button text**: Maximum 64 characters (auto-truncated with "...")
|
|
292
|
+
- **Callback data**: Maximum 64 characters per button
|
|
293
|
+
|
|
294
|
+
## Rich Media Support
|
|
295
|
+
|
|
296
|
+
### Sending Media in Flows
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
def product_showcase
|
|
300
|
+
# Photo with prompt
|
|
301
|
+
feedback = app.screen(:product_feedback) do |prompt|
|
|
302
|
+
prompt.ask "What do you think of this product?",
|
|
303
|
+
media: {
|
|
304
|
+
type: :photo,
|
|
305
|
+
url: "https://example.com/product.jpg"
|
|
306
|
+
}
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Document response
|
|
310
|
+
app.say "Thanks! Here's the product catalog:",
|
|
311
|
+
media: {
|
|
312
|
+
type: :document,
|
|
313
|
+
url: "https://example.com/catalog.pdf",
|
|
314
|
+
filename: "product_catalog.pdf"
|
|
315
|
+
}
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def video_tutorial
|
|
319
|
+
app.say "Watch this tutorial:",
|
|
320
|
+
media: {
|
|
321
|
+
type: :video,
|
|
322
|
+
url: "https://example.com/tutorial.mp4"
|
|
323
|
+
}
|
|
324
|
+
end
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Media Types Supported
|
|
328
|
+
|
|
329
|
+
| Type | Usage | Options |
|
|
330
|
+
|------|-------|---------|
|
|
331
|
+
| `:photo` | Photos, images | `url` or `file_id` |
|
|
332
|
+
| `:document` | PDFs, files | `url` or `file_id`, `filename` |
|
|
333
|
+
| `:video` | Video files | `url` or `file_id` |
|
|
334
|
+
| `:audio` | Audio files | `url` or `file_id` |
|
|
335
|
+
| `:voice` | Voice messages | `url` or `file_id` |
|
|
336
|
+
|
|
337
|
+
### Media with Inline Keyboard
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
def media_menu
|
|
341
|
+
choice = app.screen(:options) do |prompt|
|
|
342
|
+
prompt.select "Choose an option:",
|
|
343
|
+
{
|
|
344
|
+
"details" => "More Details",
|
|
345
|
+
"buy" => "Buy Now",
|
|
346
|
+
"share" => "Share"
|
|
347
|
+
},
|
|
348
|
+
media: {
|
|
349
|
+
type: :photo,
|
|
350
|
+
url: "https://example.com/product.jpg"
|
|
351
|
+
}
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Handling Incoming Media
|
|
357
|
+
|
|
358
|
+
```ruby
|
|
359
|
+
def handle_media_upload
|
|
360
|
+
media = context["request.media"]
|
|
361
|
+
|
|
362
|
+
if media
|
|
363
|
+
case media[:type]
|
|
364
|
+
when :photo
|
|
365
|
+
app.say "Thanks for the photo! Processing..."
|
|
366
|
+
# Access file_id: media[:file_id]
|
|
367
|
+
when :document
|
|
368
|
+
app.say "Document received: #{media[:file_name]}"
|
|
369
|
+
# Access mime_type: media[:mime_type]
|
|
370
|
+
when :voice
|
|
371
|
+
app.say "Got your voice message (#{media[:duration]}s)"
|
|
372
|
+
end
|
|
373
|
+
else
|
|
374
|
+
app.say "Please send a photo."
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Handling Location
|
|
380
|
+
|
|
381
|
+
```ruby
|
|
382
|
+
def handle_location
|
|
383
|
+
location = context["request.location"]
|
|
384
|
+
|
|
385
|
+
if location
|
|
386
|
+
lat = location["latitude"]
|
|
387
|
+
lon = location["longitude"]
|
|
388
|
+
app.say "You're at: #{lat}, #{lon}"
|
|
389
|
+
else
|
|
390
|
+
app.say "Please share your location."
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Handling Contact
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
def handle_contact
|
|
399
|
+
contact = context["request.contact"]
|
|
400
|
+
|
|
401
|
+
if contact
|
|
402
|
+
phone = contact[:phone_number]
|
|
403
|
+
name = contact[:first_name]
|
|
404
|
+
app.say "Thanks #{name}! We'll contact you at #{phone}."
|
|
405
|
+
else
|
|
406
|
+
app.say "Please share your contact."
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Telegram Client API
|
|
412
|
+
|
|
413
|
+
The `FlowChat::Telegram::Client` provides methods for sending messages outside of flows.
|
|
414
|
+
|
|
415
|
+
### Client Initialization
|
|
416
|
+
|
|
417
|
+
```ruby
|
|
418
|
+
# From credentials/environment variables
|
|
419
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
420
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
421
|
+
|
|
422
|
+
# Using named configuration
|
|
423
|
+
config = FlowChat::Telegram::Configuration.get(:my_bot)
|
|
424
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Text Messages
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
# Simple text message
|
|
431
|
+
client.send_text(chat_id, "Hello! How can I help you today?")
|
|
432
|
+
|
|
433
|
+
# With parse mode (default is HTML)
|
|
434
|
+
client.send_text(chat_id, "<b>Bold</b> and <i>italic</i>", parse_mode: "HTML")
|
|
435
|
+
client.send_text(chat_id, "*Bold* and _italic_", parse_mode: "Markdown")
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Messages with Inline Keyboard
|
|
439
|
+
|
|
440
|
+
```ruby
|
|
441
|
+
keyboard = [
|
|
442
|
+
[
|
|
443
|
+
{ text: "Option 1", callback_data: "opt1" },
|
|
444
|
+
{ text: "Option 2", callback_data: "opt2" }
|
|
445
|
+
],
|
|
446
|
+
[
|
|
447
|
+
{ text: "Option 3", callback_data: "opt3" }
|
|
448
|
+
]
|
|
449
|
+
]
|
|
450
|
+
|
|
451
|
+
client.send_text_with_keyboard(chat_id, "Choose an option:", keyboard)
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Media Messages
|
|
455
|
+
|
|
456
|
+
```ruby
|
|
457
|
+
# Send photo
|
|
458
|
+
client.send_photo(chat_id, "https://example.com/image.jpg", caption: "Check this out!")
|
|
459
|
+
|
|
460
|
+
# Send document
|
|
461
|
+
client.send_document(chat_id, "https://example.com/file.pdf", caption: "Your document")
|
|
462
|
+
|
|
463
|
+
# Send video
|
|
464
|
+
client.send_video(chat_id, "https://example.com/video.mp4", caption: "Watch this")
|
|
465
|
+
|
|
466
|
+
# Send audio
|
|
467
|
+
client.send_audio(chat_id, "https://example.com/audio.mp3", caption: "Listen to this")
|
|
468
|
+
|
|
469
|
+
# Send voice message
|
|
470
|
+
client.send_voice(chat_id, "https://example.com/voice.ogg")
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Photo with Inline Keyboard
|
|
474
|
+
|
|
475
|
+
```ruby
|
|
476
|
+
keyboard = [
|
|
477
|
+
[{ text: "Like", callback_data: "like" }, { text: "Share", callback_data: "share" }]
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
client.send_photo_with_keyboard(
|
|
481
|
+
chat_id,
|
|
482
|
+
"https://example.com/product.jpg",
|
|
483
|
+
caption: "New product!",
|
|
484
|
+
keyboard: keyboard
|
|
485
|
+
)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Message Management
|
|
489
|
+
|
|
490
|
+
```ruby
|
|
491
|
+
# Edit message text
|
|
492
|
+
client.edit_message_text(chat_id, message_id, "Updated text", keyboard: new_keyboard)
|
|
493
|
+
|
|
494
|
+
# Delete message
|
|
495
|
+
client.delete_message(chat_id, message_id)
|
|
496
|
+
|
|
497
|
+
# Answer callback query (acknowledge button press)
|
|
498
|
+
client.answer_callback_query(callback_query_id, text: "Processing...", show_alert: false)
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Chat Actions / Typing Indicator
|
|
502
|
+
|
|
503
|
+
Broadcast a chat action (e.g. "typing…") to a Telegram chat. The action lasts ~5 seconds or until the next outbound message; there is **no stop-typing call**.
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
507
|
+
|
|
508
|
+
# Show "typing…"
|
|
509
|
+
client.send_chat_action(chat_id)
|
|
510
|
+
|
|
511
|
+
# Convenience equivalent of the line above
|
|
512
|
+
client.indicate_typing(chat_id)
|
|
513
|
+
|
|
514
|
+
# Other actions (e.g. while uploading a generated image)
|
|
515
|
+
client.send_chat_action(chat_id, action: "upload_photo")
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Valid actions per the Telegram Bot API: `typing`, `upload_photo`, `record_video`, `upload_video`, `record_voice`, `upload_voice`, `upload_document`, `choose_sticker`, `find_location`, `record_video_note`, `upload_video_note`. FlowChat does not validate the action client-side; if Telegram rejects it the response will contain `"ok" => false` with the error description.
|
|
519
|
+
|
|
520
|
+
### Bot Information
|
|
521
|
+
|
|
522
|
+
```ruby
|
|
523
|
+
# Get bot info
|
|
524
|
+
bot_info = client.get_me
|
|
525
|
+
puts "Bot username: @#{bot_info["result"]["username"]}"
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Complete Service Example
|
|
529
|
+
|
|
530
|
+
```ruby
|
|
531
|
+
# Service for sending notifications
|
|
532
|
+
class TelegramNotificationService
|
|
533
|
+
def initialize(config_name = :default)
|
|
534
|
+
@config = FlowChat::Telegram::Configuration.get(config_name)
|
|
535
|
+
@client = FlowChat::Telegram::Client.new(@config)
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def send_order_confirmation(chat_id, order_id)
|
|
539
|
+
# Send confirmation message with buttons
|
|
540
|
+
keyboard = [
|
|
541
|
+
[
|
|
542
|
+
{ text: "Track Order", callback_data: "track_#{order_id}" },
|
|
543
|
+
{ text: "Contact Support", callback_data: "support" }
|
|
544
|
+
]
|
|
545
|
+
]
|
|
546
|
+
|
|
547
|
+
@client.send_text_with_keyboard(
|
|
548
|
+
chat_id,
|
|
549
|
+
"Order ##{order_id} confirmed!\n\nThank you for your purchase.",
|
|
550
|
+
keyboard
|
|
551
|
+
)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def send_document_notification(chat_id, document_url, filename)
|
|
555
|
+
@client.send_document(
|
|
556
|
+
chat_id,
|
|
557
|
+
document_url,
|
|
558
|
+
caption: "Your document is ready: #{filename}"
|
|
559
|
+
)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def broadcast_message(chat_ids, message)
|
|
563
|
+
chat_ids.each do |chat_id|
|
|
564
|
+
@client.send_text(chat_id, message)
|
|
565
|
+
rescue => e
|
|
566
|
+
Rails.logger.error { "Failed to send to #{chat_id}: #{e.message}" }
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Usage
|
|
572
|
+
service = TelegramNotificationService.new(:main_bot)
|
|
573
|
+
service.send_order_confirmation(123456789, "ORD-12345")
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
## Context Variables
|
|
577
|
+
|
|
578
|
+
The gateway sets these context variables during request processing:
|
|
579
|
+
|
|
580
|
+
### Request Context
|
|
581
|
+
|
|
582
|
+
| Variable | Description | Example |
|
|
583
|
+
|----------|-------------|---------|
|
|
584
|
+
| `request.id` | Chat ID | `"123456789"` |
|
|
585
|
+
| `request.user_id` | User ID | `"987654321"` |
|
|
586
|
+
| `request.user_name` | User's full name | `"John Doe"` |
|
|
587
|
+
| `request.username` | Telegram username | `"johndoe"` |
|
|
588
|
+
| `request.gateway` | Gateway identifier | `:telegram_bot_api` |
|
|
589
|
+
| `request.platform` | Platform identifier | `:telegram` |
|
|
590
|
+
| `request.message_id` | Message ID | `"456"` |
|
|
591
|
+
| `request.timestamp` | ISO8601 timestamp | `"2024-01-15T10:30:00Z"` |
|
|
592
|
+
| `request.body` | Raw webhook body | `{...}` |
|
|
593
|
+
|
|
594
|
+
### Telegram-Specific Context
|
|
595
|
+
|
|
596
|
+
| Variable | Description |
|
|
597
|
+
|----------|-------------|
|
|
598
|
+
| `telegram.client` | Telegram client instance |
|
|
599
|
+
| `telegram.chat_type` | Chat type (`private`, `group`, `supergroup`, `channel`) |
|
|
600
|
+
| `telegram.callback_query_id` | Callback query ID (for button presses) |
|
|
601
|
+
| `telegram.original_message_id` | Original message ID (for callbacks) |
|
|
602
|
+
|
|
603
|
+
### Media Context
|
|
604
|
+
|
|
605
|
+
| Variable | Description |
|
|
606
|
+
|----------|-------------|
|
|
607
|
+
| `request.media` | Media info hash with `:type`, `:file_id`, etc. |
|
|
608
|
+
| `request.location` | Location hash with `latitude`, `longitude` |
|
|
609
|
+
| `request.contact` | Contact hash with `:phone_number`, `:first_name`, etc. |
|
|
610
|
+
|
|
611
|
+
### Accessing Context in Flows
|
|
612
|
+
|
|
613
|
+
```ruby
|
|
614
|
+
class MyFlow < FlowChat::Flow
|
|
615
|
+
def main_page
|
|
616
|
+
chat_id = context["request.id"]
|
|
617
|
+
user_name = context["request.user_name"]
|
|
618
|
+
chat_type = context["telegram.chat_type"]
|
|
619
|
+
|
|
620
|
+
# Access Telegram client for custom operations
|
|
621
|
+
client = context["telegram.client"]
|
|
622
|
+
|
|
623
|
+
if chat_type == "group"
|
|
624
|
+
app.say "Hello, group members!"
|
|
625
|
+
else
|
|
626
|
+
app.say "Hello, #{user_name}!"
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## Security and Validation
|
|
633
|
+
|
|
634
|
+
### Webhook Signature Validation
|
|
635
|
+
|
|
636
|
+
Telegram sends a secret token in the `X-Telegram-Bot-Api-Secret-Token` header that you define when setting up the webhook. FlowChat validates this automatically.
|
|
637
|
+
|
|
638
|
+
#### How It Works
|
|
639
|
+
|
|
640
|
+
1. You set a `secret_token` when registering the webhook
|
|
641
|
+
2. Telegram includes this token in every webhook request
|
|
642
|
+
3. FlowChat compares the received token with your configured `secret_token`
|
|
643
|
+
4. Requests with invalid tokens are rejected with 401 Unauthorized
|
|
644
|
+
|
|
645
|
+
#### Production Configuration (Recommended)
|
|
646
|
+
|
|
647
|
+
```ruby
|
|
648
|
+
# config/credentials/production.yml
|
|
649
|
+
telegram:
|
|
650
|
+
bot_token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
|
651
|
+
secret_token: "a_very_secure_random_string_at_least_32_chars"
|
|
652
|
+
skip_signature_validation: false
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
```ruby
|
|
656
|
+
# Set up webhook with secret token
|
|
657
|
+
client.set_webhook(
|
|
658
|
+
"https://yourdomain.com/telegram/webhook",
|
|
659
|
+
secret_token: "a_very_secure_random_string_at_least_32_chars"
|
|
660
|
+
)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
#### Development Configuration
|
|
664
|
+
|
|
665
|
+
```ruby
|
|
666
|
+
# Option 1: Use real secret token (recommended for staging)
|
|
667
|
+
config = FlowChat::Telegram::Configuration.new(:dev_bot)
|
|
668
|
+
config.bot_token = "your_token"
|
|
669
|
+
config.secret_token = "your_secret"
|
|
670
|
+
config.skip_signature_validation = false
|
|
671
|
+
|
|
672
|
+
# Option 2: Disable validation (development only)
|
|
673
|
+
config = FlowChat::Telegram::Configuration.new(:dev_bot)
|
|
674
|
+
config.bot_token = "your_token"
|
|
675
|
+
config.skip_signature_validation = true
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
#### Security Warning
|
|
679
|
+
|
|
680
|
+
Never disable signature validation in production. An attacker could send fake webhook requests to your endpoint.
|
|
681
|
+
|
|
682
|
+
### Error Handling
|
|
683
|
+
|
|
684
|
+
```ruby
|
|
685
|
+
def webhook
|
|
686
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
687
|
+
config.use_gateway FlowChat::Telegram::Gateway::BotApi
|
|
688
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
processor.run WelcomeFlow, :main_page
|
|
692
|
+
rescue FlowChat::Telegram::ConfigurationError => e
|
|
693
|
+
Rails.logger.error { "Telegram configuration error: #{e.message}" }
|
|
694
|
+
head :internal_server_error
|
|
695
|
+
rescue => e
|
|
696
|
+
Rails.logger.error { "Telegram webhook error: #{e.message}" }
|
|
697
|
+
head :internal_server_error
|
|
698
|
+
end
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## Multi-Tenant Support
|
|
702
|
+
|
|
703
|
+
### Named Configurations
|
|
704
|
+
|
|
705
|
+
```ruby
|
|
706
|
+
# config/initializers/telegram_configs.rb
|
|
707
|
+
bot_a_config = FlowChat::Telegram::Configuration.new(:bot_a)
|
|
708
|
+
bot_a_config.bot_token = "bot_a_token"
|
|
709
|
+
bot_a_config.secret_token = "bot_a_secret"
|
|
710
|
+
|
|
711
|
+
bot_b_config = FlowChat::Telegram::Configuration.new(:bot_b)
|
|
712
|
+
bot_b_config.bot_token = "bot_b_token"
|
|
713
|
+
bot_b_config.secret_token = "bot_b_secret"
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### Configuration API
|
|
717
|
+
|
|
718
|
+
```ruby
|
|
719
|
+
# Check if configuration exists
|
|
720
|
+
FlowChat::Telegram::Configuration.exists?(:bot_a) # => true/false
|
|
721
|
+
|
|
722
|
+
# Get configuration by name
|
|
723
|
+
config = FlowChat::Telegram::Configuration.get(:bot_a)
|
|
724
|
+
|
|
725
|
+
# List all registered configurations
|
|
726
|
+
FlowChat::Telegram::Configuration.configuration_names # => [:bot_a, :bot_b]
|
|
727
|
+
|
|
728
|
+
# Clear all configurations (useful for testing)
|
|
729
|
+
FlowChat::Telegram::Configuration.clear_all!
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### Multi-Bot Controller
|
|
733
|
+
|
|
734
|
+
```ruby
|
|
735
|
+
class MultiTelegramController < ApplicationController
|
|
736
|
+
skip_forgery_protection
|
|
737
|
+
|
|
738
|
+
def webhook
|
|
739
|
+
bot_name = params[:bot_name]&.to_sym
|
|
740
|
+
|
|
741
|
+
unless FlowChat::Telegram::Configuration.exists?(bot_name)
|
|
742
|
+
Rails.logger.error { "No configuration found for bot: #{bot_name}" }
|
|
743
|
+
head :not_found
|
|
744
|
+
return
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
config = FlowChat::Telegram::Configuration.get(bot_name)
|
|
748
|
+
|
|
749
|
+
processor = FlowChat::Processor.new(self) do |c|
|
|
750
|
+
c.use_gateway FlowChat::Telegram::Gateway::BotApi, config
|
|
751
|
+
c.use_session_store FlowChat::Session::CacheSessionStore
|
|
752
|
+
c.use_session_config(
|
|
753
|
+
boundaries: [:flow, :platform, :bot],
|
|
754
|
+
identifier: :user_id,
|
|
755
|
+
bot: bot_name
|
|
756
|
+
)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
processor.run WelcomeFlow, :main_page
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
```ruby
|
|
765
|
+
# config/routes.rb
|
|
766
|
+
post '/telegram/:bot_name/webhook', to: 'multi_telegram#webhook'
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
## Testing and Development
|
|
770
|
+
|
|
771
|
+
### Local Development with ngrok
|
|
772
|
+
|
|
773
|
+
```bash
|
|
774
|
+
# Start ngrok tunnel
|
|
775
|
+
ngrok http 3000
|
|
776
|
+
|
|
777
|
+
# Use the HTTPS URL for webhook
|
|
778
|
+
# https://abc123.ngrok.io/telegram/webhook
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### Test Setup
|
|
782
|
+
|
|
783
|
+
```ruby
|
|
784
|
+
# test/integration/telegram_test.rb
|
|
785
|
+
class TelegramIntegrationTest < ActionDispatch::IntegrationTest
|
|
786
|
+
def setup
|
|
787
|
+
@config = FlowChat::Telegram::Configuration.new(:test)
|
|
788
|
+
@config.bot_token = "test_token"
|
|
789
|
+
@config.skip_signature_validation = true
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def test_text_message_webhook
|
|
793
|
+
webhook_data = {
|
|
794
|
+
update_id: 123,
|
|
795
|
+
message: {
|
|
796
|
+
message_id: 1,
|
|
797
|
+
from: { id: 123456, first_name: "Test", username: "testuser" },
|
|
798
|
+
chat: { id: 123456, type: "private" },
|
|
799
|
+
date: Time.now.to_i,
|
|
800
|
+
text: "Hello"
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
post "/telegram/webhook",
|
|
805
|
+
params: webhook_data.to_json,
|
|
806
|
+
headers: { "Content-Type" => "application/json" }
|
|
807
|
+
|
|
808
|
+
assert_response :ok
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def test_callback_query_webhook
|
|
812
|
+
webhook_data = {
|
|
813
|
+
update_id: 124,
|
|
814
|
+
callback_query: {
|
|
815
|
+
id: "callback_123",
|
|
816
|
+
from: { id: 123456, first_name: "Test" },
|
|
817
|
+
message: {
|
|
818
|
+
message_id: 1,
|
|
819
|
+
chat: { id: 123456, type: "private" }
|
|
820
|
+
},
|
|
821
|
+
data: "option_1"
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
post "/telegram/webhook",
|
|
826
|
+
params: webhook_data.to_json,
|
|
827
|
+
headers: { "Content-Type" => "application/json" }
|
|
828
|
+
|
|
829
|
+
assert_response :ok
|
|
830
|
+
end
|
|
831
|
+
end
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Mock Client for Tests
|
|
835
|
+
|
|
836
|
+
```ruby
|
|
837
|
+
class MockTelegramClient
|
|
838
|
+
attr_reader :sent_messages
|
|
839
|
+
|
|
840
|
+
def initialize
|
|
841
|
+
@sent_messages = []
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
def send_message(chat_id, prompt, choices: nil, media: nil)
|
|
845
|
+
@sent_messages << {
|
|
846
|
+
chat_id: chat_id,
|
|
847
|
+
prompt: prompt,
|
|
848
|
+
choices: choices,
|
|
849
|
+
media: media
|
|
850
|
+
}
|
|
851
|
+
{ "ok" => true, "result" => { "message_id" => 123 } }
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
def answer_callback_query(*)
|
|
855
|
+
{ "ok" => true }
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
## Troubleshooting
|
|
861
|
+
|
|
862
|
+
### Common Issues
|
|
863
|
+
|
|
864
|
+
**1. Webhook not receiving updates**
|
|
865
|
+
```
|
|
866
|
+
No updates being received at webhook endpoint
|
|
867
|
+
```
|
|
868
|
+
**Solution**:
|
|
869
|
+
- Verify webhook is set: `client.get_webhook_info`
|
|
870
|
+
- Check URL is HTTPS with valid certificate
|
|
871
|
+
- Ensure secret_token matches (if using validation)
|
|
872
|
+
- Check server logs for 401 errors
|
|
873
|
+
|
|
874
|
+
**2. Invalid webhook signature**
|
|
875
|
+
```
|
|
876
|
+
401 Unauthorized responses
|
|
877
|
+
```
|
|
878
|
+
**Solution**:
|
|
879
|
+
- Verify `secret_token` matches what was set in `set_webhook`
|
|
880
|
+
- Check `X-Telegram-Bot-Api-Secret-Token` header is being sent
|
|
881
|
+
- For development, set `skip_signature_validation: true`
|
|
882
|
+
|
|
883
|
+
**3. Callback query not working**
|
|
884
|
+
```
|
|
885
|
+
Button presses don't trigger flow
|
|
886
|
+
```
|
|
887
|
+
**Solution**:
|
|
888
|
+
- Ensure `callback_query` is in `allowed_updates` when setting webhook
|
|
889
|
+
- Check callback_data is within 64 character limit
|
|
890
|
+
- Verify the callback is being answered (done automatically by gateway)
|
|
891
|
+
|
|
892
|
+
**4. Media not sending**
|
|
893
|
+
```
|
|
894
|
+
Photos/documents fail to send
|
|
895
|
+
```
|
|
896
|
+
**Solution**:
|
|
897
|
+
- Use HTTPS URLs for media
|
|
898
|
+
- Check file size limits (photos: 10MB, documents: 50MB)
|
|
899
|
+
- Verify MIME types are correct
|
|
900
|
+
- For large files, upload first and use file_id
|
|
901
|
+
|
|
902
|
+
### Debug Mode
|
|
903
|
+
|
|
904
|
+
Enable debug logging:
|
|
905
|
+
|
|
906
|
+
```ruby
|
|
907
|
+
# config/environments/development.rb
|
|
908
|
+
config.log_level = :debug
|
|
909
|
+
|
|
910
|
+
# This will log:
|
|
911
|
+
# - Webhook request parsing
|
|
912
|
+
# - Signature validation
|
|
913
|
+
# - Message extraction
|
|
914
|
+
# - API requests/responses
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
### Webhook Diagnostics
|
|
918
|
+
|
|
919
|
+
```ruby
|
|
920
|
+
# Check webhook status
|
|
921
|
+
config = FlowChat::Telegram::Configuration.from_credentials
|
|
922
|
+
client = FlowChat::Telegram::Client.new(config)
|
|
923
|
+
|
|
924
|
+
info = client.get_webhook_info
|
|
925
|
+
puts "URL: #{info.dig("result", "url")}"
|
|
926
|
+
puts "Pending updates: #{info.dig("result", "pending_update_count")}"
|
|
927
|
+
puts "Last error: #{info.dig("result", "last_error_message")}"
|
|
928
|
+
puts "Last error date: #{info.dig("result", "last_error_date")}"
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
## Best Practices
|
|
932
|
+
|
|
933
|
+
### 1. Message Design
|
|
934
|
+
- Keep button text concise (under 64 characters)
|
|
935
|
+
- Use clear, actionable labels
|
|
936
|
+
- Limit inline keyboard to reasonable number of options
|
|
937
|
+
- Consider user experience on mobile
|
|
938
|
+
|
|
939
|
+
### 2. Media Usage
|
|
940
|
+
- Compress images appropriately
|
|
941
|
+
- Provide meaningful captions
|
|
942
|
+
- Use appropriate media types
|
|
943
|
+
- Consider bandwidth limitations
|
|
944
|
+
|
|
945
|
+
### 3. Flow Design
|
|
946
|
+
- Handle unexpected input gracefully
|
|
947
|
+
- Provide clear error messages
|
|
948
|
+
- Use progressive disclosure for complex flows
|
|
949
|
+
- Always acknowledge user actions
|
|
950
|
+
|
|
951
|
+
### 4. Security
|
|
952
|
+
- Always validate webhooks in production
|
|
953
|
+
- Use environment variables for tokens
|
|
954
|
+
- Rotate secret tokens periodically
|
|
955
|
+
- Monitor for unusual patterns
|
|
956
|
+
|
|
957
|
+
### 5. Performance
|
|
958
|
+
- Use background processing for slow operations
|
|
959
|
+
- Respond to webhooks quickly (under 60s)
|
|
960
|
+
- Cache frequently used data
|
|
961
|
+
- Handle rate limits gracefully
|
|
962
|
+
|
|
963
|
+
## API Reference
|
|
964
|
+
|
|
965
|
+
### Core Classes
|
|
966
|
+
|
|
967
|
+
| Class | Description |
|
|
968
|
+
|-------|-------------|
|
|
969
|
+
| `FlowChat::Telegram::Gateway::BotApi` | Telegram Bot API gateway |
|
|
970
|
+
| `FlowChat::Telegram::Client` | API client for messaging |
|
|
971
|
+
| `FlowChat::Telegram::Configuration` | Configuration management |
|
|
972
|
+
| `FlowChat::Telegram::Renderer` | Message rendering logic |
|
|
973
|
+
| `FlowChat::Telegram::Middleware::ChoiceMapper` | Choice validation middleware |
|
|
974
|
+
|
|
975
|
+
### Client Methods
|
|
976
|
+
|
|
977
|
+
| Method | Description | Parameters |
|
|
978
|
+
|--------|-------------|------------|
|
|
979
|
+
| `send_message(chat_id, prompt, choices:, media:)` | Send FlowChat response | chat_id, prompt, options |
|
|
980
|
+
| `send_text(chat_id, text, parse_mode:)` | Send text message | chat_id, text, parse_mode |
|
|
981
|
+
| `send_text_with_keyboard(chat_id, text, keyboard)` | Send text with inline keyboard | chat_id, text, keyboard array |
|
|
982
|
+
| `send_photo(chat_id, photo, caption:)` | Send photo | chat_id, URL/file_id, caption |
|
|
983
|
+
| `send_photo_with_keyboard(chat_id, photo, caption:, keyboard:)` | Send photo with buttons | chat_id, photo, options |
|
|
984
|
+
| `send_document(chat_id, document, caption:)` | Send document | chat_id, URL/file_id, caption |
|
|
985
|
+
| `send_video(chat_id, video, caption:)` | Send video | chat_id, URL/file_id, caption |
|
|
986
|
+
| `send_audio(chat_id, audio, caption:)` | Send audio | chat_id, URL/file_id, caption |
|
|
987
|
+
| `send_voice(chat_id, voice)` | Send voice message | chat_id, URL/file_id |
|
|
988
|
+
| `edit_message_text(chat_id, message_id, text, keyboard:)` | Edit message | chat_id, msg_id, text, keyboard |
|
|
989
|
+
| `delete_message(chat_id, message_id)` | Delete message | chat_id, message_id |
|
|
990
|
+
| `answer_callback_query(id, text:, show_alert:)` | Acknowledge callback | query_id, options |
|
|
991
|
+
| `set_webhook(url, secret_token:, allowed_updates:)` | Set webhook URL | url, options |
|
|
992
|
+
| `delete_webhook` | Remove webhook | none |
|
|
993
|
+
| `get_webhook_info` | Get webhook status | none |
|
|
994
|
+
| `get_me` | Get bot information | none |
|
|
995
|
+
|
|
996
|
+
### Configuration Methods
|
|
997
|
+
|
|
998
|
+
| Method | Description |
|
|
999
|
+
|--------|-------------|
|
|
1000
|
+
| `Configuration.from_credentials` | Load from Rails credentials/ENV |
|
|
1001
|
+
| `Configuration.new(name)` | Create named configuration |
|
|
1002
|
+
| `Configuration.get(name)` | Get configuration by name |
|
|
1003
|
+
| `Configuration.exists?(name)` | Check if config exists |
|
|
1004
|
+
| `Configuration.configuration_names` | List all config names |
|
|
1005
|
+
| `Configuration.clear_all!` | Remove all configurations |
|
|
1006
|
+
| `config.valid?` | Check if config is valid |
|
|
1007
|
+
| `config.api_base_url` | Get API base URL |
|
|
1008
|
+
| `config.bot_id` | Get bot ID from token |
|
|
1009
|
+
| `config.register_as(name)` | Register config with name |
|
|
1010
|
+
|
|
1011
|
+
---
|
|
1012
|
+
|
|
1013
|
+
This guide covers everything you need to build Telegram bot integrations with FlowChat. For more examples and advanced patterns, check the [examples directory](../../examples/) in the FlowChat repository.
|