telegem 3.2.2 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e38340f8acfbe77b677a30960ba7f5f7e074b7fe373890b676d492f30141207
4
- data.tar.gz: 9ad810bb1fb0e63af1de6a1120f8adcc791e2c40765446731e67eee2d4b82d6a
3
+ metadata.gz: 64cc027dc87d2e24f181a24705a85243f95c7f498a44f70fcb175d8a9b2dd597
4
+ data.tar.gz: 37310a9a9c973f31f3dfd5b93e8347843a50c8218475b90a7f055df116c8353e
5
5
  SHA512:
6
- metadata.gz: ebea1b6df44831d3fbb912f345cce9f5bee57f9e887fa39df1f7dd8056b5a360f7703b56c5f591de17e13aa105b9a911737c00364bc48ea21af974dcf19382c1
7
- data.tar.gz: 2727f777908d295aff07fe535c11a5a6238cb841e2f4944ca7b26973ed87cb140671aa45cc2d3cf937cc422a507b31b7f69e165e1641c5255691bc2d930b7060
6
+ metadata.gz: 9cd4708d09629445cf1398677521270c22991eee81b45204d2384570ba14876c937888cf86fc9ba8bfdad78138a48ea9bee6fed6e019a12ae89b29433f91e881
7
+ data.tar.gz: ee85cb0026f096291a1700d825e68cf21f08385e1c7eaf5396763e00fbe4edecfb574b4ed1017293e4e41a06d60b98be5963893b6b0ba2e53f8816396ae33dfc
data/Readme.md CHANGED
@@ -108,7 +108,7 @@ Perfect For:
108
108
 
109
109
  ---
110
110
 
111
- [📚 Documentation](https://www.rubydoc.info/gems/telegem/3.2.0/index)
111
+ [📚 Documentation](https://rubydoc.info/gems/telegem/3.2.2/index)
112
112
 
113
113
 
114
114
 
data/docs/.gitkeep ADDED
File without changes
data/docs/ctx.md ADDED
@@ -0,0 +1,437 @@
1
+ The Context object (ctx) is the heart of every Telegem bot handler. It provides access to everything about the current update and methods to interact with Telegram.
2
+
3
+ ---
4
+
5
+ 📦 Basic Properties
6
+
7
+ Property Type Description
8
+ ctx.update Update Raw Telegram update object
9
+ ctx.bot Bot The bot instance
10
+ ctx.state Hash Temporary state for current request (cleared after)
11
+ ctx.session Hash Persistent user session (stored between requests)
12
+ ctx.match MatchData Regex match data from hears or pattern matching
13
+ ctx.scene String Current scene name (if in a scene)
14
+
15
+ ---
16
+
17
+ 👤 User & Chat Information
18
+
19
+ ```ruby
20
+ ctx.from # User who sent the message
21
+ ctx.from.id # User ID
22
+ ctx.from.username # Username (may be nil)
23
+ ctx.from.first_name
24
+ ctx.from.last_name
25
+
26
+ ctx.chat # Current chat
27
+ ctx.chat.id # Chat ID
28
+ ctx.chat.type # "private", "group", "supergroup", "channel"
29
+
30
+ ctx.user_id # Shortcut for ctx.from&.id
31
+ ```
32
+
33
+ ---
34
+
35
+ 📨 Message Access
36
+
37
+ ```ruby
38
+ ctx.message # Current message
39
+ ctx.message.text # Message text
40
+ ctx.message.caption # Caption for media
41
+ ctx.message.message_id
42
+ ctx.message.date
43
+
44
+ ctx.message.reply_to_message # Message being replied to
45
+ ctx.reply_to_message # Same as above
46
+
47
+ ctx.message.photo # Array of photo sizes
48
+ ctx.message.document # Document object
49
+ ctx.message.audio
50
+ ctx.message.video
51
+ ctx.message.voice
52
+ ctx.message.sticker
53
+ ctx.message.location
54
+
55
+ ctx.message.entities # Formatting entities
56
+ ctx.message.caption_entities # Caption entities
57
+ ```
58
+
59
+ ---
60
+
61
+ 🔍 Update Type Detection
62
+
63
+ ```ruby
64
+ ctx.callback_query # Callback query object (if present)
65
+ ctx.inline_query # Inline query object
66
+ ctx.chosen_inline_result
67
+ ctx.poll # Poll object
68
+ ctx.poll_answer
69
+ ctx.chat_member
70
+ ctx.my_chat_member
71
+ ctx.chat_join_request
72
+ ```
73
+
74
+ ---
75
+
76
+ 📝 Message Content Shortcuts
77
+
78
+ ```ruby
79
+ ctx.text # ctx.message&.text
80
+ ctx.data # ctx.callback_query&.data
81
+ ctx.query # ctx.inline_query&.query
82
+ ctx.command? # True if message is a command
83
+ ctx.command # Command name (e.g., "start")
84
+ ctx.command_args # Arguments after command
85
+ ```
86
+
87
+ ---
88
+
89
+ 💬 Sending Messages
90
+
91
+ Basic Text
92
+
93
+ ```ruby
94
+ ctx.reply("Hello world") # Simple reply
95
+ ctx.reply("Hello", parse_mode: "Markdown") # With formatting
96
+ ctx.reply("Hello", disable_web_page_preview: true)
97
+ ctx.reply("Hello", reply_to_message_id: 123)
98
+ ```
99
+
100
+ With Keyboard
101
+
102
+ ```ruby
103
+ # Reply keyboard
104
+ keyboard = Telegem::Markup.keyboard do
105
+ button "Option 1"
106
+ button "Option 2"
107
+ request_location "Share Location"
108
+ end
109
+ ctx.reply("Choose:", reply_markup: keyboard)
110
+
111
+ # Inline keyboard
112
+ inline = Telegem::Markup.inline do
113
+ callback "Yes", "confirm"
114
+ callback "No", "cancel"
115
+ url "Visit", "https://example.com"
116
+ end
117
+ ctx.reply("Confirm?", reply_markup: inline)
118
+ ```
119
+
120
+ Remove Keyboard
121
+
122
+ ```ruby
123
+ ctx.remove_keyboard # Just removes keyboard
124
+ ctx.remove_keyboard("Done!") # Sends message + removes keyboard
125
+ ctx.remove_keyboard(selective: true) # Only for specific users
126
+ ```
127
+
128
+ Force Reply
129
+
130
+ ```ruby
131
+ ctx.reply("What's your name?", reply_markup: Telegem::Markup.force_reply)
132
+ ```
133
+
134
+ ---
135
+
136
+ 🖼️ Sending Media
137
+
138
+ Photos
139
+
140
+ ```ruby
141
+ ctx.photo("https://example.com/image.jpg")
142
+ ctx.photo("https://example.com/image.jpg", caption: "Beautiful gem")
143
+ ctx.photo(File.open("local.jpg"))
144
+ ctx.photo(File.open("local.jpg"), caption: "Local file")
145
+ ```
146
+
147
+ Documents
148
+
149
+ ```ruby
150
+ ctx.document("https://example.com/file.pdf")
151
+ ctx.document(File.open("report.pdf"), caption: "Monthly report")
152
+ ctx.document(file_id) # Using Telegram file_id
153
+ ```
154
+
155
+ Audio
156
+
157
+ ```ruby
158
+ ctx.audio("song.mp3", title: "My Song", performer: "Artist", duration: 180)
159
+ ```
160
+
161
+ Video
162
+
163
+ ```ruby
164
+ ctx.video("video.mp4", caption: "Check this out", width: 1920, height: 1080)
165
+ ```
166
+
167
+ Voice
168
+
169
+ ```ruby
170
+ ctx.voice("voice.ogg") # Must be OGG format
171
+ ```
172
+
173
+ Sticker
174
+
175
+ ```ruby
176
+ ctx.sticker("CAACAgIAAxkBAAIBZmd...") # Sticker file_id
177
+ ```
178
+
179
+ Location
180
+
181
+ ```ruby
182
+ ctx.location(6.454, 3.394) # Latitude, Longitude
183
+ ```
184
+
185
+ Contact
186
+
187
+ ```ruby
188
+ ctx.contact("+1234567890", "John", last_name: "Doe")
189
+ ```
190
+
191
+ ---
192
+
193
+ ✏️ Editing Messages
194
+
195
+ ```ruby
196
+ ctx.edit_message_text("Updated text")
197
+ ctx.edit_message_text("New text", message_id: 123) # Specific message
198
+
199
+ ctx.edit_message_caption("New caption")
200
+ ctx.edit_message_media(new_photo)
201
+ ctx.edit_message_reply_markup(new_keyboard)
202
+
203
+ ctx.edit_message_live_location(6.455, 3.395) # For live locations
204
+ ctx.stop_message_live_location
205
+ ```
206
+
207
+ ---
208
+
209
+ ❌ Deleting Messages
210
+
211
+ ```ruby
212
+ ctx.delete_message # Deletes current message
213
+ ctx.delete_message(123) # Deletes specific message ID
214
+ ```
215
+
216
+ ---
217
+
218
+ 🔄 Replying to Callbacks
219
+
220
+ ```ruby
221
+ ctx.answer_callback_query("Done!") # Simple toast
222
+ ctx.answer_callback_query("Error!", show_alert: true) # Alert popup
223
+ ctx.answer_callback_query(url: "https://example.com") # Open URL
224
+ ctx.answer_callback_query(text: "Loading...", cache_time: 5)
225
+ ```
226
+
227
+ ---
228
+
229
+ 🔎 Inline Queries
230
+
231
+ ```ruby
232
+ ctx.answer_inline_query(results, cache_time: 300)
233
+ ctx.answer_inline_query(results, next_offset: "20") # Pagination
234
+
235
+ # Results array of InlineQueryResult objects
236
+ results = [
237
+ Telegem::Types::InlineQueryResultArticle.new(
238
+ id: "1",
239
+ title: "Result",
240
+ input_message_content: { message_text: "Text" }
241
+ )
242
+ ]
243
+ ```
244
+
245
+ ---
246
+
247
+ 📥 File Operations
248
+
249
+ ```ruby
250
+ ctx.download_file(file_id, "local/path") # Download file
251
+ ctx.file(file_id) # Get file info
252
+ ctx.file_path(file_id) # Get path on Telegram servers
253
+ ```
254
+
255
+ ---
256
+
257
+ 🎬 Chat Actions (Typing Indicators)
258
+
259
+ ```ruby
260
+ ctx.typing # "typing..."
261
+ ctx.uploading_photo # "sending photo..."
262
+ ctx.uploading_video # "sending video..."
263
+ ctx.uploading_audio # "sending audio..."
264
+ ctx.uploading_document # "sending document..."
265
+ ctx.find_location # "finding location..."
266
+ ctx.record_video # "recording video..."
267
+ ctx.record_audio # "recording audio..."
268
+ ctx.choose_sticker # "choosing sticker..."
269
+ ```
270
+
271
+ All accept optional **options:
272
+
273
+ ```ruby
274
+ ctx.typing(business_connection_id: "123") # For business connections
275
+ ```
276
+
277
+ ---
278
+
279
+ 👥 Chat Management
280
+
281
+ ```ruby
282
+ ctx.kick_chat_member(user_id) # Kick user
283
+ ctx.ban_chat_member(user_id, until_date: future_time)
284
+ ctx.unban_chat_member(user_id, only_if_banned: true)
285
+
286
+ ctx.restrict_chat_member(user_id, permissions: { can_send_messages: false })
287
+ ctx.promote_chat_member(user_id, can_invite_users: true)
288
+
289
+ ctx.get_chat_administrators
290
+ ctx.get_chat_member(user_id)
291
+ ctx.get_chat_members_count
292
+
293
+ ctx.leave_chat
294
+ ```
295
+
296
+ ---
297
+
298
+ 📌 Pinning Messages
299
+
300
+ ```ruby
301
+ ctx.pin_message(123) # Pin message
302
+ ctx.pin_message(123, disable_notification: true)
303
+ ctx.unpin_message # Unpin current
304
+ ctx.unpin_message(123) # Unpin specific
305
+ ctx.unpin_all_messages
306
+ ```
307
+
308
+ ---
309
+
310
+ 🔁 Forwarding & Copying
311
+
312
+ ```ruby
313
+ ctx.forward_message(from_chat_id, message_id)
314
+ ctx.copy_message(from_chat_id, message_id, caption: "New caption")
315
+ ```
316
+
317
+ ---
318
+
319
+ 🌐 Web App & Polls
320
+
321
+ ```ruby
322
+ ctx.web_app_data # Data from Web App
323
+
324
+ ctx.send_poll("Question?", ["Option1", "Option2"], is_anonymous: true)
325
+ ctx.stop_poll(message_id)
326
+ ```
327
+
328
+ ---
329
+
330
+ 🎭 Scenes (Multi-step Conversations)
331
+
332
+ ```ruby
333
+ ctx.enter_scene(:survey) # Enter scene
334
+ ctx.leave_scene # Leave current scene
335
+ ctx.leave_scene(reason: :cancel) # Leave with reason
336
+ ctx.in_scene? # Check if in a scene
337
+ ctx.current_scene # Get current scene name
338
+
339
+ ctx.ask("What's your name?") # Prompt for response (scene helper)
340
+ ctx.next_step # Move to next scene step
341
+ ctx.next_step(:payment) # Move to specific step
342
+ ctx.scene_data # Get scene data hash
343
+ ```
344
+
345
+ ---
346
+
347
+ 🎨 Keyboard Building Helpers
348
+
349
+ ```ruby
350
+ # Create and use in one line
351
+ ctx.reply_with_keyboard("Choose:", Telegem::Markup.keyboard { button "Yes" })
352
+
353
+ ctx.reply_with_inline_keyboard("Options:", Telegem::Markup.inline { callback "Yes", "data" })
354
+ ```
355
+
356
+ ---
357
+
358
+ 📊 Poll Helpers
359
+
360
+ ```ruby
361
+ ctx.poll? # True if update is a poll
362
+ ctx.poll_answer? # True if poll answer
363
+ ctx.poll_answer # Get poll answer object
364
+ ```
365
+
366
+ ---
367
+
368
+ ⏱️ Timing & Metadata
369
+
370
+ ```ruby
371
+ ctx.logger # Bot's logger
372
+ ctx.api # Direct API client
373
+ ctx.raw_update # Original update hash
374
+ ctx.update_id # Update ID
375
+ ```
376
+
377
+ ---
378
+
379
+ 🔄 Session Management
380
+
381
+ ```ruby
382
+ ctx.session[:user_preference] = "dark" # Store in session
383
+ pref = ctx.session[:user_preference] # Retrieve
384
+ ctx.session.delete(:temp_data) # Delete
385
+ ctx.session.clear # Clear all
386
+ ```
387
+
388
+ ---
389
+
390
+ 🎯 Complete Usage Example
391
+
392
+ ```ruby
393
+ bot.command('start') do |ctx|
394
+ ctx.session[:visits] ||= 0
395
+ ctx.session[:visits] += 1
396
+
397
+ keyboard = Telegem::Markup.keyboard do
398
+ button "💎 Random Gem"
399
+ request_location "📍 Share Location"
400
+ end
401
+
402
+ ctx.reply(
403
+ "Welcome! You've visited #{ctx.session[:visits]} times",
404
+ reply_markup: keyboard
405
+ )
406
+ end
407
+
408
+ bot.hears(/^gem$/i) do |ctx|
409
+ ctx.typing
410
+ sleep 1 # Simulate processing
411
+
412
+ gem = GemDatabase.random
413
+ ctx.photo(gem.image_url, caption: "#{gem.name}\n#{gem.fact}")
414
+ end
415
+
416
+ bot.callback_query('favorite') do |ctx|
417
+ gem_id = ctx.data.split('_').last
418
+ ctx.session[:favorites] ||= []
419
+ ctx.session[:favorites] << gem_id
420
+
421
+ ctx.answer_callback_query("Added to favorites! ❤️")
422
+ ctx.edit_message_text("⭐ Favorited!")
423
+ end
424
+ ```
425
+
426
+ ---
427
+
428
+ ⚠️ Error Handling
429
+
430
+ ```ruby
431
+ bot.error do |error, ctx|
432
+ ctx.logger.error("Error for user #{ctx.user_id}: #{error.message}")
433
+ ctx.reply("Something went wrong. Please try again.") if ctx.chat
434
+ end
435
+ ```
436
+
437
+ ---
@@ -0,0 +1,378 @@
1
+
2
+
3
+ # Telegem::Plugins::FileExtract
4
+
5
+ **Version:** 3.2.2
6
+ **Status:** Production Ready
7
+ **Dependencies:** `pdf-reader`
8
+
9
+ ---
10
+
11
+ ## Overview
12
+
13
+ FileExtractor provides seamless document processing for Telegram bots. It automatically detects file types, extracts structured content, and handles all edge cases—eliminating boilerplate code for developers.
14
+
15
+ Unlike traditional approaches that require manual MIME type checking and format-specific handlers, FileExtractor implements a unified interface that works across all supported formats.
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ Add to your Gemfile:
22
+
23
+ ```ruby
24
+ gem 'telegem'
25
+ ```
26
+
27
+ ---
28
+
29
+ Quick Start
30
+
31
+ ```ruby
32
+ # Initialize with any Telegram file_id
33
+ extractor = Telegem::Plugins::FileExtract.new(
34
+ bot,
35
+ ctx.message.document.file_id
36
+ )
37
+
38
+ # Extract content - auto-detects file type
39
+ result = extractor.extract
40
+
41
+ if result[:success]
42
+ # Unified response structure across all file types
43
+ puts "Type: #{result[:type]}"
44
+ puts "Content: #{result[:content]}"
45
+ puts "Metadata: #{result[:metadata]}"
46
+ else
47
+ # Graceful error handling
48
+ puts "Error: #{result[:error]}"
49
+ end
50
+ ```
51
+
52
+ ---
53
+
54
+ Supported Formats
55
+
56
+ | format | Mime type | extension | features |
57
+ |:--------:| :--------:| :--------: |:--------:|
58
+ | pdf | application/pdf | .pdf | text extraction and page count |
59
+ | json| application/json | .json | ful parsibg nested structure |
60
+ | html | text/html | .html | tag stripping |
61
+ | plain text| text/plain | .txt | full content |
62
+
63
+
64
+ Note: All formats except PDF work without additional dependencies.
65
+
66
+ ---
67
+
68
+ API Reference
69
+
70
+ Constructor
71
+
72
+ ```ruby
73
+ def initialize(bot, file_id, **options)
74
+ ```
75
+
76
+ | Parameter | Type Required | Default Description |
77
+ |:--------: | :--------:| :--------:| :--------:|
78
+ | bot | Telegem::Bot ✓ | – Active bot instance
79
+ | file_id | String ✓| – Telegram file identifier
80
+ | auto_delete | Boolean |true Remove temp files after processing|
81
+ | max_size | Integer | 50_000_000 Maximum file size in bytes (50MB) |
82
+ | timeout | Integer | 30 Processing timeout in seconds |
83
+
84
+
85
+ Instance Methods
86
+
87
+ extract → Hash
88
+
89
+ Processes the file and returns a standardized response hash.
90
+
91
+ Success Response:
92
+
93
+ ```ruby
94
+ {
95
+ success: true,
96
+ type: :pdf, # Symbol identifying file format
97
+ content: "extracted text", # Parsed content (String, Hash, or Array)
98
+ metadata: {
99
+ size: 45210, # File size in bytes
100
+ # Format-specific fields (see below)
101
+ },
102
+ error: nil
103
+ }
104
+ ```
105
+
106
+ Error Response:
107
+
108
+ ```ruby
109
+ {
110
+ success: false,
111
+ type: :unknown,
112
+ content: nil,
113
+ metadata: {},
114
+ error: "Descriptive error message"
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ Format-Specific Behavior
121
+
122
+ PDF Documents
123
+
124
+ Requirements: gem 'pdf-reader'
125
+
126
+ ```ruby
127
+ result = extractor.extract
128
+
129
+ if result[:success]
130
+ puts "Pages: #{result[:metadata][:pages]}"
131
+ puts "Content: #{result[:content][0..500]}..."
132
+ end
133
+ ```
134
+
135
+ Metadata:
136
+
137
+ ```ruby
138
+ metadata: {
139
+ pages: 12, # Total page count
140
+ size: 1048576 # File size in bytes
141
+ }
142
+ ```
143
+
144
+ Error Cases:
145
+
146
+ - Malformed PDF format – Corrupted or invalid PDF structure
147
+ - Encrypted PDF (password protected) – Document requires password
148
+ - PDF contains no extractable text – Scanned/image-only document
149
+
150
+ ---
151
+
152
+ JSON Documents
153
+
154
+ ```ruby
155
+ result = extractor.extract
156
+
157
+ if result[:success]
158
+ # Content is already parsed Ruby objects
159
+ data = result[:content]
160
+ puts data['user']['name'] # Direct hash access
161
+ end
162
+ ```
163
+
164
+ Metadata:
165
+
166
+ ```ruby
167
+ metadata: {
168
+ size: 1250,
169
+ structure: "hash", # "hash", "array", or "scalar"
170
+ keys: 5 # Top-level keys (hash only)
171
+ }
172
+ ```
173
+
174
+ Error Cases:
175
+
176
+ - Invalid JSON: unexpected token – Syntax error with position
177
+ - JSON file is empty – Zero-byte file
178
+ - File encoding not supported – Non-UTF8 encoding
179
+
180
+ ---
181
+
182
+ HTML Documents
183
+
184
+ ```ruby
185
+ result = extractor.extract
186
+
187
+ if result[:success]
188
+ # HTML tags stripped, whitespace normalized
189
+ text = result[:content]
190
+
191
+ # Original HTML available when keep_original: true
192
+ original = result[:metadata][:original] if @keep_original
193
+ end
194
+ ```
195
+
196
+ Metadata:
197
+
198
+ ```ruby
199
+ metadata: {
200
+ size: 3072,
201
+ original: "<html>...</html>" # Only if keep_original: true
202
+ }
203
+ ```
204
+
205
+ Error Cases:
206
+
207
+ - HTML file is empty – Zero-byte file
208
+ - HTML contains no extractable text – Only tags, comments, or scripts
209
+
210
+ ---
211
+
212
+ Text-Based Formats (TXT, CSV, Markdown)
213
+
214
+ ```ruby
215
+ result = extractor.extract
216
+
217
+ if result[:success]
218
+ lines = result[:metadata][:lines]
219
+ encoding = result[:metadata][:encoding]
220
+ end
221
+ ```
222
+
223
+ Metadata:
224
+
225
+ ```ruby
226
+ metadata: {
227
+ size: 2048,
228
+ lines: 42, # Line count
229
+ encoding: "UTF-8" # Detected encoding
230
+ }
231
+ ```
232
+
233
+ Error Cases:
234
+ - Text file is empty – Zero-byte file
235
+ - File encoding not supported – Binary or invalid UTF-8
236
+
237
+ ---
238
+
239
+ Error Handling
240
+
241
+ FileExtractor never raises exceptions during normal operation. All errors are captured and returned in the standardized hash format.
242
+
243
+
244
+
245
+ Client-Side Error Handling
246
+
247
+ ```ruby
248
+ result = extractor.extract
249
+
250
+ unless result[:success]
251
+ case result[:error]
252
+ when /encrypted/i
253
+ ctx.reply("🔒 Please send an unencrypted PDF")
254
+ when /malformed/i
255
+ ctx.reply("📄 File appears corrupted, please resend")
256
+ when /size exceeds/i
257
+ ctx.reply("📦 File too large (max 50MB)")
258
+ else
259
+ ctx.reply("❌ #{result[:error]}")
260
+ end
261
+ end
262
+ ```
263
+
264
+ ---
265
+
266
+
267
+ Benchmarks: (50KB file, Ruby 3.2)
268
+
269
+ - PDF: ~150ms
270
+ - JSON: ~20ms
271
+ - HTML: ~15ms
272
+ - Text: ~5ms
273
+
274
+ ---
275
+
276
+ Security Considerations
277
+
278
+ File Size Limits
279
+
280
+ Default max_size: 50_000_000 prevents denial-of-service attacks via large file uploads.
281
+
282
+ Temporary File Management
283
+
284
+ All files are written to Dir.tmpdir with:
285
+
286
+ - Random filename generation (SecureRandom.hex)
287
+ - Automatic deletion (auto_delete: true)
288
+ - Explicit cleanup in ensure blocks
289
+
290
+ Memory Protection
291
+
292
+ - Large files are never fully read into memory unless required
293
+ - Streaming parsers used where available
294
+ - Configurable timeouts prevent hanging operations
295
+
296
+ ---
297
+
298
+ Common Patterns
299
+
300
+ Reply-to-Message Processing
301
+
302
+ ```ruby
303
+ bot.command('extract') do |ctx|
304
+ replied = ctx.message.reply_to_message
305
+
306
+ if replied&.document
307
+ result = FileExtractor.new(bot, replied.document.file_id).extract
308
+
309
+ if result[:success]
310
+ ctx.reply("📄 Extracted from #{result[:type]}:\n\n#{result[:content][0..300]}")
311
+ else
312
+ ctx.reply("❌ #{result[:error]}")
313
+ end
314
+ end
315
+ end
316
+ ```
317
+
318
+ Batch Processing
319
+
320
+ ```ruby
321
+ files = updates.map do |update|
322
+ next unless update.message&.document
323
+
324
+ Thread.new do
325
+ FileExtractor.new(bot, update.message.document.file_id).extract
326
+ end
327
+ end.map(&:value)
328
+ ```
329
+
330
+ Caching Extracted Content
331
+
332
+ ```ruby
333
+ class CachedExtractor < Telegem::Plugins::FileExtractor
334
+ def extract
335
+ cache_key = "extract:#{@file_id}"
336
+
337
+ Rails.cache.fetch(cache_key, expires_in: 1.hour) do
338
+ super
339
+ end
340
+ end
341
+ end
342
+ ```
343
+
344
+ ---
345
+
346
+ Troubleshooting
347
+
348
+ PDF::Reader Not Found
349
+
350
+ ```
351
+ LoadError: cannot load such file -- pdf/reader
352
+ ```
353
+
354
+ Solution: gem install pdf-reader or add to Gemfile
355
+
356
+ File ID Expired
357
+
358
+ ```
359
+ Error: File not found
360
+ ```
361
+
362
+ Solution: File IDs expire after 24 hours. Request fresh file from user.
363
+
364
+ Memory Usage Spikes
365
+
366
+ Solution: Reduce max_size or process files sequentially rather than in parallel.
367
+
368
+ Unsupported File Types
369
+
370
+ Solution: Check file extension or MIME type before extraction:
371
+
372
+ ```ruby
373
+ if ctx.message.document.mime_type.start_with?('image/')
374
+ # Handle images separately
375
+ else
376
+ result = FileExtractor.new(bot, file_id).extract
377
+ end
378
+ ```
data/lib/core/bot.rb CHANGED
@@ -134,6 +134,22 @@ module Telegem
134
134
  end
135
135
  end
136
136
 
137
+ def set_my_profile_photo(photo, **options)
138
+ @api.call('setMyProfilePhoto', { photo: photo }.merge(options))
139
+ end
140
+
141
+ def remove_my_profile_photo
142
+ @api.call('removeMyProfilePhoto', {})
143
+ end
144
+
145
+ def get_user_profile_audios(user_id, **options)
146
+ result = @api.call('getUserProfileAudios', { user_id: user_id }.merge(options))
147
+ result ? Types::UserProfileAudios.new(result) : nil
148
+ end
149
+
150
+ def create_forum_topic(chat_id, name, **options)
151
+ @api.call('createForumTopic', { chat_id: chat_id, name: name }.merge(options))
152
+ end
137
153
  def location(&block)
138
154
  on(:message, location: true) do |ctx|
139
155
  block.call(ctx)
@@ -369,4 +385,4 @@ module Telegem
369
385
  end
370
386
  end
371
387
  end
372
- end
388
+ end
@@ -27,14 +27,19 @@ module Telegem
27
27
  self
28
28
  end
29
29
 
30
- def button(text, **options)
31
- if @buttons.empty? || !@buttons.last.is_a?(Array)
32
- @buttons << [{ text: text }.merge(options)]
33
- else
34
- @buttons.last << { text: text }.merge(options)
35
- end
36
- self
37
- end
30
+ def button(text, style: nil, icon_custom_emoji_id: nil, **options)
31
+ btn = {
32
+ text: text
33
+ }.merge(options)
34
+ btn[:style] = style if style
35
+ btn[:icon_custom_emoji_id] = icon_custom_emoji_id if icon_custom_emoji_id
36
+ if @buttons.empty? || @buttons.last.is_a?(Array)
37
+ @buttons << [btn]
38
+ else
39
+ @buttons.last << btn
40
+ end
41
+ self
42
+ end
38
43
 
39
44
  def request_contact(text)
40
45
  button(text, request_contact: true)
@@ -114,14 +119,19 @@ module Telegem
114
119
  self
115
120
  end
116
121
 
117
- def button(text, **options)
118
- if @buttons.empty? || !@buttons.last.is_a?(Array)
119
- @buttons << [{ text: text }.merge(options)]
120
- else
121
- @buttons.last << { text: text }.merge(options)
122
- end
123
- self
124
- end
122
+ def button(text, style: nil, icon_custom_emoji_id: nil, **options)
123
+ btn = {
124
+ text: text
125
+ }.merge(options)
126
+ btn[:style] = style if style
127
+ btn[:icon_custom_emoji_id] = icon_custom_emoji_id if icon_custom_emoji_id
128
+ if @buttons.empty? || @buttons.last.is_a?(Array)
129
+ @buttons << [btn]
130
+ else
131
+ @buttons.last << btn
132
+ end
133
+ self
134
+ end
125
135
 
126
136
  def url(text, url)
127
137
  button(text, url: url)
data/lib/telegem.rb CHANGED
@@ -1,12 +1,12 @@
1
- # lib/telegem.rb - MAIN ENTRY POINT
1
+
2
2
  require 'logger'
3
3
  require 'json'
4
4
 
5
5
  module Telegem
6
- VERSION = "3.2.2".freeze
6
+ VERSION = "3.2.4".freeze
7
7
  end
8
8
 
9
- # Load core components
9
+ #
10
10
  require_relative 'api/client'
11
11
  require_relative 'api/types'
12
12
  require_relative 'core/bot'
@@ -16,17 +16,17 @@ require_relative 'core/scene'
16
16
  require_relative 'session/middleware'
17
17
  require_relative 'session/memory_store'
18
18
  require_relative 'markup/keyboard'
19
- # Webhook is loaded lazily when needed
19
+
20
20
  require_relative 'plugins/file_extract'
21
21
  require_relative 'session/scene_middleware'
22
22
 
23
23
  module Telegem
24
- # Main entry point: Telegem.new(token)
24
+
25
25
  def self.new(token, **options)
26
26
  Core::Bot.new(token, **options)
27
27
  end
28
28
 
29
- # Shortcut for creating keyboards
29
+
30
30
  def self.keyboard(&block)
31
31
  Markup.keyboard(&block)
32
32
  end
@@ -35,28 +35,23 @@ module Telegem
35
35
  Markup.inline(&block)
36
36
  end
37
37
 
38
- # Remove keyboard markup
39
38
  def self.remove_keyboard(**options)
40
39
  Markup.remove(**options)
41
40
  end
42
41
 
43
- # Force reply markup
44
42
  def self.force_reply(**options)
45
43
  Markup.force_reply(**options)
46
44
  end
47
45
 
48
- # Current version
49
46
  def self.version
50
47
  VERSION
51
48
  end
52
-
53
- # Quick webhook setup
49
+
54
50
  def self.webhook(bot, **options)
55
51
  require_relative 'webhook/server'
56
52
  Webhook::Server.setup(bot, **options)
57
53
  end
58
54
 
59
- # Framework information
60
55
  def self.info
61
56
  <<~INFO
62
57
  🤖 Telegem #{VERSION}
@@ -77,7 +72,7 @@ module Telegem
77
72
  end
78
73
  end
79
74
 
80
- # Optional global shortcut (enabled by env var)
75
+
81
76
  if ENV['TELEGEM_GLOBAL'] == 'true'
82
77
  def Telegem(token, **options)
83
78
  ::Telegem.new(token, **options)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegem
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - sick_phantom
@@ -9,34 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: httpx
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: '1.0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: '1.0'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: concurrent-ruby
28
14
  requirement: !ruby/object:Gem::Requirement
29
15
  requirements:
30
16
  - - "~>"
31
17
  - !ruby/object:Gem::Version
32
- version: '1.0'
18
+ version: 1.3.6
33
19
  type: :runtime
34
20
  prerelease: false
35
21
  version_requirements: !ruby/object:Gem::Requirement
36
22
  requirements:
37
23
  - - "~>"
38
24
  - !ruby/object:Gem::Version
39
- version: '1.0'
25
+ version: 1.3.6
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: securerandom
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -57,56 +43,56 @@ dependencies:
57
43
  requirements:
58
44
  - - "~>"
59
45
  - !ruby/object:Gem::Version
60
- version: '1.0'
46
+ version: 2.35.2
61
47
  type: :runtime
62
48
  prerelease: false
63
49
  version_requirements: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
- version: '1.0'
53
+ version: 2.35.2
68
54
  - !ruby/object:Gem::Dependency
69
55
  name: async-http
70
56
  requirement: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - "~>"
73
59
  - !ruby/object:Gem::Version
74
- version: '0.100'
60
+ version: 0.92.1
75
61
  type: :runtime
76
62
  prerelease: false
77
63
  version_requirements: !ruby/object:Gem::Requirement
78
64
  requirements:
79
65
  - - "~>"
80
66
  - !ruby/object:Gem::Version
81
- version: '0.100'
67
+ version: 0.92.1
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: pdf-reader
84
70
  requirement: !ruby/object:Gem::Requirement
85
71
  requirements:
86
72
  - - "~>"
87
73
  - !ruby/object:Gem::Version
88
- version: '2.0'
74
+ version: 2.14.1
89
75
  type: :runtime
90
76
  prerelease: false
91
77
  version_requirements: !ruby/object:Gem::Requirement
92
78
  requirements:
93
79
  - - "~>"
94
80
  - !ruby/object:Gem::Version
95
- version: '2.0'
81
+ version: 2.14.1
96
82
  - !ruby/object:Gem::Dependency
97
83
  name: docx
98
84
  requirement: !ruby/object:Gem::Requirement
99
85
  requirements:
100
86
  - - "~>"
101
87
  - !ruby/object:Gem::Version
102
- version: '0.3'
88
+ version: 0.10.0
103
89
  type: :runtime
104
90
  prerelease: false
105
91
  version_requirements: !ruby/object:Gem::Requirement
106
92
  requirements:
107
93
  - - "~>"
108
94
  - !ruby/object:Gem::Version
109
- version: '0.3'
95
+ version: 0.10.0
110
96
  - !ruby/object:Gem::Dependency
111
97
  name: rspec
112
98
  requirement: !ruby/object:Gem::Requirement
@@ -158,6 +144,9 @@ files:
158
144
  - assets/logo.png
159
145
  - bin/.gitkeep
160
146
  - bin/telegem-ssl
147
+ - docs/.gitkeep
148
+ - docs/ctx.md
149
+ - docs/file_extract.md
161
150
  - examples/.gitkeep
162
151
  - lib/api/client.rb
163
152
  - lib/api/types.rb
@@ -173,7 +162,6 @@ files:
173
162
  - lib/session/memory_store.rb
174
163
  - lib/session/middleware.rb
175
164
  - lib/session/scene_middleware.rb
176
- - lib/telegem.code-workspace
177
165
  - lib/telegem.rb
178
166
  - lib/webhook/.gitkeep
179
167
  - lib/webhook/server.rb
@@ -188,7 +176,7 @@ metadata:
188
176
  bug_tracker_uri: https://gitlab.com/ruby-telegem/telegem/-/issues
189
177
  documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/tree/main/docs-src?ref_type=heads
190
178
  rubygems_mfa_required: 'false'
191
- post_install_message: "Thanks for installing Telegem 3.2.2!\n\n\U0001F4DA Documentation:
179
+ post_install_message: "Thanks for installing Telegem 3.2.4!\n\n\U0001F4DA Documentation:
192
180
  https://gitlab.com/ruby-telegem/telegem\n\n\U0001F510 For SSL Webhooks:\nRun: telegem-ssl
193
181
  your-domain.com\nThis sets up Let's Encrypt certificates automatically.\n\n\U0001F916
194
182
  Happy bot building!\n"
@@ -1,8 +0,0 @@
1
- {
2
- "folders": [
3
- {
4
- "path": ".."
5
- }
6
- ],
7
- "settings": {}
8
- }