telegem 3.3.0 → 3.4.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/.rubocop.yml +57 -0
- data/CHANGELOG.md +121 -0
- data/Gemfile +1 -1
- data/README.md +147 -0
- data/bin/telegem-ssl +71 -25
- data/contributing.md +375 -0
- data/docs/api.md +663 -0
- data/docs/bot.md +332 -0
- data/docs/changelog.md +182 -0
- data/docs/context.md +554 -0
- data/docs/core_concepts.md +218 -0
- data/docs/deployment.md +702 -0
- data/docs/error_handling.md +435 -0
- data/docs/examples.md +752 -0
- data/docs/getting_started.md +151 -0
- data/docs/handlers.md +580 -0
- data/docs/keyboards.md +446 -0
- data/docs/middleware.md +536 -0
- data/docs/plugins.md +384 -0
- data/docs/scenes.md +517 -0
- data/docs/sessions.md +544 -0
- data/docs/testing.md +612 -0
- data/docs/troubleshooting.md +574 -0
- data/docs/types.md +538 -0
- data/docs/webhooks.md +456 -0
- data/lib/api/client.rb +38 -10
- data/lib/api/types.rb +433 -172
- data/lib/core/composer.rb +3 -3
- data/lib/core/context.rb +17 -11
- data/lib/plugins/cc +3 -0
- data/lib/plugins/file_extract.rb +2 -2
- data/lib/plugins/translate.rb +43 -0
- data/lib/session/memory_store.rb +90 -103
- data/lib/session/redis.rb +91 -0
- data/lib/telegem.rb +4 -4
- data/lib/webhook/server.rb +5 -3
- metadata +51 -35
- data/CHANGELOG +0 -95
- data/Contributing.md +0 -161
- data/Readme.md +0 -302
- data/examples/.gitkeep +0 -0
- data/public/.gitkeep +0 -0
data/docs/examples.md
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Practical examples showing how to build various types of bots with Telegem.
|
|
4
|
+
|
|
5
|
+
## Basic Echo Bot
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require 'telegem'
|
|
9
|
+
|
|
10
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
11
|
+
|
|
12
|
+
bot.hears(/.*/) do |ctx|
|
|
13
|
+
ctx.reply(ctx.text)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
bot.start_polling
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Command Bot
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require 'telegem'
|
|
23
|
+
|
|
24
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
25
|
+
|
|
26
|
+
bot.command('start') do |ctx|
|
|
27
|
+
ctx.reply("Hello! I'm a command bot.")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
bot.command('help') do |ctx|
|
|
31
|
+
help_text = <<~HELP
|
|
32
|
+
Available commands:
|
|
33
|
+
/start - Start the bot
|
|
34
|
+
/help - Show this help
|
|
35
|
+
/echo <text> - Echo back your text
|
|
36
|
+
/time - Show current time
|
|
37
|
+
HELP
|
|
38
|
+
|
|
39
|
+
ctx.reply(help_text)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
bot.command('echo') do |ctx|
|
|
43
|
+
text = ctx.command_args
|
|
44
|
+
if text.empty?
|
|
45
|
+
ctx.reply("Usage: /echo <text>")
|
|
46
|
+
else
|
|
47
|
+
ctx.reply(text)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
bot.command('time') do |ctx|
|
|
52
|
+
ctx.reply("Current time: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
bot.start_polling
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Inline Keyboard Bot
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
require 'telegem'
|
|
62
|
+
|
|
63
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
64
|
+
|
|
65
|
+
bot.command('menu') do |ctx|
|
|
66
|
+
keyboard = Telegem.inline_keyboard do |kb|
|
|
67
|
+
kb.row do
|
|
68
|
+
kb.button('Option 1', callback_data: 'opt1')
|
|
69
|
+
kb.button('Option 2', callback_data: 'opt2')
|
|
70
|
+
end
|
|
71
|
+
kb.row do
|
|
72
|
+
kb.button('Help', callback_data: 'help')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
ctx.reply('Choose an option:', keyboard: keyboard)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
bot.callback_query do |ctx|
|
|
80
|
+
case ctx.callback_data
|
|
81
|
+
when 'opt1'
|
|
82
|
+
ctx.answer_callback_query('You chose Option 1')
|
|
83
|
+
ctx.edit_message_text('You selected Option 1')
|
|
84
|
+
when 'opt2'
|
|
85
|
+
ctx.answer_callback_query('You chose Option 2')
|
|
86
|
+
ctx.edit_message_text('You selected Option 2')
|
|
87
|
+
when 'help'
|
|
88
|
+
ctx.answer_callback_query('Help coming soon')
|
|
89
|
+
ctx.edit_message_text('Help: This is a demo bot')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
bot.start_polling
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Reply Keyboard Bot
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
require 'telegem'
|
|
100
|
+
|
|
101
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
102
|
+
|
|
103
|
+
bot.command('keyboard') do |ctx|
|
|
104
|
+
keyboard = Telegem.reply_keyboard do |kb|
|
|
105
|
+
kb.row('Button 1', 'Button 2')
|
|
106
|
+
kb.row('Button 3', 'Button 4')
|
|
107
|
+
kb.row do
|
|
108
|
+
kb.button('Remove Keyboard', request_contact: false, request_location: false)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
ctx.reply('Choose a button:', keyboard: keyboard)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
bot.hears(/^Button/) do |ctx|
|
|
116
|
+
ctx.reply("You pressed: #{ctx.text}")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
bot.hears('Remove Keyboard') do |ctx|
|
|
120
|
+
ctx.reply('Keyboard removed', keyboard: Telegem.remove_keyboard)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
bot.start_polling
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## File Handling Bot
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
require 'telegem'
|
|
130
|
+
|
|
131
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
132
|
+
|
|
133
|
+
bot.document do |ctx|
|
|
134
|
+
doc = ctx.message.document
|
|
135
|
+
file_name = doc.file_name
|
|
136
|
+
file_size = doc.file_size
|
|
137
|
+
|
|
138
|
+
ctx.reply("Received file: #{file_name} (#{file_size} bytes)")
|
|
139
|
+
|
|
140
|
+
# Download file
|
|
141
|
+
begin
|
|
142
|
+
file_content = ctx.download_file(doc.file_id)
|
|
143
|
+
ctx.reply("Downloaded #{file_content.length} bytes")
|
|
144
|
+
rescue => e
|
|
145
|
+
ctx.reply("Failed to download file: #{e.message}")
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
bot.photo do |ctx|
|
|
150
|
+
photo = ctx.message.photo.last # Get highest resolution
|
|
151
|
+
file_id = photo.file_id
|
|
152
|
+
|
|
153
|
+
ctx.reply("Received photo: #{photo.width}x#{photo.height}")
|
|
154
|
+
|
|
155
|
+
# Download and process photo
|
|
156
|
+
begin
|
|
157
|
+
photo_data = ctx.download_file(file_id)
|
|
158
|
+
# Process photo_data...
|
|
159
|
+
ctx.reply("Photo processed successfully")
|
|
160
|
+
rescue => e
|
|
161
|
+
ctx.reply("Failed to process photo: #{e.message}")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
bot.start_polling
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Scene-Based Bot (Multi-step Conversation)
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
require 'telegem'
|
|
172
|
+
|
|
173
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
174
|
+
|
|
175
|
+
# Registration scene
|
|
176
|
+
bot.scene('register') do |scene|
|
|
177
|
+
scene.step('name') do |ctx|
|
|
178
|
+
ctx.session[:name] = ctx.text
|
|
179
|
+
ctx.reply('Enter your email:')
|
|
180
|
+
scene.next_step
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
scene.step('email') do |ctx|
|
|
184
|
+
ctx.session[:email] = ctx.text
|
|
185
|
+
ctx.reply('Enter your age:')
|
|
186
|
+
scene.next_step
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
scene.step('age') do |ctx|
|
|
190
|
+
ctx.session[:age] = ctx.text.to_i
|
|
191
|
+
ctx.reply("Registration complete!\nName: #{ctx.session[:name]}\nEmail: #{ctx.session[:email]}\nAge: #{ctx.session[:age]}")
|
|
192
|
+
scene.complete
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
scene.on_timeout do |ctx|
|
|
196
|
+
ctx.reply('Registration timed out. Start again with /register')
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
bot.command('register') do |ctx|
|
|
201
|
+
ctx.reply('Enter your name:')
|
|
202
|
+
ctx.enter_scene('register')
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
bot.command('cancel') do |ctx|
|
|
206
|
+
if ctx.current_scene
|
|
207
|
+
ctx.exit_scene
|
|
208
|
+
ctx.reply('Registration cancelled')
|
|
209
|
+
else
|
|
210
|
+
ctx.reply('No active registration')
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
bot.start_polling
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Weather Bot
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
require 'telegem'
|
|
221
|
+
require 'httparty'
|
|
222
|
+
|
|
223
|
+
class WeatherService
|
|
224
|
+
BASE_URL = 'https://api.openweathermap.org/data/2.5'
|
|
225
|
+
|
|
226
|
+
def initialize(api_key)
|
|
227
|
+
@api_key = api_key
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def get_weather(city)
|
|
231
|
+
response = HTTParty.get("#{BASE_URL}/weather", query: {
|
|
232
|
+
q: city,
|
|
233
|
+
appid: @api_key,
|
|
234
|
+
units: 'metric'
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
if response.success?
|
|
238
|
+
data = response.parsed_response
|
|
239
|
+
{
|
|
240
|
+
city: data['name'],
|
|
241
|
+
temp: data['main']['temp'],
|
|
242
|
+
description: data['weather'].first['description'],
|
|
243
|
+
humidity: data['main']['humidity']
|
|
244
|
+
}
|
|
245
|
+
else
|
|
246
|
+
nil
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
252
|
+
weather_service = WeatherService.new('YOUR_OPENWEATHER_API_KEY')
|
|
253
|
+
|
|
254
|
+
bot.command('weather') do |ctx|
|
|
255
|
+
city = ctx.command_args
|
|
256
|
+
|
|
257
|
+
if city.empty?
|
|
258
|
+
ctx.reply('Usage: /weather <city>')
|
|
259
|
+
return
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
ctx.reply('Getting weather data...')
|
|
263
|
+
|
|
264
|
+
Async do
|
|
265
|
+
weather = weather_service.get_weather(city)
|
|
266
|
+
|
|
267
|
+
if weather
|
|
268
|
+
message = <<~WEATHER
|
|
269
|
+
Weather in #{weather[:city]}:
|
|
270
|
+
Temperature: #{weather[:temp]}°C
|
|
271
|
+
Conditions: #{weather[:description]}
|
|
272
|
+
Humidity: #{weather[:humidity]}%
|
|
273
|
+
WEATHER
|
|
274
|
+
|
|
275
|
+
ctx.reply(message)
|
|
276
|
+
else
|
|
277
|
+
ctx.reply('Could not get weather data. Check city name.')
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
bot.start_polling
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Translation Bot
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
require 'telegem'
|
|
289
|
+
require 'telegem/plugins/translate'
|
|
290
|
+
|
|
291
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
292
|
+
|
|
293
|
+
bot.command('translate') do |ctx|
|
|
294
|
+
args = ctx.command_args.split(' to ')
|
|
295
|
+
|
|
296
|
+
if args.length != 2
|
|
297
|
+
ctx.reply('Usage: /translate <text> to <language>')
|
|
298
|
+
ctx.reply('Example: /translate hello world to es')
|
|
299
|
+
return
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
text, target_lang = args
|
|
303
|
+
|
|
304
|
+
ctx.reply('Translating...')
|
|
305
|
+
|
|
306
|
+
Async do
|
|
307
|
+
translator = Telegem::Plugins::Translate.new(text, 'auto', target_lang)
|
|
308
|
+
result = translator.start_translating
|
|
309
|
+
|
|
310
|
+
if result['error'] == 'false'
|
|
311
|
+
ctx.reply("Translation: #{result['translation']}")
|
|
312
|
+
else
|
|
313
|
+
ctx.reply('Translation failed. Please try again.')
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
bot.hears(/^translate (.+) to (\w+)/) do |ctx|
|
|
319
|
+
text = ctx.match[1]
|
|
320
|
+
target_lang = ctx.match[2]
|
|
321
|
+
|
|
322
|
+
translator = Telegem::Plugins::Translate.new(text, 'auto', target_lang)
|
|
323
|
+
result = translator.start_translating
|
|
324
|
+
|
|
325
|
+
if result['error'] == 'false'
|
|
326
|
+
ctx.reply("Translation: #{result['translation']}")
|
|
327
|
+
else
|
|
328
|
+
ctx.reply('Translation failed.')
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
bot.start_polling
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## File Analysis Bot
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
require 'telegem'
|
|
339
|
+
require 'telegem/plugins/file_extract'
|
|
340
|
+
|
|
341
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
342
|
+
|
|
343
|
+
bot.document do |ctx|
|
|
344
|
+
doc = ctx.message.document
|
|
345
|
+
file_name = doc.file_name
|
|
346
|
+
|
|
347
|
+
ctx.reply("Analyzing file: #{file_name}")
|
|
348
|
+
|
|
349
|
+
Async do
|
|
350
|
+
begin
|
|
351
|
+
extractor = Telegem::Plugins::FileExtract.new(ctx.bot, doc.file_id)
|
|
352
|
+
result = extractor.extract
|
|
353
|
+
|
|
354
|
+
if result[:success]
|
|
355
|
+
content_preview = result[:content].first(500)
|
|
356
|
+
metadata_info = result[:metadata].map { |k,v| "#{k}: #{v}" }.join("\n")
|
|
357
|
+
|
|
358
|
+
response = <<~ANALYSIS
|
|
359
|
+
File Type: #{result[:type].to_s.upcase}
|
|
360
|
+
Metadata:
|
|
361
|
+
#{metadata_info}
|
|
362
|
+
|
|
363
|
+
Content Preview:
|
|
364
|
+
#{content_preview}#{'...' if result[:content].length > 500}
|
|
365
|
+
ANALYSIS
|
|
366
|
+
|
|
367
|
+
ctx.reply(response)
|
|
368
|
+
else
|
|
369
|
+
ctx.reply("Analysis failed: #{result[:error]}")
|
|
370
|
+
end
|
|
371
|
+
rescue => e
|
|
372
|
+
ctx.reply("Error analyzing file: #{e.message}")
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
bot.start_polling
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Admin Bot with Middleware
|
|
381
|
+
|
|
382
|
+
```ruby
|
|
383
|
+
require 'telegem'
|
|
384
|
+
|
|
385
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
386
|
+
|
|
387
|
+
# Admin check middleware
|
|
388
|
+
bot.use do |ctx, next_middleware|
|
|
389
|
+
admin_ids = [123456789, 987654321] # Replace with actual admin IDs
|
|
390
|
+
|
|
391
|
+
if ctx.from && admin_ids.include?(ctx.from.id)
|
|
392
|
+
ctx.is_admin = true
|
|
393
|
+
else
|
|
394
|
+
ctx.is_admin = false
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
next_middleware.call(ctx)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Logging middleware
|
|
401
|
+
bot.use do |ctx, next_middleware|
|
|
402
|
+
start_time = Time.now
|
|
403
|
+
next_middleware.call(ctx)
|
|
404
|
+
duration = Time.now - start_time
|
|
405
|
+
|
|
406
|
+
puts "[#{Time.now}] #{ctx.from&.username}: #{ctx.text} (#{duration.round(3)}s)"
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Admin-only commands
|
|
410
|
+
bot.command('stats') do |ctx|
|
|
411
|
+
unless ctx.is_admin
|
|
412
|
+
ctx.reply('Access denied')
|
|
413
|
+
return
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
# Get bot statistics
|
|
417
|
+
stats = {
|
|
418
|
+
uptime: '24h 30m',
|
|
419
|
+
messages_processed: 15420,
|
|
420
|
+
active_users: 234
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
message = <<~STATS
|
|
424
|
+
Bot Statistics:
|
|
425
|
+
Uptime: #{stats[:uptime]}
|
|
426
|
+
Messages: #{stats[:messages_processed]}
|
|
427
|
+
Active Users: #{stats[:active_users]}
|
|
428
|
+
STATS
|
|
429
|
+
|
|
430
|
+
ctx.reply(message)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
bot.command('broadcast') do |ctx|
|
|
434
|
+
unless ctx.is_admin
|
|
435
|
+
ctx.reply('Access denied')
|
|
436
|
+
return
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
message = ctx.command_args
|
|
440
|
+
if message.empty?
|
|
441
|
+
ctx.reply('Usage: /broadcast <message>')
|
|
442
|
+
return
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# In a real bot, you'd get all user IDs from database
|
|
446
|
+
user_ids = [111111, 222222, 333333] # Example user IDs
|
|
447
|
+
|
|
448
|
+
user_ids.each do |user_id|
|
|
449
|
+
begin
|
|
450
|
+
bot.api.call('sendMessage', chat_id: user_id, text: message)
|
|
451
|
+
rescue => e
|
|
452
|
+
puts "Failed to send to #{user_id}: #{e.message}"
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
ctx.reply("Broadcast sent to #{user_ids.length} users")
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
# Public commands
|
|
460
|
+
bot.command('help') do |ctx|
|
|
461
|
+
help_text = <<~HELP
|
|
462
|
+
Commands:
|
|
463
|
+
/help - Show this help
|
|
464
|
+
/info - Bot information
|
|
465
|
+
HELP
|
|
466
|
+
|
|
467
|
+
if ctx.is_admin
|
|
468
|
+
help_text += "\nAdmin Commands:\n/stats - Bot statistics\n/broadcast <msg> - Send broadcast"
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
ctx.reply(help_text)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
bot.command('info') do |ctx|
|
|
475
|
+
ctx.reply('This is a demo admin bot with Telegem')
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
bot.start_polling
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## E-commerce Bot
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
require 'telegem'
|
|
485
|
+
|
|
486
|
+
class ProductCatalog
|
|
487
|
+
def initialize
|
|
488
|
+
@products = {
|
|
489
|
+
'1' => { name: 'Laptop', price: 999.99, description: 'High-performance laptop' },
|
|
490
|
+
'2' => { name: 'Mouse', price: 29.99, description: 'Wireless mouse' },
|
|
491
|
+
'3' => { name: 'Keyboard', price: 79.99, description: 'Mechanical keyboard' }
|
|
492
|
+
}
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def get_product(id)
|
|
496
|
+
@products[id]
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def all_products
|
|
500
|
+
@products
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
505
|
+
catalog = ProductCatalog.new()
|
|
506
|
+
|
|
507
|
+
# Session-based shopping cart
|
|
508
|
+
bot.use Telegem::Session::Middleware.new
|
|
509
|
+
|
|
510
|
+
bot.command('shop') do |ctx|
|
|
511
|
+
keyboard = Telegem.inline_keyboard do |kb|
|
|
512
|
+
catalog.all_products.each do |id, product|
|
|
513
|
+
kb.row do
|
|
514
|
+
kb.button("#{product[:name]} - $#{product[:price]}", callback_data: "view_#{id}")
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
ctx.reply('Welcome to our shop! Choose a product:', keyboard: keyboard)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
bot.callback_query(/^view_/) do |ctx|
|
|
523
|
+
product_id = ctx.callback_data.sub('view_', '')
|
|
524
|
+
product = catalog.get_product(product_id)
|
|
525
|
+
|
|
526
|
+
if product
|
|
527
|
+
keyboard = Telegem.inline_keyboard do |kb|
|
|
528
|
+
kb.row do
|
|
529
|
+
kb.button('Add to Cart', callback_data: "add_#{product_id}")
|
|
530
|
+
kb.button('Back to Shop', callback_data: 'shop')
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
message = <<~PRODUCT
|
|
535
|
+
#{product[:name]}
|
|
536
|
+
Price: $#{product[:price]}
|
|
537
|
+
Description: #{product[:description]}
|
|
538
|
+
PRODUCT
|
|
539
|
+
|
|
540
|
+
ctx.edit_message_text(message, keyboard: keyboard)
|
|
541
|
+
else
|
|
542
|
+
ctx.answer_callback_query('Product not found')
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
bot.callback_query(/^add_/) do |ctx|
|
|
547
|
+
product_id = ctx.callback_data.sub('add_', '')
|
|
548
|
+
product = catalog.get_product(product_id)
|
|
549
|
+
|
|
550
|
+
if product
|
|
551
|
+
ctx.session[:cart] ||= []
|
|
552
|
+
ctx.session[:cart] << product_id
|
|
553
|
+
|
|
554
|
+
cart_count = ctx.session[:cart].length
|
|
555
|
+
ctx.answer_callback_query("Added to cart! Items in cart: #{cart_count}")
|
|
556
|
+
|
|
557
|
+
# Update message with cart count
|
|
558
|
+
ctx.edit_message_reply_markup(
|
|
559
|
+
keyboard: Telegem.inline_keyboard do |kb|
|
|
560
|
+
kb.row do
|
|
561
|
+
kb.button("View Cart (#{cart_count})", callback_data: 'cart')
|
|
562
|
+
kb.button('Continue Shopping', callback_data: 'shop')
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
bot.callback_query('cart') do |ctx|
|
|
570
|
+
cart = ctx.session[:cart] || []
|
|
571
|
+
|
|
572
|
+
if cart.empty?
|
|
573
|
+
ctx.edit_message_text('Your cart is empty')
|
|
574
|
+
return
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
total = 0
|
|
578
|
+
message = "Your Cart:\n\n"
|
|
579
|
+
|
|
580
|
+
cart.each do |product_id|
|
|
581
|
+
product = catalog.get_product(product_id)
|
|
582
|
+
if product
|
|
583
|
+
message += "#{product[:name]} - $#{product[:price]}\n"
|
|
584
|
+
total += product[:price]
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
message += "\nTotal: $#{total.round(2)}"
|
|
589
|
+
|
|
590
|
+
keyboard = Telegem.inline_keyboard do |kb|
|
|
591
|
+
kb.row do
|
|
592
|
+
kb.button('Checkout', callback_data: 'checkout')
|
|
593
|
+
kb.button('Clear Cart', callback_data: 'clear_cart')
|
|
594
|
+
kb.button('Continue Shopping', callback_data: 'shop')
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
ctx.edit_message_text(message, keyboard: keyboard)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
bot.callback_query('clear_cart') do |ctx|
|
|
602
|
+
ctx.session[:cart] = []
|
|
603
|
+
ctx.edit_message_text('Cart cleared!')
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
bot.callback_query('checkout') do |ctx|
|
|
607
|
+
cart = ctx.session[:cart] || []
|
|
608
|
+
|
|
609
|
+
if cart.empty?
|
|
610
|
+
ctx.answer_callback_query('Your cart is empty')
|
|
611
|
+
return
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# In a real bot, you'd process payment here
|
|
615
|
+
ctx.session[:cart] = []
|
|
616
|
+
ctx.edit_message_text('Thank you for your purchase! Your order has been processed.')
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
bot.callback_query('shop') do |ctx|
|
|
620
|
+
# Re-show shop
|
|
621
|
+
keyboard = Telegem.inline_keyboard do |kb|
|
|
622
|
+
catalog.all_products.each do |id, product|
|
|
623
|
+
kb.row do
|
|
624
|
+
kb.button("#{product[:name]} - $#{product[:price]}", callback_data: "view_#{id}")
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
ctx.edit_message_text('Welcome to our shop! Choose a product:', keyboard: keyboard)
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
bot.start_polling
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## Reminder Bot
|
|
636
|
+
|
|
637
|
+
```ruby
|
|
638
|
+
require 'telegem'
|
|
639
|
+
|
|
640
|
+
bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
|
|
641
|
+
|
|
642
|
+
# Use session to store reminders
|
|
643
|
+
bot.use Telegem::Session::Middleware.new
|
|
644
|
+
|
|
645
|
+
bot.command('remind') do |ctx|
|
|
646
|
+
args = ctx.command_args.split(' in ')
|
|
647
|
+
|
|
648
|
+
if args.length != 2
|
|
649
|
+
ctx.reply('Usage: /remind <message> in <time>')
|
|
650
|
+
ctx.reply('Example: /remind Buy milk in 5 minutes')
|
|
651
|
+
return
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
message, time_spec = args
|
|
655
|
+
|
|
656
|
+
# Parse time (simple implementation)
|
|
657
|
+
minutes = parse_time(time_spec)
|
|
658
|
+
|
|
659
|
+
if minutes.nil?
|
|
660
|
+
ctx.reply('Invalid time format. Use: 5 minutes, 1 hour, 30 seconds, etc.')
|
|
661
|
+
return
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# Store reminder
|
|
665
|
+
ctx.session[:reminders] ||= []
|
|
666
|
+
reminder = {
|
|
667
|
+
message: message,
|
|
668
|
+
time: Time.now + (minutes * 60),
|
|
669
|
+
id: rand(1000000)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
ctx.session[:reminders] << reminder
|
|
673
|
+
|
|
674
|
+
ctx.reply("Reminder set: '#{message}' in #{minutes} minutes")
|
|
675
|
+
|
|
676
|
+
# Schedule reminder (in real bot, use a job queue)
|
|
677
|
+
Async do
|
|
678
|
+
sleep(minutes * 60)
|
|
679
|
+
ctx.reply("⏰ Reminder: #{message}")
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
bot.command('list') do |ctx|
|
|
684
|
+
reminders = ctx.session[:reminders] || []
|
|
685
|
+
|
|
686
|
+
if reminders.empty?
|
|
687
|
+
ctx.reply('No active reminders')
|
|
688
|
+
return
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
message = "Your reminders:\n\n"
|
|
692
|
+
reminders.each_with_index do |reminder, index|
|
|
693
|
+
time_left = ((reminder[:time] - Time.now) / 60).round
|
|
694
|
+
message += "#{index + 1}. #{reminder[:message]} (in #{time_left} minutes)\n"
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
ctx.reply(message)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
bot.command('cancel') do |ctx|
|
|
701
|
+
reminders = ctx.session[:reminders] || []
|
|
702
|
+
|
|
703
|
+
if reminders.empty?
|
|
704
|
+
ctx.reply('No active reminders to cancel')
|
|
705
|
+
return
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
keyboard = Telegem.inline_keyboard do |kb|
|
|
709
|
+
reminders.each_with_index do |reminder, index|
|
|
710
|
+
kb.row do
|
|
711
|
+
kb.button("Cancel: #{reminder[:message][0..20]}...", callback_data: "cancel_#{reminder[:id]}")
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
ctx.reply('Select reminder to cancel:', keyboard: keyboard)
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
bot.callback_query(/^cancel_/) do |ctx|
|
|
720
|
+
reminder_id = ctx.callback_data.sub('cancel_', '').to_i
|
|
721
|
+
|
|
722
|
+
reminders = ctx.session[:reminders] || []
|
|
723
|
+
reminder = reminders.find { |r| r[:id] == reminder_id }
|
|
724
|
+
|
|
725
|
+
if reminder
|
|
726
|
+
ctx.session[:reminders].delete(reminder)
|
|
727
|
+
ctx.edit_message_text("Reminder cancelled: #{reminder[:message]}")
|
|
728
|
+
else
|
|
729
|
+
ctx.answer_callback_query('Reminder not found')
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
def parse_time(time_spec)
|
|
734
|
+
match = time_spec.match(/(\d+)\s*(second|minute|hour|day)s?/)
|
|
735
|
+
return nil unless match
|
|
736
|
+
|
|
737
|
+
value = match[1].to_i
|
|
738
|
+
unit = match[2]
|
|
739
|
+
|
|
740
|
+
case unit
|
|
741
|
+
when 'second' then value / 60.0 # Convert to minutes
|
|
742
|
+
when 'minute' then value
|
|
743
|
+
when 'hour' then value * 60
|
|
744
|
+
when 'day' then value * 24 * 60
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
bot.start_polling
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
These examples demonstrate various bot patterns and use cases. Each example can be extended and customized based on specific requirements.</content>
|
|
752
|
+
<parameter name="filePath">/home/slick/telegem/docs/examples.md
|