grammerb 0.1.0-x86_64-linux

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7e0a2199adf7c1a5140d3b584391bd015d2a900af94e0f4e363a83ceaa28703a
4
+ data.tar.gz: 67dd66363f2f48aabea7c8455d54c4ab0fe7b191b81342c70d04ec81036d5f96
5
+ SHA512:
6
+ metadata.gz: 20afead47786b1a6f0c780d79d6e2a36c2303efccb15e5ebeeaf9516fd94664b5d0a94d2adfe1e4d66f75f6770c52ca6c6955d5165956e5db34f8ce36b2bbc02
7
+ data.tar.gz: 5ec252049f8f9a360720266b4438b53997e1e034a501a57a9b1266d967978df924b097c557447c59457078ced114adae3c6680b2640767baddc1e902ccd10833
data/README.md ADDED
@@ -0,0 +1,554 @@
1
+ # Grammerb
2
+
3
+ Grammerb -- as in (Tele)**gramme.rb** -- is a Telegram MTProto client library for Ruby. It wraps the Rust [grammers](https://github.com/Lonami/grammers) crate via native extensions, giving you a direct MTProto connection instead of going through the Bot HTTP API.
4
+
5
+ It works for both user accounts and bots.
6
+
7
+ ## Requirements
8
+
9
+ - Ruby >= 3.1
10
+ - Rust toolchain (for compiling the native extension)
11
+ - `libclang-dev` and `ruby-dev` system packages
12
+
13
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "grammerb", path: "/path/to/grammerb"
19
+ ```
20
+
21
+ Then build the native extension:
22
+
23
+ ```bash
24
+ bundle install
25
+ rake compile
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ ### Bot login
31
+
32
+ ```ruby
33
+ require "grammerb"
34
+
35
+ client = Grammerb::Client.new(
36
+ api_id: ENV["API_ID"].to_i,
37
+ api_hash: ENV["API_HASH"],
38
+ session: "bot.session"
39
+ )
40
+ client.bot_start(token: ENV["BOT_TOKEN"])
41
+
42
+ client.on(Grammerb::Events::NewMessage, pattern: /^\/start/) do |event|
43
+ event.reply("Hello!")
44
+ end
45
+
46
+ client.run
47
+ ```
48
+
49
+ ### User login
50
+
51
+ ```ruby
52
+ client = Grammerb::Client.new(
53
+ api_id: ENV["API_ID"].to_i,
54
+ api_hash: ENV["API_HASH"],
55
+ session: "user.session"
56
+ )
57
+ client.start(phone: "+1234567890")
58
+ ```
59
+
60
+ You can pass callbacks instead of reading from stdin:
61
+
62
+ ```ruby
63
+ client.start(
64
+ phone: "+1234567890",
65
+ code_callback: -> { get_code_from_somewhere },
66
+ password_callback: -> { get_2fa_password }
67
+ )
68
+ ```
69
+
70
+ The session file is saved after authentication, so you only need to log in once.
71
+
72
+ ## Client options
73
+
74
+ ```ruby
75
+ client = Grammerb::Client.new(
76
+ api_id: 12345,
77
+ api_hash: "abc123",
78
+ session: "grammerb.session", # session file path (default: "grammerb.session")
79
+ proxy: "socks5://host:port", # SOCKS5 proxy (optional)
80
+ logger: Logger.new($stdout), # custom logger (optional, defaults to stderr at WARN level)
81
+ pool_size: 20 # handler thread pool size (default: 20)
82
+ )
83
+ ```
84
+
85
+ ## API
86
+
87
+ ### Connection
88
+
89
+ ```ruby
90
+ client.connect
91
+ client.disconnect
92
+ client.authorized?
93
+ client.save_session
94
+ client.sign_out
95
+ ```
96
+
97
+ ### Messages
98
+
99
+ ```ruby
100
+ # Send with all options
101
+ client.send_message(chat, "text",
102
+ reply_to: msg_id,
103
+ parse_mode: "html", # "html" or "markdown"
104
+ silent: true, # no notification
105
+ link_preview: false, # disable link preview
106
+ schedule: Time.now + 3600, # scheduled message (unix timestamp)
107
+ buttons: buttons # inline keyboard (see below)
108
+ )
109
+
110
+ client.edit_message(chat, message_id, "new text", parse_mode: "html", buttons: buttons)
111
+ client.delete_messages(chat, msg_id1, msg_id2, msg_id3)
112
+ client.forward_messages(from_chat, [msg_id], to_chat)
113
+ client.get_messages(chat, limit: 20)
114
+ client.get_messages_by_id(chat, 123, 456)
115
+ client.search_messages(chat, query: "keyword", limit: 100)
116
+ client.search_all_messages(query: "keyword", limit: 100)
117
+ client.pin_message(chat, message_id)
118
+ client.unpin_message(chat, message_id)
119
+ client.unpin_all_messages(chat)
120
+ client.get_pinned_message(chat)
121
+ client.get_reply_to_message(chat, message_id)
122
+ client.send_reactions(chat, message_id, "thumbs_up")
123
+ client.send_chat_action(chat, "typing")
124
+ ```
125
+
126
+ ### Inline keyboards
127
+
128
+ ```ruby
129
+ buttons = [
130
+ [
131
+ { text: "Click me", callback_data: "btn1" },
132
+ { text: "Open link", url: "https://example.com" },
133
+ { text: "Switch inline", switch_inline: "query" }
134
+ ]
135
+ ]
136
+ client.send_message(chat, "Pick one:", buttons: buttons)
137
+ client.edit_message(chat, msg_id, "Updated", buttons: buttons)
138
+ client.send_file(chat, "/path/to/file.pdf", caption: "Doc", buttons: buttons)
139
+ ```
140
+
141
+ ### Reply keyboards
142
+
143
+ ```ruby
144
+ keys = [
145
+ ["Option A", "Option B"],
146
+ [{ text: "Share Phone", request_phone: true }]
147
+ ]
148
+ client.send_reply_keyboard(chat, "Choose:", keys,
149
+ resize: true,
150
+ single_use: false,
151
+ placeholder: "Pick an option..."
152
+ )
153
+ client.remove_keyboard(chat)
154
+ client.remove_keyboard(chat, "Custom removal text")
155
+ client.force_reply(chat, "What is your name?")
156
+ ```
157
+
158
+ ### Files and media
159
+
160
+ ```ruby
161
+ client.send_file(chat, "/path/to/file.pdf", caption: "Document", parse_mode: "html")
162
+ client.send_photo(chat, "/path/to/image.jpg", caption: "Photo", parse_mode: "html")
163
+ client.send_video(chat, "/path/to/video.mp4",
164
+ caption: "Video", parse_mode: "html",
165
+ duration: 30, width: 1920, height: 1080, supports_streaming: true
166
+ )
167
+ client.send_audio(chat, "/path/to/song.mp3",
168
+ caption: "Song", parse_mode: "html",
169
+ duration: 180, title: "Song Title", performer: "Artist"
170
+ )
171
+ client.send_voice(chat, "/path/to/voice.ogg", caption: "Voice", duration: 5)
172
+
173
+ # Albums (grouped media)
174
+ client.send_album(chat, "/path/a.jpg", "/path/b.jpg")
175
+ client.send_album(chat, { path: "/a.jpg" }, { path: "/b.jpg" })
176
+
177
+ # Download
178
+ client.download_media(chat, message_id, "/path/to/save")
179
+
180
+ # Download with progress
181
+ client.download_media(chat, message_id, "/path/to/save") do |downloaded, total|
182
+ puts "#{downloaded}/#{total} bytes"
183
+ end
184
+
185
+ # Upload management
186
+ idx = client.upload_file("/path/to/file")
187
+ client.clear_uploaded(idx)
188
+ client.clear_all_uploads
189
+ ```
190
+
191
+ ### Polls
192
+
193
+ ```ruby
194
+ client.send_poll(chat, "Favorite color?", ["Red", "Blue", "Green"])
195
+ client.send_poll(chat, "Capital of France?", ["London", "Paris", "Berlin"], quiz: true, correct_option: 1)
196
+ client.get_poll_results(chat, message_id)
197
+ client.get_poll_votes(chat, message_id, option: 0, limit: 100)
198
+ ```
199
+
200
+ ### Dialogs
201
+
202
+ ```ruby
203
+ dialogs = client.iter_dialogs(limit: 100) # alias: get_dialogs
204
+ dialogs.each do |d|
205
+ puts "#{d.name} (#{d.peer_type}) - #{d.unread_count} unread"
206
+ puts " Last: #{d.last_message_text}"
207
+ end
208
+ client.delete_dialog(chat)
209
+ client.mark_as_read(chat)
210
+ client.clear_mentions(chat)
211
+ ```
212
+
213
+ ### Users and chats
214
+
215
+ ```ruby
216
+ me = client.get_me # => Types::User
217
+ me.id, me.first_name, me.last_name, me.username, me.phone, me.bot?
218
+
219
+ peer = client.resolve_username("durov") # => Types::Peer
220
+ peer.id, peer.name, peer.peer_type
221
+
222
+ results = client.search_peer("query", limit: 10)
223
+
224
+ participants = client.iter_participants(chat, limit: 200) # alias: get_participants
225
+ participants.each { |p| puts "#{p.full_name} (@#{p.username})" }
226
+
227
+ client.kick_participant(chat, user_id)
228
+ client.set_admin_rights(chat, user_id, post_messages: true)
229
+ client.set_banned_rights(chat, user_id, send_messages: true)
230
+
231
+ perms = client.get_permissions(chat, user_id)
232
+ perms.admin?, perms.creator?, perms.banned?, perms.has_default_permissions
233
+
234
+ client.join_chat("https://t.me/+invite_link")
235
+ link = client.export_invite_link(chat)
236
+ client.block_user(user_id)
237
+ client.unblock_user(user_id)
238
+ client.iter_profile_photos(peer_id, limit: 10)
239
+ ```
240
+
241
+ ### Bots
242
+
243
+ ```ruby
244
+ client.inline_query("@bot", "search term", limit: 20)
245
+ client.answer_callback_query(query_id, text: "Done!", alert: false, cache_time: 60)
246
+ client.answer_inline_query(query_id, results,
247
+ cache_time: 300,
248
+ is_personal: false,
249
+ next_offset: "page2"
250
+ )
251
+ ```
252
+
253
+ ### Sessions
254
+
255
+ ```ruby
256
+ encoded = client.export_session
257
+ Grammerb::Client.import_session(encoded, path: "copy.session")
258
+ ```
259
+
260
+ ### Raw TL
261
+
262
+ ```ruby
263
+ response_bytes = client.invoke_raw(request_bytes)
264
+ ```
265
+
266
+ ## Events
267
+
268
+ Register handlers with `client.on` and call `client.run` to start the event loop.
269
+
270
+ ```ruby
271
+ # All new messages
272
+ client.on(Grammerb::Events::NewMessage) { |e| puts e.text }
273
+
274
+ # Pattern matching
275
+ client.on(Grammerb::Events::NewMessage, pattern: /^\/help/) { |e| e.reply("Help text") }
276
+
277
+ # Filter by chat
278
+ client.on(Grammerb::Events::NewMessage, chats: ["mychannel", -100123456]) { |e| ... }
279
+
280
+ # Callback queries (inline button presses)
281
+ client.on(Grammerb::Events::CallbackQuery) do |e|
282
+ e.answer(text: "Button pressed", alert: true, cache_time: 60)
283
+ puts e.data_str # callback_data as UTF-8 string
284
+ puts e.chat_id
285
+ puts e.sender_id
286
+ puts e.message_id
287
+ end
288
+
289
+ # Message edits
290
+ client.on(Grammerb::Events::MessageEdited) { |e| puts "Edited: #{e.text}" }
291
+
292
+ # Message deletions
293
+ client.on(Grammerb::Events::MessageDeleted) do |e|
294
+ puts "Deleted: #{e.deleted_ids}"
295
+ puts "In chat: #{e.chat_id}"
296
+ end
297
+
298
+ # Inline queries
299
+ client.on(Grammerb::Events::InlineQuery) do |e|
300
+ puts e.query, e.offset, e.sender_id
301
+ e.answer(results, cache_time: 300, is_personal: false, next_offset: "2")
302
+ end
303
+
304
+ # Inline send (when user picks an inline result)
305
+ client.on(Grammerb::Events::InlineSend) { |e| puts e.sender_id }
306
+
307
+ # Albums (grouped media) -- automatically buffered from individual messages
308
+ client.on(Grammerb::Events::Album) do |e|
309
+ puts "Album with #{e.messages.size} items in #{e.chat_name}"
310
+ puts e.texts
311
+ e.reply("Nice album!")
312
+ end
313
+
314
+ # Raw/unhandled updates
315
+ client.on(Grammerb::Events::RawUpdate) { |e| puts e.raw }
316
+
317
+ # Update errors
318
+ client.on(Grammerb::Events::UpdateError) { |e| puts e.message }
319
+
320
+ # Remove a handler
321
+ handler = client.on(Grammerb::Events::NewMessage) { |e| ... }
322
+ client.off(handler)
323
+
324
+ # Stop the event loop
325
+ client.stop
326
+ ```
327
+
328
+ ### Alternative event loop
329
+
330
+ Instead of handler registration, you can iterate updates directly:
331
+
332
+ ```ruby
333
+ client.each_update do |event|
334
+ case event
335
+ when Grammerb::Events::NewMessage
336
+ puts event.text
337
+ when Grammerb::Events::CallbackQuery
338
+ event.answer(text: "ok")
339
+ end
340
+ end
341
+ ```
342
+
343
+ ### Event fields
344
+
345
+ NewMessage and MessageEdited events share these fields:
346
+
347
+ ```ruby
348
+ event.message_id
349
+ event.text
350
+ event.chat_id
351
+ event.chat_name
352
+ event.sender_id
353
+ event.sender_name
354
+ event.date # Time object
355
+ event.chat_type # "user", "chat", or "channel"
356
+ event.grouped_id # non-nil for album messages
357
+ event.sender_is_bot
358
+ event.private? # chat_type == "user"
359
+ event.group? # chat_type == "chat"
360
+ event.channel? # chat_type == "channel"
361
+ event.bot? # sender is a bot
362
+ event.album? # part of a grouped album
363
+ event.reply("text")
364
+ event.edit("new text", parse_mode: "html")
365
+ event.delete
366
+ event.forward(other_chat)
367
+ ```
368
+
369
+ ## Types
370
+
371
+ ### Message
372
+
373
+ Returned by `get_messages`, `get_messages_by_id`, `search_messages`, etc.
374
+
375
+ ```ruby
376
+ msg.id
377
+ msg.text
378
+ msg.chat_id
379
+ msg.chat_name
380
+ msg.sender_id
381
+ msg.sender_name
382
+ msg.date # Time object
383
+ msg.chat_type # "user", "chat", "channel"
384
+ msg.grouped_id
385
+ msg.reply_to_message_id
386
+
387
+ # Media
388
+ msg.has_media?
389
+ msg.media_type # "photo", "document", "sticker", "poll", "contact",
390
+ # "geo", "venue", "dice", "geo_live", "web_page"
391
+ msg.photo?
392
+ msg.document?
393
+ msg.sticker?
394
+ msg.poll?
395
+ msg.album?
396
+ msg.media_id
397
+ msg.media_file_name
398
+ msg.media_mime_type
399
+ msg.media_size
400
+ msg.media_duration
401
+ msg.media_width
402
+ msg.media_height
403
+ msg.media_sticker_emoji
404
+
405
+ # Buttons
406
+ msg.has_buttons?
407
+ msg.buttons # array of rows, each row is array of Types::Button
408
+
409
+ # Actions
410
+ msg.reply("text")
411
+ msg.edit("new text", parse_mode: "html", buttons: buttons)
412
+ msg.delete
413
+ msg.forward(other_chat)
414
+ msg.pin
415
+ msg.download("/path/to/save") { |downloaded, total| ... }
416
+ ```
417
+
418
+ ### Button
419
+
420
+ ```ruby
421
+ button.text
422
+ button.callback_data # raw bytes
423
+ button.data_str # callback_data as UTF-8 string
424
+ button.url
425
+ button.switch_inline
426
+ button.callback?
427
+ button.url?
428
+ ```
429
+
430
+ ### User
431
+
432
+ ```ruby
433
+ user.id
434
+ user.first_name
435
+ user.last_name
436
+ user.username
437
+ user.phone
438
+ user.bot?
439
+ user.full_name # "first last"
440
+ ```
441
+
442
+ ### Dialog
443
+
444
+ ```ruby
445
+ dialog.id
446
+ dialog.name
447
+ dialog.peer_type
448
+ dialog.unread_count
449
+ dialog.last_message_text
450
+ ```
451
+
452
+ ### Participant
453
+
454
+ ```ruby
455
+ participant.id
456
+ participant.first_name
457
+ participant.last_name
458
+ participant.username
459
+ participant.full_name
460
+ ```
461
+
462
+ ### Peer
463
+
464
+ ```ruby
465
+ peer.id
466
+ peer.name
467
+ peer.peer_type
468
+ ```
469
+
470
+ ### Permissions
471
+
472
+ ```ruby
473
+ perms.is_admin
474
+ perms.is_creator
475
+ perms.is_banned
476
+ perms.has_default_permissions
477
+ perms.admin?
478
+ perms.creator?
479
+ perms.banned?
480
+ ```
481
+
482
+ ## Errors
483
+
484
+ All errors inherit from `Grammerb::Errors::Error`.
485
+
486
+ ```
487
+ Grammerb::Errors::Error
488
+ Grammerb::Errors::ConnectionError
489
+ Grammerb::Errors::AuthError
490
+ Grammerb::Errors::NotConnectedError
491
+ Grammerb::Errors::RPCError
492
+ Grammerb::Errors::FloodWait
493
+ Grammerb::Errors::UserNotFound
494
+ Grammerb::Errors::ChatWriteForbidden
495
+ Grammerb::Errors::PhoneNumberInvalid
496
+ Grammerb::Errors::SessionPasswordNeeded
497
+ ```
498
+
499
+ RPCError has `code`, `message_text`, and `value` attributes. FloodWait has a `seconds` attribute with the wait time.
500
+
501
+ FloodWait errors inside event handlers are automatically caught and slept on.
502
+
503
+ ```ruby
504
+ begin
505
+ client.send_message(chat, "hello")
506
+ rescue Grammerb::Errors::FloodWait => e
507
+ sleep e.seconds
508
+ retry
509
+ rescue Grammerb::Errors::ChatWriteForbidden
510
+ puts "Can't write to this chat"
511
+ rescue Grammerb::Errors::RPCError => e
512
+ puts "RPC error #{e.code}: #{e.message_text}"
513
+ end
514
+ ```
515
+
516
+ ## Not yet implemented
517
+
518
+ The following Telegram features are not currently supported:
519
+
520
+ - Creating groups, channels, or supergroups
521
+ - Editing chat title, description, or photo
522
+ - Slow mode
523
+ - Forum topics / threads
524
+ - Message threads and replies-to-threads
525
+ - Scheduled message management (listing, deleting scheduled messages)
526
+ - Drafts
527
+ - Voice and video calls
528
+ - Stories
529
+ - Contacts (import, add, delete, get contacts list)
530
+ - Account management (update profile, change username, set privacy)
531
+ - Two-factor authentication setup/management
532
+ - Notification settings
533
+ - Chat folders / filters
534
+ - Sticker set management (create, add, remove sticker packs)
535
+ - GIF search and saved GIFs
536
+ - Payments and invoices
537
+ - Bot menu button and commands management
538
+ - Channel and group statistics
539
+ - Sponsored messages
540
+ - Message translations
541
+ - Custom emoji
542
+ - Takeout / data export
543
+ - Telegram Premium features
544
+ - Usernames (collectible usernames, multiple usernames)
545
+ - Reactions list (who reacted)
546
+ - Chat event log / admin log
547
+ - Nearby users and chats
548
+ - Password hint and recovery email
549
+
550
+ These can still be accessed via `client.invoke_raw(request_bytes)` if you construct the TL request manually.
551
+
552
+ ## License
553
+
554
+ MIT