max_bot_api 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3513bab6b7e83202a50274c5eba30655b5825bc56a5a6e26de78eae549e0e38f
4
+ data.tar.gz: 0f97c303649e2c8db1d54f64af70fffc99a030ab95866d089094cff533a4e98c
5
+ SHA512:
6
+ metadata.gz: 0d9ee73288cf706f2ba868e82bab3456bc6f7857d8b1a46cc8cf6fa273d2d0d4e800c93f62bce64f6bb822231385c4166d160fb6c13300649a1dcd18cf80a217
7
+ data.tar.gz: e4f90c9ccbc8ddaf116922c1dc3f6b7c83e4d261e20237e83b3d593703fd709898aa2ae201bfced427279c3b3378c318635e0fcbdf2c0b9721448f25a51962ed
data/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # MaxBotApi Ruby Client
2
+
3
+ Ruby client for the MAX Bot API, mirroring the official Go client.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install max_bot_api
9
+ ```
10
+
11
+ Or in your Gemfile:
12
+
13
+ ```ruby
14
+ gem "max_bot_api"
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```ruby
20
+ require "max_bot_api"
21
+
22
+ client = MaxBotApi::Client.new(token: ENV.fetch("TOKEN"))
23
+
24
+ me = client.bots.get_bot
25
+ puts "Bot: #{me[:name]}"
26
+
27
+ client.messages.send(
28
+ MaxBotApi::Builders::MessageBuilder.new
29
+ .set_chat(12345)
30
+ .set_text("Hello from Ruby")
31
+ )
32
+ ```
33
+
34
+ ## Features
35
+
36
+ - Full API coverage: Bots, Chats, Messages, Subscriptions, Uploads, Debugs.
37
+ - Builder helpers for messages and keyboards.
38
+ - Long polling updates with retry and backoff.
39
+ - Webhook parsing helper.
40
+ - Upload helpers for files, photos, audio, video.
41
+
42
+ ## Configuration
43
+
44
+ ```ruby
45
+ client = MaxBotApi::Client.new(
46
+ token: ENV.fetch("TOKEN"),
47
+ base_url: "https://botapi.max.ru/",
48
+ version: "1.2.5"
49
+ )
50
+ ```
51
+
52
+ Notes:
53
+
54
+ - The client uses `Authorization: <token>` (no `Bearer`).
55
+ - Every request includes `v=<version>` query param.
56
+
57
+ ## Common tasks
58
+
59
+ ### Send messages
60
+
61
+ ```ruby
62
+ message = MaxBotApi::Builders::MessageBuilder.new
63
+ .set_chat(12345)
64
+ .set_text("Hello, chat!")
65
+
66
+ client.messages.send(message)
67
+ ```
68
+
69
+ ### Send with result
70
+
71
+ ```ruby
72
+ message = MaxBotApi::Builders::MessageBuilder.new
73
+ .set_chat(12345)
74
+ .set_text("Hello, chat!")
75
+
76
+ sent = client.messages.send_with_result(message)
77
+ puts sent[:body][:mid]
78
+ ```
79
+
80
+ ### Reply to a message
81
+
82
+ ```ruby
83
+ message = MaxBotApi::Builders::MessageBuilder.new
84
+ .set_chat(12345)
85
+ .set_reply("Thanks!", "mid123")
86
+
87
+ client.messages.send(message)
88
+ ```
89
+
90
+ ### Long polling updates
91
+
92
+ ```ruby
93
+ client.each_update do |update|
94
+ case update[:update_type]
95
+ when "message_created"
96
+ text = update.dig(:message, :body, :text)
97
+ puts "New message: #{text}"
98
+ end
99
+ end
100
+ ```
101
+
102
+ ### Webhook parsing
103
+
104
+ ```ruby
105
+ body = request.body.read
106
+ update = client.parse_webhook(body)
107
+ ```
108
+
109
+ ## Builders
110
+
111
+ ### Keyboard
112
+
113
+ ```ruby
114
+ keyboard = client.messages.new_keyboard_builder
115
+ keyboard
116
+ .add_row
117
+ .add_geolocation("Share location", true)
118
+ .add_contact("Share contact")
119
+
120
+ keyboard
121
+ .add_row
122
+ .add_link("Open MAX", "positive", "https://max.ru")
123
+ .add_callback("Audio", "negative", "audio")
124
+
125
+ message = MaxBotApi::Builders::MessageBuilder.new
126
+ .set_chat(12345)
127
+ .add_keyboard(keyboard)
128
+ .set_text("Choose an action")
129
+
130
+ client.messages.send(message)
131
+ ```
132
+
133
+ ### Attachments
134
+
135
+ ```ruby
136
+ photo = client.uploads.upload_photo_from_file(path: "./image.png")
137
+
138
+ message = MaxBotApi::Builders::MessageBuilder.new
139
+ .set_chat(12345)
140
+ .add_photo(photo)
141
+
142
+ client.messages.send(message)
143
+ ```
144
+
145
+ ## Error handling
146
+
147
+ ```ruby
148
+ begin
149
+ client.bots.get_bot
150
+ rescue MaxBotApi::ApiError => e
151
+ puts "API error: #{e.message}"
152
+ rescue MaxBotApi::TimeoutError => e
153
+ puts "Timeout: #{e.message}"
154
+ end
155
+ ```
156
+
157
+ ## More docs
158
+
159
+ - `docs/01-your-first-bot.md`
160
+ - `docs/02-listen-and-respond.md`
161
+ - `docs/03-attachments.md`
162
+ - `docs/04-keyboard.md`
163
+ - `docs/05-uploads.md`
164
+ - `docs/06-subscriptions.md`
165
+
166
+ ## Examples
167
+
168
+ See `examples/` for ready-to-run scripts.
@@ -0,0 +1,44 @@
1
+ # 1. Your First Bot
2
+
3
+ This guide shows how to create a basic bot using the Ruby client.
4
+
5
+ ## Get a token
6
+
7
+ Open a dialog with [MasterBot](https://max.ru/masterbot) and create a bot. You will receive a token.
8
+
9
+ ## Install the gem
10
+
11
+ ```bash
12
+ gem install max_bot_api
13
+ ```
14
+
15
+ ## Minimal bot
16
+
17
+ ```ruby
18
+ require "max_bot_api"
19
+
20
+ client = MaxBotApi::Client.new(token: ENV.fetch("TOKEN"))
21
+
22
+ me = client.bots.get_bot
23
+ puts "Hello, #{me[:name]}"
24
+ ```
25
+
26
+ ## Send a message
27
+
28
+ ```ruby
29
+ message = MaxBotApi::Builders::MessageBuilder.new
30
+ .set_chat(12345)
31
+ .set_text("Hello from Ruby")
32
+
33
+ client.messages.send(message)
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ ```ruby
39
+ client = MaxBotApi::Client.new(
40
+ token: ENV.fetch("TOKEN"),
41
+ base_url: "https://botapi.max.ru/",
42
+ version: "1.2.5"
43
+ )
44
+ ```
@@ -0,0 +1,64 @@
1
+ # 2. Listen and Respond
2
+
3
+ You can receive updates via long polling or webhooks.
4
+
5
+ ## Long polling
6
+
7
+ ```ruby
8
+ client = MaxBotApi::Client.new(token: ENV.fetch("TOKEN"))
9
+
10
+ client.each_update do |update|
11
+ case update[:update_type]
12
+ when "message_created"
13
+ text = update.dig(:message, :body, :text)
14
+ puts "New message: #{text}"
15
+ when "bot_started"
16
+ puts "Bot started"
17
+ end
18
+ end
19
+ ```
20
+
21
+ ### Advanced polling options
22
+
23
+ ```ruby
24
+ client.each_update(
25
+ limit: 50,
26
+ timeout: 30,
27
+ pause: 1,
28
+ types: ["message_created", "message_callback"]
29
+ ) do |update|
30
+ # ...
31
+ end
32
+ ```
33
+
34
+ ## Webhook handling
35
+
36
+ ```ruby
37
+ post "/webhook" do
38
+ update = client.parse_webhook(request.body.read)
39
+
40
+ case update[:update_type]
41
+ when "message_created"
42
+ # ...
43
+ end
44
+
45
+ status 200
46
+ end
47
+ ```
48
+
49
+ ## Updates model
50
+
51
+ Updates are returned as hashes. The `update_type` field maps to the official types:
52
+
53
+ - `message_created`
54
+ - `message_edited`
55
+ - `message_removed`
56
+ - `message_callback`
57
+ - `bot_added`
58
+ - `bot_removed`
59
+ - `bot_started`
60
+ - `user_added`
61
+ - `user_removed`
62
+ - `chat_title_changed`
63
+
64
+ If you enable `debug: true` in `parse_webhook` or `each_update`, the update includes `:debug_raw`.
@@ -0,0 +1,49 @@
1
+ # 3. Attachments
2
+
3
+ Use `uploads` to upload files and then attach them to a message.
4
+
5
+ ## Upload from file
6
+
7
+ ```ruby
8
+ photo = client.uploads.upload_photo_from_file(path: "./image.png")
9
+
10
+ message = MaxBotApi::Builders::MessageBuilder.new
11
+ .set_chat(12345)
12
+ .add_photo(photo)
13
+
14
+ client.messages.send(message)
15
+ ```
16
+
17
+ ## Upload from URL
18
+
19
+ ```ruby
20
+ audio = client.uploads.upload_media_from_url(type: :audio, url: "https://example.com/audio.mp3")
21
+
22
+ message = MaxBotApi::Builders::MessageBuilder.new
23
+ .set_chat(12345)
24
+ .add_audio(audio)
25
+
26
+ client.messages.send(message)
27
+ ```
28
+
29
+ ## Other attachment helpers
30
+
31
+ - `add_video(uploaded_info)`
32
+ - `add_file(uploaded_info)`
33
+ - `add_location(lat, lon)`
34
+ - `add_contact(name:, contact_id:, vcf_info:, vcf_phone:)`
35
+ - `add_sticker(code)`
36
+
37
+ ## Photo tokens
38
+
39
+ When you upload photos, you receive a token payload. Pass it to `add_photo`:
40
+
41
+ ```ruby
42
+ photo_tokens = client.uploads.upload_photo_from_file(path: "./image.png")
43
+
44
+ message = MaxBotApi::Builders::MessageBuilder.new
45
+ .set_chat(12345)
46
+ .add_photo(photo_tokens)
47
+
48
+ client.messages.send(message)
49
+ ```
@@ -0,0 +1,36 @@
1
+ # 4. Keyboard
2
+
3
+ The keyboard builder mirrors the Go client.
4
+
5
+ ```ruby
6
+ keyboard = client.messages.new_keyboard_builder
7
+ keyboard
8
+ .add_row
9
+ .add_geolocation("Share location", true)
10
+ .add_contact("Share contact")
11
+
12
+ keyboard
13
+ .add_row
14
+ .add_link("Open MAX", "positive", "https://max.ru")
15
+ .add_callback("Audio", "negative", "audio")
16
+ .add_callback("Video", "negative", "video")
17
+
18
+ keyboard
19
+ .add_row
20
+ .add_callback("Picture", "positive", "picture")
21
+
22
+ message = MaxBotApi::Builders::MessageBuilder.new
23
+ .set_chat(12345)
24
+ .add_keyboard(keyboard)
25
+ .set_text("Choose an action")
26
+
27
+ client.messages.send(message)
28
+ ```
29
+
30
+ ## Button types
31
+
32
+ - `add_callback(text, intent, payload)`
33
+ - `add_link(text, intent, url)`
34
+ - `add_contact(text)`
35
+ - `add_geolocation(text, quick)`
36
+ - `add_open_app(text, app, payload, contact_id)`
@@ -0,0 +1,50 @@
1
+ # 5. Uploads
2
+
3
+ Uploads use a two-step flow:
4
+
5
+ 1. `POST /uploads?type=...` to get a temporary upload URL.
6
+ 2. `POST` multipart data to that URL with field `data`.
7
+
8
+ This is handled automatically by `MaxBotApi::Resources::Uploads`.
9
+
10
+ ## Media upload types
11
+
12
+ - `:photo` (image)
13
+ - `:video`
14
+ - `:audio`
15
+ - `:file`
16
+
17
+ ## From disk
18
+
19
+ ```ruby
20
+ video = client.uploads.upload_media_from_file(type: :video, filename: "./video.mp4")
21
+ message = MaxBotApi::Builders::MessageBuilder.new
22
+ .set_chat(12345)
23
+ .add_video(video)
24
+
25
+ client.messages.send(message)
26
+ ```
27
+
28
+ ## From URL
29
+
30
+ ```ruby
31
+ doc = client.uploads.upload_media_from_url(type: :file, url: "https://example.com/doc.pdf")
32
+ message = MaxBotApi::Builders::MessageBuilder.new
33
+ .set_chat(12345)
34
+ .add_file(doc)
35
+
36
+ client.messages.send(message)
37
+ ```
38
+
39
+ ## From IO
40
+
41
+ ```ruby
42
+ File.open("./audio.mp3", "rb") do |io|
43
+ audio = client.uploads.upload_media_from_reader(type: :audio, io: io)
44
+ message = MaxBotApi::Builders::MessageBuilder.new
45
+ .set_chat(12345)
46
+ .add_audio(audio)
47
+
48
+ client.messages.send(message)
49
+ end
50
+ ```
@@ -0,0 +1,28 @@
1
+ # 6. Subscriptions (Webhooks)
2
+
3
+ Use subscriptions to receive webhook updates.
4
+
5
+ ## Subscribe
6
+
7
+ ```ruby
8
+ client = MaxBotApi::Client.new(token: ENV.fetch("TOKEN"))
9
+
10
+ client.subscriptions.subscribe(
11
+ url: "https://example.com/webhook",
12
+ update_types: ["message_created", "message_callback"],
13
+ secret: "my-secret"
14
+ )
15
+ ```
16
+
17
+ ## Unsubscribe
18
+
19
+ ```ruby
20
+ client.subscriptions.unsubscribe(url: "https://example.com/webhook")
21
+ ```
22
+
23
+ ## List subscriptions
24
+
25
+ ```ruby
26
+ subscriptions = client.subscriptions.get_subscriptions
27
+ puts subscriptions[:subscriptions].inspect
28
+ ```
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+
5
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
6
+ chat_id = ENV.fetch('CHAT_ID').to_i
7
+
8
+ photo = client.uploads.upload_photo_from_file(path: './image.png')
9
+
10
+ message = MaxBotApi::Builders::MessageBuilder.new
11
+ .set_chat(chat_id)
12
+ .add_photo(photo)
13
+ .set_text('Photo attached')
14
+
15
+ client.messages.send(message)
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+
5
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
6
+
7
+ message = MaxBotApi::Builders::MessageBuilder.new
8
+ .set_chat(ENV.fetch('CHAT_ID').to_i)
9
+ .set_text('Hello from Ruby')
10
+
11
+ client.messages.send(message)
12
+ puts 'Sent'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+
5
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
6
+ chat_id = ENV.fetch('CHAT_ID').to_i
7
+
8
+ keyboard = client.messages.new_keyboard_builder
9
+ keyboard
10
+ .add_row
11
+ .add_geolocation('Share location', true)
12
+ .add_contact('Share contact')
13
+
14
+ keyboard
15
+ .add_row
16
+ .add_link('Open MAX', 'positive', 'https://max.ru')
17
+ .add_callback('Audio', 'negative', 'audio')
18
+
19
+ message = MaxBotApi::Builders::MessageBuilder.new
20
+ .set_chat(chat_id)
21
+ .add_keyboard(keyboard)
22
+ .set_text('Choose an action')
23
+
24
+ client.messages.send(message)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+
5
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
6
+
7
+ client.each_update do |update|
8
+ next unless update[:update_type] == 'message_created'
9
+
10
+ chat_id = update.dig(:message, :recipient, :chat_id)
11
+ text = update.dig(:message, :body, :text)
12
+
13
+ message = MaxBotApi::Builders::MessageBuilder.new
14
+ .set_chat(chat_id)
15
+ .set_text("Echo: #{text}")
16
+
17
+ client.messages.send(message)
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+
5
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
6
+
7
+ url = ENV.fetch('WEBHOOK_URL')
8
+
9
+ client.subscriptions.subscribe(
10
+ url: url,
11
+ update_types: %w[message_created message_callback],
12
+ secret: 'my-secret'
13
+ )
14
+
15
+ puts "Subscribed to #{url}"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+
5
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
6
+ chat_id = ENV.fetch('CHAT_ID').to_i
7
+
8
+ audio = client.uploads.upload_media_from_file(type: :audio, filename: './audio.mp3')
9
+
10
+ message = MaxBotApi::Builders::MessageBuilder.new
11
+ .set_chat(chat_id)
12
+ .add_audio(audio)
13
+
14
+ client.messages.send(message)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'max_bot_api'
4
+ require 'rack'
5
+
6
+ client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
7
+
8
+ app = lambda do |env|
9
+ req = Rack::Request.new(env)
10
+ if req.post? && req.path == '/webhook'
11
+ update = client.parse_webhook(req.body.read)
12
+
13
+ if update[:update_type] == 'message_created'
14
+ chat_id = update.dig(:message, :recipient, :chat_id)
15
+ message = MaxBotApi::Builders::MessageBuilder.new
16
+ .set_chat(chat_id)
17
+ .set_text('Hello from webhook')
18
+
19
+ client.messages.send(message)
20
+ end
21
+
22
+ [200, { 'Content-Type' => 'text/plain' }, ['ok']]
23
+ else
24
+ [404, { 'Content-Type' => 'text/plain' }, ['not found']]
25
+ end
26
+ end
27
+
28
+ Rack::Handler::WEBrick.run(app, Port: 10_888)
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MaxBotApi
4
+ module Builders
5
+ # Builder for inline keyboards.
6
+ class KeyboardBuilder
7
+ def initialize
8
+ @rows = []
9
+ end
10
+
11
+ # Add a new row to the keyboard.
12
+ def add_row
13
+ row = KeyboardRowBuilder.new
14
+ @rows << row
15
+ row
16
+ end
17
+
18
+ # Build keyboard payload.
19
+ def build
20
+ { buttons: @rows.map(&:build) }
21
+ end
22
+ end
23
+
24
+ # Builder for a single keyboard row.
25
+ class KeyboardRowBuilder
26
+ def initialize
27
+ @cols = []
28
+ end
29
+
30
+ # Add a prebuilt button hash.
31
+ def add_button(button)
32
+ @cols << button
33
+ self
34
+ end
35
+
36
+ # Add a link button.
37
+ def add_link(text, _intent, url)
38
+ button = {
39
+ type: 'link',
40
+ text: text,
41
+ url: url
42
+ }
43
+ add_button(button)
44
+ end
45
+
46
+ # Add a callback button.
47
+ def add_callback(text, intent, payload)
48
+ button = {
49
+ type: 'callback',
50
+ text: text,
51
+ payload: payload,
52
+ intent: intent
53
+ }
54
+ add_button(button)
55
+ end
56
+
57
+ # Add a contact request button.
58
+ def add_contact(text)
59
+ button = {
60
+ type: 'request_contact',
61
+ text: text
62
+ }
63
+ add_button(button)
64
+ end
65
+
66
+ # Add a geolocation request button.
67
+ def add_geolocation(text, quick)
68
+ button = {
69
+ type: 'request_geo_location',
70
+ text: text,
71
+ quick: quick
72
+ }
73
+ add_button(button)
74
+ end
75
+
76
+ # Add an open app button.
77
+ def add_open_app(text, app, payload, contact_id)
78
+ button = {
79
+ type: 'open_app',
80
+ text: text,
81
+ web_app: app,
82
+ payload: payload,
83
+ contact_id: contact_id
84
+ }
85
+ add_button(button)
86
+ end
87
+
88
+ # Build row payload.
89
+ def build
90
+ @cols
91
+ end
92
+ end
93
+ end
94
+ end