telegem 3.3.0 → 3.3.1
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/CHANGELOG.md +103 -0
- data/Gemfile +1 -1
- data/Readme.md +1 -5
- data/bin/telegem-init +32 -0
- data/lib/api/types.rb +308 -70
- data/lib/plugins/file_extract.rb +2 -2
- data/lib/session/memory_store.rb +90 -103
- data/lib/telegem.rb +2 -2
- metadata +16 -12
- data/CHANGELOG +0 -95
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d2afc9e262ffced76a28974a69c6e652bf433b2b6da80c6827d45e6da43002a
|
|
4
|
+
data.tar.gz: 61f440f2523473e3f5435075bfece0519c374f4aa498d789cc6752c5ce285abf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 378d55910d0b38b255b35ea357af4cb88f45000362b9d16688dff82a02fe3e5119e71e591c2cec61556d9c9cc82c0edd914ba7cd153ba1cae04c0e97b3f6d229
|
|
7
|
+
data.tar.gz: 527cb2833282c03d7f19145d7421e6df5d867ffbb479846ac9e969a8be339450c7358fdefaced5952dfc7d96b999348253a2494b2a2fe3d9c55480073587a421
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Telegem Changelog
|
|
2
|
+
|
|
3
|
+
## v3.3.1(lastest)
|
|
4
|
+
### Features
|
|
5
|
+
- improved memeorystore to include diskbackup
|
|
6
|
+
- added 'telegem-init' cli
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## v3.1.1
|
|
10
|
+
|
|
11
|
+
### New Features
|
|
12
|
+
|
|
13
|
+
-FileExtractor Plugin: New plugin for extracting content from various file types (PDF, JSON, HTML, TXT)
|
|
14
|
+
- Async File Download: Added download method to API client for downloading Telegram files
|
|
15
|
+
- Context File Helpers: Added download_file, download_photo, download_document methods to Context
|
|
16
|
+
- Extended File Support: Plugin supports PDF text extraction, JSON parsing, HTML/raw text processing
|
|
17
|
+
- Async/Sync Dual Mode: All file operations available in both sync (download) and async (download!) modes
|
|
18
|
+
|
|
19
|
+
## v3.1.0
|
|
20
|
+
|
|
21
|
+
### features
|
|
22
|
+
- BREAKING: Rewrote polling system to prevent duplicate messages
|
|
23
|
+
- Fixed thread deadlock in async polling loop
|
|
24
|
+
- Added scene_middleware.rb for scene-based conversations
|
|
25
|
+
- Improved MemoryStore with TTL and thread safety
|
|
26
|
+
- Enhanced keyboard markup builder with web_app support
|
|
27
|
+
- Added message reaction and chat boost update types
|
|
28
|
+
- Fixed callback query handling for inline keyboards
|
|
29
|
+
|
|
30
|
+
## v3.0.0
|
|
31
|
+
### features
|
|
32
|
+
|
|
33
|
+
- BREAKING: Complete async rewrite with async gem
|
|
34
|
+
- New HTTP client using HTTPX with proper async/await pattern
|
|
35
|
+
- Added scene system for multi-step conversations
|
|
36
|
+
- Middleware composer system for plugin architecture
|
|
37
|
+
- Type system with dynamic accessors for Telegram objects
|
|
38
|
+
- Session management with memory store
|
|
39
|
+
- Rate limiting middleware
|
|
40
|
+
- File upload support via multipart forms
|
|
41
|
+
|
|
42
|
+
## v2.0.0
|
|
43
|
+
|
|
44
|
+
- BREAKING: Ruby 3.0+ requirement
|
|
45
|
+
- Added webhook support with Rack middleware
|
|
46
|
+
- Inline query and callback query handlers
|
|
47
|
+
- Location, contact, and poll answer handlers
|
|
48
|
+
- Keyboard markup helpers (Telegem::Markup)
|
|
49
|
+
- Improved error handling with custom error classes
|
|
50
|
+
- Logging integration with configurable loggers
|
|
51
|
+
|
|
52
|
+
## v1.5.0
|
|
53
|
+
|
|
54
|
+
- Added command argument parsing (ctx.command_args)
|
|
55
|
+
- Message entity parsing (mentions, hashtags, bot commands)
|
|
56
|
+
- Chat member update handlers
|
|
57
|
+
- Pre-checkout and shipping query support
|
|
58
|
+
- File download helper methods
|
|
59
|
+
- Context helper methods for common API calls
|
|
60
|
+
|
|
61
|
+
## v1.0.0
|
|
62
|
+
|
|
63
|
+
- Stable API release
|
|
64
|
+
- Message handlers with text pattern matching
|
|
65
|
+
- Command handlers with regex support
|
|
66
|
+
- Basic context object with chat/message accessors
|
|
67
|
+
- Simple API client with error handling
|
|
68
|
+
- Polling and webhook modes
|
|
69
|
+
- Configuration options for timeout and limits
|
|
70
|
+
|
|
71
|
+
## v0.5.0
|
|
72
|
+
|
|
73
|
+
- Added callback query support
|
|
74
|
+
- Inline keyboard builder
|
|
75
|
+
- Message editing and deletion helpers
|
|
76
|
+
- Media sending methods (photo, document, audio, video)
|
|
77
|
+
- Chat action methods (typing, upload indicators)
|
|
78
|
+
|
|
79
|
+
## v0.3.0
|
|
80
|
+
|
|
81
|
+
- Middleware system with bot.use
|
|
82
|
+
- Session management foundation
|
|
83
|
+
- Basic rate limiting
|
|
84
|
+
- Command filtering by chat type
|
|
85
|
+
- Improved logging with debug levels
|
|
86
|
+
|
|
87
|
+
## v0.2.0
|
|
88
|
+
|
|
89
|
+
- Basic polling implementation
|
|
90
|
+
- Message type detection (text, photo, document)
|
|
91
|
+
- Command parsing with arguments
|
|
92
|
+
- Simple reply methods
|
|
93
|
+
- Error handling for API calls
|
|
94
|
+
|
|
95
|
+
## v0.1.0 (Initial Release)
|
|
96
|
+
|
|
97
|
+
- Basic Telegram Bot API wrapper
|
|
98
|
+
- Send/receive messages
|
|
99
|
+
- Simple command handling
|
|
100
|
+
- Minimal dependencies (just httparty)
|
|
101
|
+
- Support for basic message types
|
|
102
|
+
|
|
103
|
+
---
|
data/Gemfile
CHANGED
data/Readme.md
CHANGED
|
@@ -4,10 +4,6 @@ Modern, blazing-fast async Telegram Bot API for Ruby - Inspired by Telegraf, bui
|
|
|
4
4
|
|
|
5
5
|
    
|
|
6
6
|
|
|
7
|
-

|
|
8
|
-

|
|
9
|
-

|
|
10
|
-

|
|
11
7
|
|
|
12
8
|
|
|
13
9
|
|
|
@@ -299,4 +295,4 @@ ruby -r telegem -e "puts 'Welcome to Telegem! 🚀'"
|
|
|
299
295
|
|
|
300
296
|
---
|
|
301
297
|
|
|
302
|
-
Built with ❤️ for the Ruby community. Happy bot building! 🤖✨
|
|
298
|
+
Built with ❤️ for the Ruby community. Happy bot building! 🤖✨
|
data/bin/telegem-init
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# bin/telegem-init
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
puts " Creating a telegem application..."
|
|
7
|
+
|
|
8
|
+
# Create the nested directory structure
|
|
9
|
+
FileUtils.mkdir_p("src/handlers")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
code = <<~RUBY
|
|
13
|
+
require 'telegem'
|
|
14
|
+
require 'dotenv/load'
|
|
15
|
+
|
|
16
|
+
# Load all handlers from the handlers directory relative to this file
|
|
17
|
+
Dir[File.join(__dir__, 'handlers', '*.rb')].each { |file| require file }
|
|
18
|
+
|
|
19
|
+
bot = Telegem.new(ENV['BOT_TOKEN'])
|
|
20
|
+
|
|
21
|
+
puts "Bot is starting..."
|
|
22
|
+
bot.start_polling
|
|
23
|
+
RUBY
|
|
24
|
+
|
|
25
|
+
File.write('src/bot.rb', code)
|
|
26
|
+
|
|
27
|
+
# Optional: Create a blank .env file so the bot doesn't crash on load
|
|
28
|
+
File.write('.env', "BOT_TOKEN=your_token_here") unless File.exist?('.env')
|
|
29
|
+
|
|
30
|
+
puts "Successfully created src/bot.rb"
|
|
31
|
+
puts " Structure created: src/handlers/"
|
|
32
|
+
puts " Done working. Run your bot with: ruby src/bot.rb"
|
data/lib/api/types.rb
CHANGED
|
@@ -62,6 +62,21 @@ module Telegem
|
|
|
62
62
|
@_accessors_defined[name] = true
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# helpers for converting nested objects
|
|
66
|
+
def wrap(key, klass)
|
|
67
|
+
if @_raw_data[key] && !@_raw_data[key].is_a?(klass)
|
|
68
|
+
@_raw_data[key] = klass.new(@_raw_data[key])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def wrap_array(key, klass)
|
|
73
|
+
if @_raw_data[key] && @_raw_data[key].is_a?(Array)
|
|
74
|
+
@_raw_data[key] = @_raw_data[key].map do |v|
|
|
75
|
+
v.is_a?(klass) ? v : klass.new(v)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
65
80
|
def snake_to_camel(str)
|
|
66
81
|
str.gsub(/_([a-z])/) { $1.upcase }
|
|
67
82
|
end
|
|
@@ -250,84 +265,104 @@ module Telegem
|
|
|
250
265
|
private
|
|
251
266
|
|
|
252
267
|
def convert_complex_fields
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
268
|
+
# time conversions
|
|
269
|
+
@_raw_data['date'] = Time.at(@_raw_data['date']) if @_raw_data['date'] && !@_raw_data['date'].is_a?(Time)
|
|
270
|
+
@_raw_data['edit_date'] = Time.at(@_raw_data['edit_date']) if @_raw_data['edit_date'] && !@_raw_data['edit_date'].is_a?(Time)
|
|
271
|
+
@_raw_data['forward_date'] = Time.at(@_raw_data['forward_date']) if @_raw_data['forward_date'] && !@_raw_data['forward_date'].is_a?(Time)
|
|
272
|
+
|
|
273
|
+
# basic object wrappers
|
|
274
|
+
wrap('from', User)
|
|
275
|
+
wrap('chat', Chat)
|
|
276
|
+
wrap('via_bot', User)
|
|
277
|
+
wrap('forward_from', User)
|
|
278
|
+
wrap('forward_from_chat', Chat)
|
|
279
|
+
wrap('left_chat_member', User)
|
|
280
|
+
|
|
281
|
+
wrap_array('entities', MessageEntity)
|
|
282
|
+
wrap_array('caption_entities', MessageEntity)
|
|
283
|
+
|
|
284
|
+
wrap('reply_to_message', Message)
|
|
285
|
+
wrap('pinned_message', Message)
|
|
286
|
+
wrap_array('new_chat_members', User)
|
|
287
|
+
|
|
288
|
+
# media and other nested types
|
|
289
|
+
wrap('contact', Contact)
|
|
290
|
+
wrap('location', Location)
|
|
291
|
+
wrap('venue', Venue)
|
|
292
|
+
wrap('dice', Dice)
|
|
293
|
+
wrap('poll', Poll)
|
|
294
|
+
wrap('proximity_alert_triggered', ProximityAlertTriggered)
|
|
295
|
+
wrap('web_app_data', WebAppData)
|
|
296
|
+
|
|
297
|
+
wrap('animation', Animation)
|
|
298
|
+
wrap('audio', Audio)
|
|
299
|
+
wrap('document', Document)
|
|
300
|
+
wrap('video', Video)
|
|
301
|
+
wrap('voice', Voice)
|
|
302
|
+
wrap('video_note', VideoNote)
|
|
303
|
+
wrap('sticker', Sticker)
|
|
304
|
+
|
|
305
|
+
wrap('invoice', Invoice)
|
|
306
|
+
wrap('successful_payment', SuccessfulPayment)
|
|
307
|
+
wrap('reply_markup', BaseType)
|
|
308
|
+
|
|
309
|
+
wrap('passport_data', PassportData)
|
|
310
|
+
|
|
311
|
+
wrap('video_chat_scheduled', VideoChatScheduled)
|
|
312
|
+
wrap('video_chat_started', VideoChatStarted)
|
|
313
|
+
wrap('video_chat_ended', VideoChatEnded)
|
|
314
|
+
wrap('video_chat_participants_invited', VideoChatParticipantsInvited)
|
|
315
|
+
wrap('video_chat_location', VideoChatLocation)
|
|
316
|
+
|
|
317
|
+
# new message event objects introduced in later API versions
|
|
318
|
+
wrap('message_auto_delete_timer_changed', MessageAutoDeleteTimerChanged)
|
|
319
|
+
wrap('forum_topic_created', ForumTopicCreated)
|
|
320
|
+
wrap('forum_topic_edited', ForumTopicEdited)
|
|
321
|
+
wrap('forum_topic_closed', ForumTopicClosed)
|
|
322
|
+
wrap('forum_topic_reopened', ForumTopicReopened)
|
|
323
|
+
wrap('general_forum_topic_hidden', GeneralForumTopicHidden)
|
|
324
|
+
wrap('general_forum_topic_unhidden', GeneralForumTopicUnhidden)
|
|
325
|
+
wrap('write_access_allowed', WriteAccessAllowed)
|
|
326
|
+
|
|
327
|
+
# arrays of sizes and photos
|
|
328
|
+
wrap_array('photo', PhotoSize)
|
|
329
|
+
wrap_array('new_chat_photo', PhotoSize)
|
|
330
|
+
|
|
331
|
+
# fall back to original media wrapper for backward compatibility
|
|
298
332
|
wrap_media_objects
|
|
299
333
|
end
|
|
300
334
|
|
|
301
335
|
def wrap_media_objects
|
|
302
|
-
# Media files
|
|
303
|
-
@_raw_data['document'] =
|
|
304
|
-
@_raw_data['
|
|
305
|
-
@_raw_data['
|
|
306
|
-
@_raw_data['
|
|
307
|
-
@_raw_data['
|
|
308
|
-
@_raw_data['
|
|
309
|
-
|
|
336
|
+
# Media files (fall‑back to generic types if no specific class defined)
|
|
337
|
+
@_raw_data['document'] = Document.new(@_raw_data['document']) if @_raw_data['document'] && !@_raw_data['document'].is_a?(Document)
|
|
338
|
+
@_raw_data['animation'] = Animation.new(@_raw_data['animation']) if @_raw_data['animation'] && !@_raw_data['animation'].is_a?(Animation)
|
|
339
|
+
@_raw_data['audio'] = Audio.new(@_raw_data['audio']) if @_raw_data['audio'] && !@_raw_data['audio'].is_a?(Audio)
|
|
340
|
+
@_raw_data['video'] = Video.new(@_raw_data['video']) if @_raw_data['video'] && !@_raw_data['video'].is_a?(Video)
|
|
341
|
+
@_raw_data['voice'] = Voice.new(@_raw_data['voice']) if @_raw_data['voice'] && !@_raw_data['voice'].is_a?(Voice)
|
|
342
|
+
@_raw_data['video_note'] = VideoNote.new(@_raw_data['video_note']) if @_raw_data['video_note'] && !@_raw_data['video_note'].is_a?(VideoNote)
|
|
343
|
+
@_raw_data['sticker'] = Sticker.new(@_raw_data['sticker']) if @_raw_data['sticker'] && !@_raw_data['sticker'].is_a?(Sticker)
|
|
344
|
+
|
|
310
345
|
# Photo array
|
|
311
346
|
if @_raw_data['photo'] && @_raw_data['photo'].is_a?(Array)
|
|
312
347
|
@_raw_data['photo'] = @_raw_data['photo'].map do |p|
|
|
313
|
-
p.is_a?(
|
|
348
|
+
p.is_a?(PhotoSize) ? p : PhotoSize.new(p)
|
|
314
349
|
end
|
|
315
350
|
end
|
|
316
|
-
|
|
351
|
+
|
|
317
352
|
# Contact, location, venue
|
|
318
|
-
@_raw_data['contact'] =
|
|
319
|
-
@_raw_data['location'] =
|
|
320
|
-
@_raw_data['venue'] =
|
|
321
|
-
|
|
353
|
+
@_raw_data['contact'] = Contact.new(@_raw_data['contact']) if @_raw_data['contact'] && !@_raw_data['contact'].is_a?(Contact)
|
|
354
|
+
@_raw_data['location'] = Location.new(@_raw_data['location']) if @_raw_data['location'] && !@_raw_data['location'].is_a?(Location)
|
|
355
|
+
@_raw_data['venue'] = Venue.new(@_raw_data['venue']) if @_raw_data['venue'] && !@_raw_data['venue'].is_a?(Venue)
|
|
356
|
+
|
|
322
357
|
# Payment & other
|
|
323
|
-
@_raw_data['invoice'] =
|
|
324
|
-
@_raw_data['successful_payment'] =
|
|
358
|
+
@_raw_data['invoice'] = Invoice.new(@_raw_data['invoice']) if @_raw_data['invoice'] && !@_raw_data['invoice'].is_a?(Invoice)
|
|
359
|
+
@_raw_data['successful_payment'] = SuccessfulPayment.new(@_raw_data['successful_payment']) if @_raw_data['successful_payment'] && !@_raw_data['successful_payment'].is_a?(SuccessfulPayment)
|
|
325
360
|
@_raw_data['reply_markup'] = BaseType.new(@_raw_data['reply_markup']) if @_raw_data['reply_markup'] && !@_raw_data['reply_markup'].is_a?(BaseType)
|
|
326
|
-
|
|
361
|
+
|
|
327
362
|
# Chat photo array
|
|
328
363
|
if @_raw_data['new_chat_photo'] && @_raw_data['new_chat_photo'].is_a?(Array)
|
|
329
364
|
@_raw_data['new_chat_photo'] = @_raw_data['new_chat_photo'].map do |p|
|
|
330
|
-
p.is_a?(
|
|
365
|
+
p.is_a?(PhotoSize) ? p : PhotoSize.new(p)
|
|
331
366
|
end
|
|
332
367
|
end
|
|
333
368
|
end
|
|
@@ -428,15 +463,218 @@ module Telegem
|
|
|
428
463
|
private
|
|
429
464
|
|
|
430
465
|
def convert_update_objects
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
466
|
+
wrap('message', Message)
|
|
467
|
+
wrap('edited_message', Message)
|
|
468
|
+
wrap('channel_post', Message)
|
|
469
|
+
wrap('edited_channel_post', Message)
|
|
470
|
+
|
|
471
|
+
wrap('inline_query', InlineQuery)
|
|
472
|
+
wrap('chosen_inline_result', ChosenInlineResult)
|
|
473
|
+
wrap('callback_query', CallbackQuery)
|
|
474
|
+
wrap('shipping_query', ShippingQuery)
|
|
475
|
+
wrap('pre_checkout_query', PreCheckoutQuery)
|
|
476
|
+
wrap('poll', Poll)
|
|
477
|
+
wrap('poll_answer', PollAnswer)
|
|
478
|
+
wrap('my_chat_member', ChatMemberUpdated)
|
|
479
|
+
wrap('chat_member', ChatMemberUpdated)
|
|
480
|
+
wrap('chat_join_request', ChatJoinRequest)
|
|
481
|
+
wrap('forum_topic_created', ForumTopicCreated)
|
|
482
|
+
wrap('forum_topic_edited', ForumTopicEdited)
|
|
483
|
+
wrap('forum_topic_closed', ForumTopicClosed)
|
|
484
|
+
wrap('forum_topic_reopened', ForumTopicReopened)
|
|
485
|
+
wrap('general_forum_topic_hidden', GeneralForumTopicHidden)
|
|
486
|
+
wrap('general_forum_topic_unhidden', GeneralForumTopicUnhidden)
|
|
487
|
+
wrap('write_access_allowed', WriteAccessAllowed)
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# additional types returned by various methods / updates
|
|
492
|
+
class PhotoSize < BaseType; end
|
|
493
|
+
class Audio < BaseType; end
|
|
494
|
+
class Document < BaseType; end
|
|
495
|
+
class Video < BaseType; end
|
|
496
|
+
class Voice < BaseType; end
|
|
497
|
+
class VideoNote < BaseType; end
|
|
498
|
+
class Animation < BaseType; end
|
|
499
|
+
class Sticker < BaseType; end
|
|
500
|
+
class Contact < BaseType; end
|
|
501
|
+
class Dice < BaseType; end
|
|
502
|
+
|
|
503
|
+
class Location < BaseType; end
|
|
504
|
+
class Venue < BaseType; end
|
|
505
|
+
class ProximityAlertTriggered < BaseType; end
|
|
506
|
+
class WebAppData < BaseType; end
|
|
507
|
+
class PassportData < BaseType; end
|
|
508
|
+
|
|
509
|
+
class Invoice < BaseType; end
|
|
510
|
+
class SuccessfulPayment < BaseType; end
|
|
511
|
+
class ShippingAddress < BaseType; end
|
|
512
|
+
class OrderInfo < BaseType; end
|
|
513
|
+
|
|
514
|
+
class ShippingQuery < BaseType
|
|
515
|
+
def initialize(data)
|
|
516
|
+
super(data)
|
|
517
|
+
wrap('from', User)
|
|
518
|
+
wrap('shipping_address', ShippingAddress)
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
class PreCheckoutQuery < BaseType
|
|
523
|
+
def initialize(data)
|
|
524
|
+
super(data)
|
|
525
|
+
wrap('from', User)
|
|
526
|
+
wrap('shipping_address', ShippingAddress)
|
|
527
|
+
wrap('order_info', OrderInfo)
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
class PollOption < BaseType; end
|
|
532
|
+
class PollAnswer < BaseType; end
|
|
533
|
+
|
|
534
|
+
class Poll < BaseType
|
|
535
|
+
def initialize(data)
|
|
536
|
+
super(data)
|
|
537
|
+
wrap_array('options', PollOption)
|
|
538
|
+
wrap_array('explanation_entities', MessageEntity)
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
class ChatPermissions < BaseType; end
|
|
543
|
+
class ChatPhoto < BaseType; end
|
|
544
|
+
class ChatInviteLink < BaseType; end
|
|
545
|
+
|
|
546
|
+
# status-specific chat member objects. they inherit from ChatMember
|
|
547
|
+
class ChatMember < BaseType; end
|
|
548
|
+
class ChatMemberOwner < ChatMember; end
|
|
549
|
+
class ChatMemberAdministrator < ChatMember; end
|
|
550
|
+
class ChatMemberMember < ChatMember; end
|
|
551
|
+
class ChatMemberRestricted < ChatMember; end
|
|
552
|
+
class ChatMemberLeft < ChatMember; end
|
|
553
|
+
class ChatMemberBanned < ChatMember; end
|
|
554
|
+
|
|
555
|
+
class ChatAdministratorRights < BaseType; end
|
|
556
|
+
|
|
557
|
+
class ChatMemberUpdated < BaseType
|
|
558
|
+
def initialize(data)
|
|
559
|
+
super(data)
|
|
560
|
+
wrap('chat', Chat)
|
|
561
|
+
wrap('from', User)
|
|
562
|
+
wrap_member('old_chat_member')
|
|
563
|
+
wrap_member('new_chat_member')
|
|
564
|
+
wrap('invite_link', ChatInviteLink)
|
|
565
|
+
if @_raw_data['date'] && !@_raw_data['date'].is_a?(Time)
|
|
566
|
+
@_raw_data['date'] = Time.at(@_raw_data['date'])
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
private
|
|
571
|
+
|
|
572
|
+
def wrap_member(key)
|
|
573
|
+
return unless @_raw_data[key]
|
|
574
|
+
status = @_raw_data[key]['status']
|
|
575
|
+
klass = case status
|
|
576
|
+
when 'creator' then ChatMemberOwner
|
|
577
|
+
when 'administrator' then ChatMemberAdministrator
|
|
578
|
+
when 'member' then ChatMemberMember
|
|
579
|
+
when 'restricted' then ChatMemberRestricted
|
|
580
|
+
when 'left' then ChatMemberLeft
|
|
581
|
+
when 'kicked' then ChatMemberBanned
|
|
582
|
+
else ChatMember
|
|
583
|
+
end
|
|
584
|
+
@_raw_data[key] = klass.new(@_raw_data[key])
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
class ChatJoinRequest < BaseType
|
|
589
|
+
def initialize(data)
|
|
590
|
+
super(data)
|
|
591
|
+
wrap('chat', Chat)
|
|
592
|
+
wrap('from', User)
|
|
593
|
+
wrap('invite_link', ChatInviteLink)
|
|
594
|
+
if @_raw_data['date'] && !@_raw_data['date'].is_a?(Time)
|
|
595
|
+
@_raw_data['date'] = Time.at(@_raw_data['date'])
|
|
438
596
|
end
|
|
439
597
|
end
|
|
440
598
|
end
|
|
599
|
+
|
|
600
|
+
class InlineQuery < BaseType
|
|
601
|
+
def initialize(data)
|
|
602
|
+
super(data)
|
|
603
|
+
wrap('from', User)
|
|
604
|
+
wrap('location', Location)
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
class ChosenInlineResult < BaseType
|
|
609
|
+
def initialize(data)
|
|
610
|
+
super(data)
|
|
611
|
+
wrap('from', User)
|
|
612
|
+
wrap('location', Location)
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
class InlineQueryResult < BaseType; end
|
|
617
|
+
class InlineQueryResultArticle < InlineQueryResult; end
|
|
618
|
+
class InlineQueryResultPhoto < InlineQueryResult; end
|
|
619
|
+
class InlineQueryResultGif < InlineQueryResult; end
|
|
620
|
+
class InlineQueryResultMpeg4Gif < InlineQueryResult; end
|
|
621
|
+
class InlineQueryResultVideo < InlineQueryResult; end
|
|
622
|
+
class InlineQueryResultAudio < InlineQueryResult; end
|
|
623
|
+
class InlineQueryResultVoice < InlineQueryResult; end
|
|
624
|
+
class InlineQueryResultDocument < InlineQueryResult; end
|
|
625
|
+
class InlineQueryResultLocation < InlineQueryResult; end
|
|
626
|
+
class InlineQueryResultVenue < InlineQueryResult; end
|
|
627
|
+
class InlineQueryResultContact < InlineQueryResult; end
|
|
628
|
+
class InlineQueryResultGame < InlineQueryResult; end
|
|
629
|
+
class InlineQueryResultSticker < InlineQueryResult; end
|
|
630
|
+
class InlineQueryResultCachedPhoto < InlineQueryResult; end
|
|
631
|
+
class InlineQueryResultCachedGif < InlineQueryResult; end
|
|
632
|
+
class InlineQueryResultCachedMpeg4Gif < InlineQueryResult; end
|
|
633
|
+
class InlineQueryResultCachedSticker < InlineQueryResult; end
|
|
634
|
+
class InlineQueryResultCachedDocument < InlineQueryResult; end
|
|
635
|
+
class InlineQueryResultCachedVideo < InlineQueryResult; end
|
|
636
|
+
class InlineQueryResultCachedAudio < InlineQueryResult; end
|
|
637
|
+
class InlineQueryResultCachedVoice < InlineQueryResult; end
|
|
638
|
+
|
|
639
|
+
class UserProfilePhotos < BaseType
|
|
640
|
+
def initialize(data)
|
|
641
|
+
super(data)
|
|
642
|
+
wrap_array('photos', PhotoSize)
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
class UserProfileAudios < BaseType
|
|
647
|
+
def initialize(data)
|
|
648
|
+
super(data)
|
|
649
|
+
wrap_array('audios', Audio)
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
# generic utility objects returned by the API
|
|
654
|
+
class File < BaseType; end
|
|
655
|
+
class ResponseParameters < BaseType; end
|
|
656
|
+
class MaskPosition < BaseType; end
|
|
657
|
+
class StickerSet < BaseType; end
|
|
658
|
+
|
|
659
|
+
# new message event payloads (each a simple wrapper)
|
|
660
|
+
class MessageAutoDeleteTimerChanged < BaseType; end
|
|
661
|
+
class ForumTopicCreated < BaseType; end
|
|
662
|
+
class ForumTopicEdited < BaseType; end
|
|
663
|
+
class ForumTopicClosed < BaseType; end
|
|
664
|
+
class ForumTopicReopened < BaseType; end
|
|
665
|
+
class GeneralForumTopicHidden < BaseType; end
|
|
666
|
+
class GeneralForumTopicUnhidden < BaseType; end
|
|
667
|
+
class WriteAccessAllowed < BaseType; end
|
|
668
|
+
|
|
669
|
+
class BotCommand < BaseType; end
|
|
670
|
+
class BotCommandScope < BaseType; end
|
|
671
|
+
class WebhookInfo < BaseType; end
|
|
672
|
+
|
|
673
|
+
class VideoChatScheduled < BaseType; end
|
|
674
|
+
class VideoChatStarted < BaseType; end
|
|
675
|
+
class VideoChatEnded < BaseType; end
|
|
676
|
+
class VideoChatParticipantsInvited < BaseType; end
|
|
677
|
+
class VideoChatLocation < BaseType; end
|
|
678
|
+
|
|
441
679
|
end
|
|
442
680
|
end
|
data/lib/plugins/file_extract.rb
CHANGED
data/lib/session/memory_store.rb
CHANGED
|
@@ -1,125 +1,108 @@
|
|
|
1
|
-
# lib/session/memory_store.rb
|
|
1
|
+
# lib/session/memory_store.rb
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'time'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
2
6
|
module Telegem
|
|
3
7
|
module Session
|
|
4
8
|
class MemoryStore
|
|
5
|
-
def initialize
|
|
9
|
+
def initialize(
|
|
10
|
+
default_ttl: 300,
|
|
11
|
+
cleanup_interval: 300,
|
|
12
|
+
backup_path: nil,
|
|
13
|
+
backup_interval: 60
|
|
14
|
+
)
|
|
6
15
|
@store = {}
|
|
7
16
|
@ttls = {}
|
|
8
|
-
@default_ttl =
|
|
9
|
-
@cleanup_interval =
|
|
17
|
+
@default_ttl = default_ttl
|
|
18
|
+
@cleanup_interval = cleanup_interval
|
|
19
|
+
@backup_path = backup_path
|
|
20
|
+
@backup_interval = backup_interval
|
|
21
|
+
|
|
10
22
|
@last_cleanup = Time.now
|
|
23
|
+
@last_backup = Time.now
|
|
24
|
+
|
|
25
|
+
restore! if @backup_path && File.exist?(@backup_path)
|
|
11
26
|
end
|
|
12
27
|
|
|
13
|
-
# Store with optional TTL
|
|
14
28
|
def set(key, value, ttl: nil)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@store[key_s] = value
|
|
18
|
-
@ttls[key_s] = Time.now + (ttl || @default_ttl)
|
|
19
|
-
value
|
|
20
|
-
end
|
|
29
|
+
auto_cleanup
|
|
30
|
+
key_s = key.to_s
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
key_s = key.to_s
|
|
25
|
-
return nil unless @store.key?(key_s)
|
|
26
|
-
|
|
27
|
-
# Auto-clean if expired
|
|
28
|
-
if expired?(key_s)
|
|
29
|
-
delete(key_s)
|
|
30
|
-
return nil
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
@store[key_s]
|
|
34
|
-
end
|
|
32
|
+
@store[key_s] = value
|
|
33
|
+
@ttls[key_s] = Time.now + (ttl || @default_ttl)
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return false unless @store.key?(key_s)
|
|
40
|
-
!expired?(key_s)
|
|
41
|
-
end
|
|
35
|
+
auto_backup
|
|
36
|
+
value
|
|
37
|
+
end
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@store.delete(key_s)
|
|
47
|
-
@ttls.delete(key_s)
|
|
48
|
-
true
|
|
49
|
-
end
|
|
39
|
+
def get(key)
|
|
40
|
+
key_s = key.to_s
|
|
41
|
+
return nil unless @store.key?(key_s)
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
current = get(key_s) || 0
|
|
55
|
-
new_value = current + amount
|
|
56
|
-
set(key_s, new_value, ttl: ttl)
|
|
57
|
-
new_value
|
|
43
|
+
if expired?(key_s)
|
|
44
|
+
delete(key_s)
|
|
45
|
+
return nil
|
|
58
46
|
end
|
|
59
47
|
|
|
60
|
-
|
|
61
|
-
def decrement(key, amount = 1)
|
|
62
|
-
increment(key, -amount)
|
|
48
|
+
@store[key_s]
|
|
63
49
|
end
|
|
64
50
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@ttls.delete(key)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
@last_cleanup = now
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Clear everything
|
|
78
|
-
def clear
|
|
79
|
-
@store.clear
|
|
80
|
-
@ttls.clear
|
|
81
|
-
@last_cleanup = Time.now
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Get all keys (non-expired)
|
|
85
|
-
def keys
|
|
86
|
-
auto_cleanup
|
|
87
|
-
@store.keys.select { |k| !expired?(k) }
|
|
88
|
-
end
|
|
51
|
+
def delete(key)
|
|
52
|
+
key_s = key.to_s
|
|
53
|
+
@store.delete(key_s)
|
|
54
|
+
@ttls.delete(key_s)
|
|
55
|
+
true
|
|
56
|
+
end
|
|
89
57
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
58
|
+
def increment(key, amount = 1, ttl: nil)
|
|
59
|
+
current = get(key) || 0
|
|
60
|
+
# Ensure we are working with numbers
|
|
61
|
+
val = current.to_i rescue 0
|
|
62
|
+
new_val = val + amount
|
|
63
|
+
set(key, new_val, ttl: ttl)
|
|
64
|
+
new_val
|
|
93
65
|
end
|
|
94
66
|
|
|
95
|
-
|
|
96
|
-
|
|
67
|
+
# --- Persistence Logic (The "Telecr" Way) ---
|
|
68
|
+
|
|
69
|
+
def backup!
|
|
70
|
+
return unless @backup_path
|
|
71
|
+
|
|
72
|
+
# 1. Prepare data
|
|
73
|
+
data = {
|
|
74
|
+
"store" => @store,
|
|
75
|
+
"ttls" => @ttls.transform_values(&:to_i), # Save as Unix timestamp
|
|
76
|
+
"timestamp" => Time.now.to_i
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# 2. Ensure directory exists
|
|
80
|
+
FileUtils.mkdir_p(File.dirname(@backup_path))
|
|
81
|
+
|
|
82
|
+
# 3. ATOMIC WRITE: Write to temp, then rename
|
|
83
|
+
temp_path = "#{@backup_path}.tmp"
|
|
84
|
+
File.write(temp_path, JSON.generate(data))
|
|
85
|
+
File.rename(temp_path, @backup_path)
|
|
86
|
+
|
|
87
|
+
@last_backup = Time.now
|
|
97
88
|
end
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
key_s = key.to_s
|
|
102
|
-
return -1 unless @ttls[key_s]
|
|
103
|
-
|
|
104
|
-
remaining = @ttls[key_s] - Time.now
|
|
105
|
-
remaining > 0 ? remaining.ceil : -1
|
|
106
|
-
end
|
|
90
|
+
def restore!
|
|
91
|
+
return unless @backup_path && File.exist?(@backup_path)
|
|
107
92
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
key_s = key.to_s
|
|
111
|
-
return false unless @store.key?(key_s)
|
|
93
|
+
begin
|
|
94
|
+
raw = JSON.parse(File.read(@backup_path))
|
|
112
95
|
|
|
113
|
-
@
|
|
114
|
-
|
|
115
|
-
end
|
|
96
|
+
@store.clear
|
|
97
|
+
@ttls.clear
|
|
116
98
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
99
|
+
raw["store"].each { |k, v| @store[k] = v }
|
|
100
|
+
raw["ttls"].each do |k, v|
|
|
101
|
+
@ttls[k] = Time.at(v)
|
|
102
|
+
end
|
|
103
|
+
rescue => e
|
|
104
|
+
warn "Telegem: Failed to restore backup: #{e.message}"
|
|
105
|
+
end
|
|
123
106
|
end
|
|
124
107
|
|
|
125
108
|
private
|
|
@@ -129,15 +112,19 @@ module Telegem
|
|
|
129
112
|
end
|
|
130
113
|
|
|
131
114
|
def auto_cleanup
|
|
132
|
-
if Time.now - @last_cleanup > @cleanup_interval
|
|
133
|
-
|
|
115
|
+
if (Time.now - @last_cleanup) > @cleanup_interval
|
|
116
|
+
now = Time.now
|
|
117
|
+
expired_keys = @ttls.select { |_, expires| now > expires }.keys
|
|
118
|
+
expired_keys.each { |k| delete(k) }
|
|
119
|
+
@last_cleanup = now
|
|
134
120
|
end
|
|
135
121
|
end
|
|
136
122
|
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
|
|
123
|
+
def auto_backup
|
|
124
|
+
if @backup_path && (Time.now - @last_backup) > @backup_interval
|
|
125
|
+
backup!
|
|
126
|
+
end
|
|
140
127
|
end
|
|
141
128
|
end
|
|
142
129
|
end
|
|
143
|
-
end
|
|
130
|
+
end
|
data/lib/telegem.rb
CHANGED
|
@@ -3,7 +3,7 @@ require 'logger'
|
|
|
3
3
|
require 'json'
|
|
4
4
|
|
|
5
5
|
module Telegem
|
|
6
|
-
VERSION = "3.3.
|
|
6
|
+
VERSION = "3.3.1".freeze
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
#
|
|
@@ -78,4 +78,4 @@ if ENV['TELEGEM_GLOBAL'] == 'true'
|
|
|
78
78
|
def Telegem(token, **options)
|
|
79
79
|
::Telegem.new(token, **options)
|
|
80
80
|
end
|
|
81
|
-
end
|
|
81
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: telegem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.3.
|
|
4
|
+
version: 3.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- zendrx
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-04-24 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: concurrent-ruby
|
|
@@ -129,10 +130,11 @@ email:
|
|
|
129
130
|
- ynwghosted@icloud.com
|
|
130
131
|
executables:
|
|
131
132
|
- telegem-ssl
|
|
133
|
+
- telegem-init
|
|
132
134
|
extensions: []
|
|
133
135
|
extra_rdoc_files: []
|
|
134
136
|
files:
|
|
135
|
-
- CHANGELOG
|
|
137
|
+
- CHANGELOG.md
|
|
136
138
|
- CODE_OF_CONDUCT.md
|
|
137
139
|
- Contributing.md
|
|
138
140
|
- Gemfile
|
|
@@ -143,6 +145,7 @@ files:
|
|
|
143
145
|
- assets/.gitkeep
|
|
144
146
|
- assets/logo.png
|
|
145
147
|
- bin/.gitkeep
|
|
148
|
+
- bin/telegem-init
|
|
146
149
|
- bin/telegem-ssl
|
|
147
150
|
- docs/.gitkeep
|
|
148
151
|
- docs/ctx.md
|
|
@@ -167,18 +170,18 @@ files:
|
|
|
167
170
|
- lib/webhook/.gitkeep
|
|
168
171
|
- lib/webhook/server.rb
|
|
169
172
|
- public/.gitkeep
|
|
170
|
-
homepage: https://
|
|
173
|
+
homepage: https://github.com/slick-lab/telegem
|
|
171
174
|
licenses:
|
|
172
175
|
- MIT
|
|
173
176
|
metadata:
|
|
174
|
-
homepage_uri: https://
|
|
175
|
-
source_code_uri: https://
|
|
176
|
-
changelog_uri: https://
|
|
177
|
-
bug_tracker_uri: https://
|
|
177
|
+
homepage_uri: https://github.com/slick-lab/telegem/-/blob/main/README.md
|
|
178
|
+
source_code_uri: https://github.com/slick-lab/telegem
|
|
179
|
+
changelog_uri: https://github.com/slick-lab/telegem/-/blob/main/CHANGELOG.md
|
|
180
|
+
bug_tracker_uri: https://github.com/slick-lab/telegem/-/issues
|
|
178
181
|
documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/tree/main/docs-src?ref_type=heads
|
|
179
182
|
rubygems_mfa_required: 'false'
|
|
180
|
-
post_install_message: "Thanks for installing Telegem 3.3.
|
|
181
|
-
https://
|
|
183
|
+
post_install_message: "Thanks for installing Telegem 3.3.1!\n\n\U0001F4DA Documentation:
|
|
184
|
+
https://github.com/slick-lab/telegem\n\n\U0001F510 For SSL Webhooks:\nRun: telegem-ssl
|
|
182
185
|
your-domain.com\nThis sets up Let's Encrypt certificates automatically.\n\n\U0001F916
|
|
183
186
|
Happy bot building!\n"
|
|
184
187
|
rdoc_options: []
|
|
@@ -195,7 +198,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
195
198
|
- !ruby/object:Gem::Version
|
|
196
199
|
version: '0'
|
|
197
200
|
requirements: []
|
|
198
|
-
rubygems_version:
|
|
201
|
+
rubygems_version: 3.5.22
|
|
202
|
+
signing_key:
|
|
199
203
|
specification_version: 4
|
|
200
204
|
summary: Modern, fast Telegram Bot Framework for Ruby
|
|
201
205
|
test_files: []
|
data/CHANGELOG
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
Telegem Changelog
|
|
2
|
-
|
|
3
|
-
v3.1.1 (current)
|
|
4
|
-
|
|
5
|
-
🚀 New Features
|
|
6
|
-
|
|
7
|
-
· FileExtractor Plugin: New plugin for extracting content from various file types (PDF, JSON, HTML, TXT)
|
|
8
|
-
· Async File Download: Added download method to API client for downloading Telegram files
|
|
9
|
-
· Context File Helpers: Added download_file, download_photo, download_document methods to Context
|
|
10
|
-
· Extended File Support: Plugin supports PDF text extraction, JSON parsing, HTML/raw text processing
|
|
11
|
-
· Async/Sync Dual Mode: All file operations available in both sync (download) and async (download!) modes
|
|
12
|
-
|
|
13
|
-
v3.1.0
|
|
14
|
-
|
|
15
|
-
· BREAKING: Rewrote polling system to prevent duplicate messages
|
|
16
|
-
· Fixed thread deadlock in async polling loop
|
|
17
|
-
· Added scene_middleware.rb for scene-based conversations
|
|
18
|
-
· Improved MemoryStore with TTL and thread safety
|
|
19
|
-
· Enhanced keyboard markup builder with web_app support
|
|
20
|
-
· Added message reaction and chat boost update types
|
|
21
|
-
· Fixed callback query handling for inline keyboards
|
|
22
|
-
|
|
23
|
-
v3.0.0
|
|
24
|
-
|
|
25
|
-
· BREAKING: Complete async rewrite with async gem
|
|
26
|
-
· New HTTP client using HTTPX with proper async/await pattern
|
|
27
|
-
· Added scene system for multi-step conversations
|
|
28
|
-
· Middleware composer system for plugin architecture
|
|
29
|
-
· Type system with dynamic accessors for Telegram objects
|
|
30
|
-
· Session management with memory store
|
|
31
|
-
· Rate limiting middleware
|
|
32
|
-
· File upload support via multipart forms
|
|
33
|
-
|
|
34
|
-
v2.0.0
|
|
35
|
-
|
|
36
|
-
· BREAKING: Ruby 3.0+ requirement
|
|
37
|
-
· Added webhook support with Rack middleware
|
|
38
|
-
· Inline query and callback query handlers
|
|
39
|
-
· Location, contact, and poll answer handlers
|
|
40
|
-
· Keyboard markup helpers (Telegem::Markup)
|
|
41
|
-
· Improved error handling with custom error classes
|
|
42
|
-
· Logging integration with configurable loggers
|
|
43
|
-
|
|
44
|
-
v1.5.0
|
|
45
|
-
|
|
46
|
-
· Added command argument parsing (ctx.command_args)
|
|
47
|
-
· Message entity parsing (mentions, hashtags, bot commands)
|
|
48
|
-
· Chat member update handlers
|
|
49
|
-
· Pre-checkout and shipping query support
|
|
50
|
-
· File download helper methods
|
|
51
|
-
· Context helper methods for common API calls
|
|
52
|
-
|
|
53
|
-
v1.0.0
|
|
54
|
-
|
|
55
|
-
· Stable API release
|
|
56
|
-
· Message handlers with text pattern matching
|
|
57
|
-
· Command handlers with regex support
|
|
58
|
-
· Basic context object with chat/message accessors
|
|
59
|
-
· Simple API client with error handling
|
|
60
|
-
· Polling and webhook modes
|
|
61
|
-
· Configuration options for timeout and limits
|
|
62
|
-
|
|
63
|
-
v0.5.0
|
|
64
|
-
|
|
65
|
-
· Added callback query support
|
|
66
|
-
· Inline keyboard builder
|
|
67
|
-
· Message editing and deletion helpers
|
|
68
|
-
· Media sending methods (photo, document, audio, video)
|
|
69
|
-
· Chat action methods (typing, upload indicators)
|
|
70
|
-
|
|
71
|
-
v0.3.0
|
|
72
|
-
|
|
73
|
-
· Middleware system with bot.use
|
|
74
|
-
· Session management foundation
|
|
75
|
-
· Basic rate limiting
|
|
76
|
-
· Command filtering by chat type
|
|
77
|
-
· Improved logging with debug levels
|
|
78
|
-
|
|
79
|
-
v0.2.0
|
|
80
|
-
|
|
81
|
-
· Basic polling implementation
|
|
82
|
-
· Message type detection (text, photo, document)
|
|
83
|
-
· Command parsing with arguments
|
|
84
|
-
· Simple reply methods
|
|
85
|
-
· Error handling for API calls
|
|
86
|
-
|
|
87
|
-
v0.1.0 (Initial Release)
|
|
88
|
-
|
|
89
|
-
· Basic Telegram Bot API wrapper
|
|
90
|
-
· Send/receive messages
|
|
91
|
-
· Simple command handling
|
|
92
|
-
· Minimal dependencies (just httparty)
|
|
93
|
-
· Support for basic message types
|
|
94
|
-
|
|
95
|
-
---
|