max_bot_api 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +23 -1
- data/docs/01-your-first-bot.md +3 -1
- data/docs/03-attachments.md +10 -0
- data/docs/04-keyboard.md +2 -0
- data/examples/attachments.rb +8 -2
- data/examples/keyboard.rb +1 -0
- data/lib/max_bot_api/builders/keyboard_builder.rb +9 -0
- data/lib/max_bot_api/builders/message_builder.rb +8 -0
- data/lib/max_bot_api/client.rb +12 -9
- data/lib/max_bot_api/errors.rb +4 -0
- data/lib/max_bot_api/resources/messages.rb +26 -9
- data/lib/max_bot_api/resources/uploads.rb +34 -2
- data/lib/max_bot_api/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb9285c20da62a0c1fba896afde6af5fd10dff12079de7d44878f3cd4b3aea22
|
|
4
|
+
data.tar.gz: 11d42f73535db90bcbc32b89fa9886cc96d99222cca56e15ba3fb50eb4132ce8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b10c0eb6c1bef5eedc551fc841cb65d43b65d4d43c28941cbbdf8bb85e0245b251b512b4a9b9f476ca859a189c1967e0ac81f9b09b225569bb66afffb70cfd02
|
|
7
|
+
data.tar.gz: 148837674accefde112f9e8195440bce6a7c6a9abd5b8243c87c076b6af72c9b1f148fe66a24104d0ecc3ac82e94011f8841ba3db900842dd8d58f183e895286
|
data/README.md
CHANGED
|
@@ -44,7 +44,7 @@ client.messages.send(
|
|
|
44
44
|
```ruby
|
|
45
45
|
client = MaxBotApi::Client.new(
|
|
46
46
|
token: ENV.fetch("TOKEN"),
|
|
47
|
-
base_url: "https://
|
|
47
|
+
base_url: "https://platform-api.max.ru/",
|
|
48
48
|
version: "1.2.5"
|
|
49
49
|
)
|
|
50
50
|
```
|
|
@@ -54,6 +54,28 @@ Notes:
|
|
|
54
54
|
- The client uses `Authorization: <token>` (no `Bearer`).
|
|
55
55
|
- Every request includes `v=<version>` query param.
|
|
56
56
|
|
|
57
|
+
## 0.2.0 updates
|
|
58
|
+
|
|
59
|
+
- Default API host is now `https://platform-api.max.ru/` (override with `base_url:` if you need the legacy host).
|
|
60
|
+
- Message send/edit retries automatically when the API responds with `attachment.not.ready`.
|
|
61
|
+
- New helpers: `MessageBuilder#add_photo_by_token`, `KeyboardRowBuilder#add_message`.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
message = MaxBotApi::Builders::MessageBuilder.new
|
|
67
|
+
.set_chat(12345)
|
|
68
|
+
.set_text("Hello")
|
|
69
|
+
.add_photo_by_token("photo-token")
|
|
70
|
+
|
|
71
|
+
keyboard = MaxBotApi::Builders::KeyboardBuilder.new
|
|
72
|
+
row = keyboard.add_row
|
|
73
|
+
row.add_message("Continue")
|
|
74
|
+
|
|
75
|
+
message.add_keyboard(keyboard)
|
|
76
|
+
client.messages.send(message)
|
|
77
|
+
```
|
|
78
|
+
|
|
57
79
|
## Common tasks
|
|
58
80
|
|
|
59
81
|
### Send messages
|
data/docs/01-your-first-bot.md
CHANGED
|
@@ -38,7 +38,9 @@ client.messages.send(message)
|
|
|
38
38
|
```ruby
|
|
39
39
|
client = MaxBotApi::Client.new(
|
|
40
40
|
token: ENV.fetch("TOKEN"),
|
|
41
|
-
base_url: "https://
|
|
41
|
+
base_url: "https://platform-api.max.ru/",
|
|
42
42
|
version: "1.2.5"
|
|
43
43
|
)
|
|
44
44
|
```
|
|
45
|
+
|
|
46
|
+
Note: if you still use the legacy host, set `base_url: "https://botapi.max.ru/"`.
|
data/docs/03-attachments.md
CHANGED
|
@@ -47,3 +47,13 @@ message = MaxBotApi::Builders::MessageBuilder.new
|
|
|
47
47
|
|
|
48
48
|
client.messages.send(message)
|
|
49
49
|
```
|
|
50
|
+
|
|
51
|
+
You can also attach a photo directly by token:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
message = MaxBotApi::Builders::MessageBuilder.new
|
|
55
|
+
.set_chat(12345)
|
|
56
|
+
.add_photo_by_token("photo-token")
|
|
57
|
+
|
|
58
|
+
client.messages.send(message)
|
|
59
|
+
```
|
data/docs/04-keyboard.md
CHANGED
|
@@ -18,6 +18,7 @@ keyboard
|
|
|
18
18
|
keyboard
|
|
19
19
|
.add_row
|
|
20
20
|
.add_callback("Picture", "positive", "picture")
|
|
21
|
+
.add_message("Continue")
|
|
21
22
|
|
|
22
23
|
message = MaxBotApi::Builders::MessageBuilder.new
|
|
23
24
|
.set_chat(12345)
|
|
@@ -34,3 +35,4 @@ client.messages.send(message)
|
|
|
34
35
|
- `add_contact(text)`
|
|
35
36
|
- `add_geolocation(text, quick)`
|
|
36
37
|
- `add_open_app(text, app, payload, contact_id)`
|
|
38
|
+
- `add_message(text)`
|
data/examples/attachments.rb
CHANGED
|
@@ -5,11 +5,17 @@ require 'max_bot_api'
|
|
|
5
5
|
client = MaxBotApi::Client.new(token: ENV.fetch('TOKEN'))
|
|
6
6
|
chat_id = ENV.fetch('CHAT_ID').to_i
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
photo_token = ENV['PHOTO_TOKEN']
|
|
9
9
|
|
|
10
10
|
message = MaxBotApi::Builders::MessageBuilder.new
|
|
11
11
|
.set_chat(chat_id)
|
|
12
|
-
.add_photo(photo)
|
|
13
12
|
.set_text('Photo attached')
|
|
14
13
|
|
|
14
|
+
if photo_token && !photo_token.empty?
|
|
15
|
+
message.add_photo_by_token(photo_token)
|
|
16
|
+
else
|
|
17
|
+
photo = client.uploads.upload_photo_from_file(path: './image.png')
|
|
18
|
+
message.add_photo(photo)
|
|
19
|
+
end
|
|
20
|
+
|
|
15
21
|
client.messages.send(message)
|
data/examples/keyboard.rb
CHANGED
|
@@ -4,6 +4,9 @@ module MaxBotApi
|
|
|
4
4
|
module Builders
|
|
5
5
|
# Builder for message payloads.
|
|
6
6
|
class MessageBuilder
|
|
7
|
+
FORMAT_HTML = 'html'
|
|
8
|
+
FORMAT_MARKDOWN = 'markdown'
|
|
9
|
+
|
|
7
10
|
attr_reader :user_id, :chat_id, :reset
|
|
8
11
|
|
|
9
12
|
# Create a builder from an existing hash payload.
|
|
@@ -106,6 +109,11 @@ module MaxBotApi
|
|
|
106
109
|
add_attachment(type: 'image', payload: { photos: payload[:photos] || payload['photos'] })
|
|
107
110
|
end
|
|
108
111
|
|
|
112
|
+
# Attach a photo payload by token.
|
|
113
|
+
def add_photo_by_token(token)
|
|
114
|
+
add_attachment(type: 'image', payload: { token: token })
|
|
115
|
+
end
|
|
116
|
+
|
|
109
117
|
# Attach audio payload.
|
|
110
118
|
def add_audio(uploaded_info)
|
|
111
119
|
add_attachment(type: 'audio', payload: uploaded_info)
|
data/lib/max_bot_api/client.rb
CHANGED
|
@@ -6,7 +6,7 @@ module MaxBotApi
|
|
|
6
6
|
# Main API client. Holds auth config and provides resource accessors.
|
|
7
7
|
class Client
|
|
8
8
|
# Default API base URL.
|
|
9
|
-
DEFAULT_BASE_URL = 'https://
|
|
9
|
+
DEFAULT_BASE_URL = 'https://platform-api.max.ru/'
|
|
10
10
|
# Default API version appended as query param.
|
|
11
11
|
DEFAULT_VERSION = '1.2.5'
|
|
12
12
|
# Default pause between update polling loops.
|
|
@@ -195,8 +195,8 @@ module MaxBotApi
|
|
|
195
195
|
def handle_response(response)
|
|
196
196
|
return parse_body(response) if response.status == 200
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
raise ApiError.new(code: response.status, message:
|
|
198
|
+
error_payload = parse_error_payload(response)
|
|
199
|
+
raise ApiError.new(code: response.status, message: error_payload[:message], details: error_payload[:details])
|
|
200
200
|
end
|
|
201
201
|
|
|
202
202
|
def parse_body(response)
|
|
@@ -209,17 +209,20 @@ module MaxBotApi
|
|
|
209
209
|
JSON.parse(body, symbolize_names: true)
|
|
210
210
|
end
|
|
211
211
|
|
|
212
|
-
def
|
|
212
|
+
def parse_error_payload(response)
|
|
213
213
|
body = response.body.to_s
|
|
214
|
-
return response.reason_phrase.to_s if body.empty?
|
|
214
|
+
return { message: response.reason_phrase.to_s, details: nil } if body.empty?
|
|
215
215
|
|
|
216
216
|
json = JSON.parse(body)
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
if json.is_a?(Hash)
|
|
218
|
+
return { message: json['code'], details: json['message'] } if json['code'] && json['message']
|
|
219
|
+
return { message: json['error'], details: json['details'] || json['message'] } if json['error']
|
|
220
|
+
return { message: json['message'], details: json['details'] } if json['message']
|
|
221
|
+
end
|
|
219
222
|
|
|
220
|
-
response.reason_phrase.to_s
|
|
223
|
+
{ message: response.reason_phrase.to_s, details: nil }
|
|
221
224
|
rescue JSON::ParserError
|
|
222
|
-
response.reason_phrase.to_s
|
|
225
|
+
{ message: response.reason_phrase.to_s, details: nil }
|
|
223
226
|
end
|
|
224
227
|
end
|
|
225
228
|
end
|
data/lib/max_bot_api/errors.rb
CHANGED
|
@@ -14,9 +14,11 @@ module MaxBotApi
|
|
|
14
14
|
def get_messages(chat_id: nil, message_ids: nil, from: nil, to: nil, count: nil)
|
|
15
15
|
query = {}
|
|
16
16
|
query['chat_id'] = chat_id if chat_id && chat_id.to_i != 0
|
|
17
|
-
Array(message_ids).
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
query['message_ids'] = Array(message_ids).join(',') if message_ids && !Array(message_ids).empty?
|
|
18
|
+
if from && to
|
|
19
|
+
from_i = from.to_i
|
|
20
|
+
to_i = to.to_i
|
|
21
|
+
from, to = to, from if from_i > to_i
|
|
20
22
|
end
|
|
21
23
|
query['from'] = from if from && from.to_i != 0
|
|
22
24
|
query['to'] = to if to && to.to_i != 0
|
|
@@ -36,11 +38,13 @@ module MaxBotApi
|
|
|
36
38
|
# @param message_id [String]
|
|
37
39
|
# @param message [MaxBotApi::Builders::MessageBuilder, Hash]
|
|
38
40
|
def edit_message(message_id:, message:)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
with_attachment_retry do
|
|
42
|
+
body = message_payload(message)
|
|
43
|
+
result = @client.request(:put, 'messages', query: { 'message_id' => message_id }, body: body)
|
|
44
|
+
return true if result.is_a?(Hash) && result[:success]
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
raise Error, (result[:message] || 'message update failed')
|
|
47
|
+
end
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
# Delete a message by ID.
|
|
@@ -64,13 +68,13 @@ module MaxBotApi
|
|
|
64
68
|
# Send a message builder without returning the created message.
|
|
65
69
|
# @param message [MaxBotApi::Builders::MessageBuilder, Hash]
|
|
66
70
|
def send(message)
|
|
67
|
-
send_message(message, with_result: false)
|
|
71
|
+
with_attachment_retry { send_message(message, with_result: false) }
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
# Send a message builder and return the created message hash.
|
|
71
75
|
# @param message [MaxBotApi::Builders::MessageBuilder, Hash]
|
|
72
76
|
def send_with_result(message)
|
|
73
|
-
send_message(message, with_result: true)
|
|
77
|
+
with_attachment_retry { send_message(message, with_result: true) }
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
# Check if a message can be sent to the provided phone numbers.
|
|
@@ -133,6 +137,19 @@ module MaxBotApi
|
|
|
133
137
|
end
|
|
134
138
|
end
|
|
135
139
|
end
|
|
140
|
+
|
|
141
|
+
def with_attachment_retry
|
|
142
|
+
attempts = 0
|
|
143
|
+
begin
|
|
144
|
+
yield
|
|
145
|
+
rescue ApiError => e
|
|
146
|
+
attempts += 1
|
|
147
|
+
raise e unless e.attachment_not_ready? && attempts < Client::MAX_RETRIES
|
|
148
|
+
|
|
149
|
+
sleep(2**(attempts - 1))
|
|
150
|
+
retry
|
|
151
|
+
end
|
|
152
|
+
end
|
|
136
153
|
end
|
|
137
154
|
end
|
|
138
155
|
end
|
|
@@ -32,8 +32,12 @@ module MaxBotApi
|
|
|
32
32
|
# @param url [String]
|
|
33
33
|
def upload_media_from_url(type:, url:)
|
|
34
34
|
response = Faraday.get(url.to_s)
|
|
35
|
+
raise Error, "fetch URL failed: HTTP #{response.status}" unless response.status.between?(200, 299)
|
|
36
|
+
|
|
35
37
|
name = attachment_name(response.headers)
|
|
36
38
|
upload_media_from_reader_with_name(type: type, io: StringIO.new(response.body), name: name)
|
|
39
|
+
rescue Faraday::Error => e
|
|
40
|
+
raise NetworkError.new(op: 'GET upload source', original_error: e)
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
# Upload media from an IO object.
|
|
@@ -89,20 +93,48 @@ module MaxBotApi
|
|
|
89
93
|
upload_type = UPLOAD_TYPES.fetch(type.to_sym) { type.to_s }
|
|
90
94
|
endpoint = @client.request(:post, 'uploads', query: { 'type' => upload_type })
|
|
91
95
|
|
|
92
|
-
file_name = name.to_s.empty? ? 'file' : name.to_s
|
|
96
|
+
file_name = name.to_s.empty? ? 'file' : File.basename(name.to_s)
|
|
93
97
|
file_part = Faraday::Multipart::FilePart.new(io, nil, file_name)
|
|
94
98
|
|
|
95
99
|
response = Faraday.post(endpoint[:url]) do |req|
|
|
96
100
|
req.body = { 'data' => file_part }
|
|
97
101
|
end
|
|
98
102
|
|
|
99
|
-
raise
|
|
103
|
+
raise upload_api_error(response) unless response.status.between?(200, 299)
|
|
104
|
+
|
|
105
|
+
return { token: endpoint[:token] } if %w[audio video].include?(upload_type) && endpoint[:token]
|
|
100
106
|
|
|
101
107
|
JSON.parse(response.body, symbolize_names: true)
|
|
102
108
|
rescue Faraday::Error => e
|
|
103
109
|
raise NetworkError.new(op: 'POST uploads', original_error: e)
|
|
104
110
|
end
|
|
105
111
|
|
|
112
|
+
def upload_api_error(response)
|
|
113
|
+
body = response.body.to_s
|
|
114
|
+
return Error.new("upload failed: HTTP #{response.status}") if body.empty?
|
|
115
|
+
|
|
116
|
+
json = begin
|
|
117
|
+
JSON.parse(body)
|
|
118
|
+
rescue StandardError
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
if json.is_a?(Hash)
|
|
122
|
+
if json['code'] && json['message']
|
|
123
|
+
return ApiError.new(code: response.status, message: json['code'], details: json['message'])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if json['error']
|
|
127
|
+
return ApiError.new(code: response.status, message: json['error'],
|
|
128
|
+
details: json['details'] || json['message'])
|
|
129
|
+
end
|
|
130
|
+
if json['message']
|
|
131
|
+
return ApiError.new(code: response.status, message: json['message'], details: json['details'])
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Error.new("upload failed: HTTP #{response.status}")
|
|
136
|
+
end
|
|
137
|
+
|
|
106
138
|
def attachment_name(headers)
|
|
107
139
|
disposition = headers['content-disposition'].to_s
|
|
108
140
|
return '' if disposition.empty?
|
data/lib/max_bot_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: max_bot_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ChatGPT Codex
|
|
8
8
|
- Dmitry Merkushin
|
|
9
|
+
autorequire:
|
|
9
10
|
bindir: bin
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
+
date: 2026-03-05 00:00:00.000000000 Z
|
|
12
13
|
dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
|
14
15
|
name: base64
|
|
@@ -120,6 +121,7 @@ licenses:
|
|
|
120
121
|
metadata:
|
|
121
122
|
homepage_uri: https://github.com/creogen/max_bot_api
|
|
122
123
|
source_code_uri: https://github.com/creogen/max_bot_api/tree/main
|
|
124
|
+
post_install_message:
|
|
123
125
|
rdoc_options: []
|
|
124
126
|
require_paths:
|
|
125
127
|
- lib
|
|
@@ -134,7 +136,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
134
136
|
- !ruby/object:Gem::Version
|
|
135
137
|
version: '0'
|
|
136
138
|
requirements: []
|
|
137
|
-
rubygems_version: 3.
|
|
139
|
+
rubygems_version: 3.4.10
|
|
140
|
+
signing_key:
|
|
138
141
|
specification_version: 4
|
|
139
142
|
summary: Ruby client for MAX Bot API
|
|
140
143
|
test_files: []
|