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 +7 -0
- data/README.md +168 -0
- data/docs/01-your-first-bot.md +44 -0
- data/docs/02-listen-and-respond.md +64 -0
- data/docs/03-attachments.md +49 -0
- data/docs/04-keyboard.md +36 -0
- data/docs/05-uploads.md +50 -0
- data/docs/06-subscriptions.md +28 -0
- data/examples/attachments.rb +15 -0
- data/examples/basic_send.rb +12 -0
- data/examples/keyboard.rb +24 -0
- data/examples/long_poll.rb +18 -0
- data/examples/subscriptions.rb +15 -0
- data/examples/uploads.rb +14 -0
- data/examples/webhook_rack.rb +28 -0
- data/lib/max_bot_api/builders/keyboard_builder.rb +94 -0
- data/lib/max_bot_api/builders/message_builder.rb +196 -0
- data/lib/max_bot_api/client.rb +225 -0
- data/lib/max_bot_api/errors.rb +91 -0
- data/lib/max_bot_api/resources/bots.rb +23 -0
- data/lib/max_bot_api/resources/chats.rb +91 -0
- data/lib/max_bot_api/resources/debugs.rb +35 -0
- data/lib/max_bot_api/resources/messages.rb +138 -0
- data/lib/max_bot_api/resources/subscriptions.rb +38 -0
- data/lib/max_bot_api/resources/uploads.rb +115 -0
- data/lib/max_bot_api/updates/parser.rb +60 -0
- data/lib/max_bot_api/version.rb +6 -0
- data/lib/max_bot_api.rb +21 -0
- metadata +140 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxBotApi
|
|
4
|
+
module Builders
|
|
5
|
+
# Builder for message payloads.
|
|
6
|
+
class MessageBuilder
|
|
7
|
+
attr_reader :user_id, :chat_id, :reset
|
|
8
|
+
|
|
9
|
+
# Create a builder from an existing hash payload.
|
|
10
|
+
# @param hash [Hash]
|
|
11
|
+
# @return [MessageBuilder]
|
|
12
|
+
def self.from_hash(hash)
|
|
13
|
+
builder = new
|
|
14
|
+
return builder if hash.nil?
|
|
15
|
+
|
|
16
|
+
builder.set_user(hash[:user_id] || hash['user_id']) if hash.key?(:user_id) || hash.key?('user_id')
|
|
17
|
+
builder.set_chat(hash[:chat_id] || hash['chat_id']) if hash.key?(:chat_id) || hash.key?('chat_id')
|
|
18
|
+
builder.set_reset(hash[:reset] || hash['reset']) if hash.key?(:reset) || hash.key?('reset')
|
|
19
|
+
|
|
20
|
+
payload = hash[:message] || hash['message'] || hash
|
|
21
|
+
builder.apply_payload(payload)
|
|
22
|
+
builder
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Initialize a new builder with empty payload.
|
|
26
|
+
def initialize
|
|
27
|
+
@user_id = nil
|
|
28
|
+
@chat_id = nil
|
|
29
|
+
@reset = false
|
|
30
|
+
@message = {
|
|
31
|
+
attachments: []
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Set recipient user ID.
|
|
36
|
+
def set_user(user_id)
|
|
37
|
+
@user_id = user_id
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Set recipient chat ID.
|
|
42
|
+
def set_chat(chat_id)
|
|
43
|
+
@chat_id = chat_id
|
|
44
|
+
self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Toggle reset mode (skip Authorization header).
|
|
48
|
+
def set_reset(reset)
|
|
49
|
+
@reset = !!reset
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Set message text.
|
|
54
|
+
def set_text(text)
|
|
55
|
+
@message[:text] = text
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Set message format (markdown/html).
|
|
60
|
+
def set_format(format)
|
|
61
|
+
@message[:format] = format
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Toggle notification flag.
|
|
66
|
+
def set_notify(notify)
|
|
67
|
+
@message[:notify] = notify
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Set reply with explicit message ID.
|
|
72
|
+
def set_reply(text, message_id)
|
|
73
|
+
@message[:text] = text
|
|
74
|
+
@message[:link] = { type: 'reply', mid: message_id }
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Reply to a message hash with inferred recipient.
|
|
79
|
+
def reply(text, reply_message)
|
|
80
|
+
recipient = reply_message[:recipient] || reply_message['recipient'] || {}
|
|
81
|
+
set_user(recipient[:user_id] || recipient['user_id']) if recipient[:user_id] || recipient['user_id']
|
|
82
|
+
set_chat(recipient[:chat_id] || recipient['chat_id']) if recipient[:chat_id] || recipient['chat_id']
|
|
83
|
+
|
|
84
|
+
body = reply_message[:body] || reply_message['body'] || {}
|
|
85
|
+
@message[:text] = text
|
|
86
|
+
@message[:link] = { type: 'reply', mid: body[:mid] || body['mid'] }
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Add user mention markup.
|
|
91
|
+
def add_markup(user_id, from, length)
|
|
92
|
+
@message[:markup] ||= []
|
|
93
|
+
@message[:markup] << { user_id: user_id, from: from, length: length, type: 'user_mention' }
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Attach a keyboard.
|
|
98
|
+
def add_keyboard(keyboard)
|
|
99
|
+
payload = keyboard.is_a?(Builders::KeyboardBuilder) ? keyboard.build : keyboard
|
|
100
|
+
add_attachment(type: 'inline_keyboard', payload: payload)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Attach a photo payload.
|
|
104
|
+
def add_photo(photo_tokens)
|
|
105
|
+
payload = photo_tokens.is_a?(Hash) ? photo_tokens : { photos: photo_tokens }
|
|
106
|
+
add_attachment(type: 'image', payload: { photos: payload[:photos] || payload['photos'] })
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Attach audio payload.
|
|
110
|
+
def add_audio(uploaded_info)
|
|
111
|
+
add_attachment(type: 'audio', payload: uploaded_info)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Attach video payload.
|
|
115
|
+
def add_video(uploaded_info)
|
|
116
|
+
add_attachment(type: 'video', payload: uploaded_info)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Attach file payload.
|
|
120
|
+
def add_file(uploaded_info)
|
|
121
|
+
add_attachment(type: 'file', payload: uploaded_info)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Attach a location.
|
|
125
|
+
def add_location(lat, lon)
|
|
126
|
+
add_attachment(type: 'location', latitude: lat, longitude: lon)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Attach a contact card.
|
|
130
|
+
def add_contact(name:, contact_id:, vcf_info: nil, vcf_phone: nil)
|
|
131
|
+
payload = {
|
|
132
|
+
name: name,
|
|
133
|
+
contact_id: contact_id,
|
|
134
|
+
vcf_info: vcf_info,
|
|
135
|
+
vcf_phone: vcf_phone
|
|
136
|
+
}.compact
|
|
137
|
+
add_attachment(type: 'contact', payload: payload)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Attach a sticker.
|
|
141
|
+
def add_sticker(code)
|
|
142
|
+
add_attachment(type: 'sticker', payload: { code: code })
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Set bot token for reset mode.
|
|
146
|
+
def set_bot_token(token)
|
|
147
|
+
@message[:bot_token] = token
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Set phone numbers for notify/exists.
|
|
152
|
+
def set_phone_numbers(numbers)
|
|
153
|
+
@message[:phone_numbers] = Array(numbers)
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Return bot token used for reset mode.
|
|
158
|
+
def bot_token
|
|
159
|
+
@message[:bot_token]
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Return phone numbers used for notify/exists.
|
|
163
|
+
def phone_numbers
|
|
164
|
+
@message[:phone_numbers]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Whether reset mode is enabled.
|
|
168
|
+
def reset?
|
|
169
|
+
@reset
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Return the message payload hash.
|
|
173
|
+
def to_h
|
|
174
|
+
@message
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
protected
|
|
178
|
+
|
|
179
|
+
def apply_payload(payload)
|
|
180
|
+
return if payload.nil?
|
|
181
|
+
|
|
182
|
+
payload.each do |key, value|
|
|
183
|
+
@message[key.to_sym] = value
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def add_attachment(attachment)
|
|
190
|
+
@message[:attachments] ||= []
|
|
191
|
+
@message[:attachments] << attachment
|
|
192
|
+
self
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module MaxBotApi
|
|
6
|
+
# Main API client. Holds auth config and provides resource accessors.
|
|
7
|
+
class Client
|
|
8
|
+
# Default API base URL.
|
|
9
|
+
DEFAULT_BASE_URL = 'https://botapi.max.ru/'
|
|
10
|
+
# Default API version appended as query param.
|
|
11
|
+
DEFAULT_VERSION = '1.2.5'
|
|
12
|
+
# Default pause between update polling loops.
|
|
13
|
+
DEFAULT_PAUSE = 1
|
|
14
|
+
# Default limit for updates requests.
|
|
15
|
+
DEFAULT_UPDATES_LIMIT = 50
|
|
16
|
+
# Max retry attempts for update polling.
|
|
17
|
+
MAX_RETRIES = 3
|
|
18
|
+
|
|
19
|
+
# @return [String] bot token
|
|
20
|
+
# @return [String] base URL
|
|
21
|
+
# @return [String] API version
|
|
22
|
+
attr_reader :token, :base_url, :version
|
|
23
|
+
|
|
24
|
+
# @param token [String] bot token
|
|
25
|
+
# @param base_url [String] API base URL
|
|
26
|
+
# @param version [String] API version
|
|
27
|
+
# @param faraday [Faraday::Connection, nil] custom Faraday connection
|
|
28
|
+
# @param adapter [Symbol] Faraday adapter
|
|
29
|
+
def initialize(token:, base_url: DEFAULT_BASE_URL, version: DEFAULT_VERSION, faraday: nil,
|
|
30
|
+
adapter: Faraday.default_adapter)
|
|
31
|
+
raise EmptyTokenError, 'bot token is empty' if token.to_s.empty?
|
|
32
|
+
|
|
33
|
+
@token = token
|
|
34
|
+
@base_url = normalize_base_url(base_url)
|
|
35
|
+
@version = version.to_s.empty? ? DEFAULT_VERSION : version.to_s
|
|
36
|
+
|
|
37
|
+
@conn = faraday || Faraday.new(url: @base_url) do |f|
|
|
38
|
+
f.request :multipart
|
|
39
|
+
f.request :url_encoded
|
|
40
|
+
f.adapter adapter
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def bots
|
|
45
|
+
Resources::Bots.new(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def chats
|
|
49
|
+
Resources::Chats.new(self)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def messages
|
|
53
|
+
Resources::Messages.new(self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def subscriptions
|
|
57
|
+
Resources::Subscriptions.new(self)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def uploads
|
|
61
|
+
Resources::Uploads.new(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Build a debug sender bound to a chat.
|
|
65
|
+
# @param chat_id [Integer]
|
|
66
|
+
def debugs(chat_id: 0)
|
|
67
|
+
Resources::Debugs.new(self, chat_id: chat_id)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Fetch updates from the API.
|
|
71
|
+
# @param limit [Integer, nil]
|
|
72
|
+
# @param timeout [Integer, nil]
|
|
73
|
+
# @param marker [Integer, nil]
|
|
74
|
+
# @param types [Array<String>, nil]
|
|
75
|
+
# @param debug [Boolean]
|
|
76
|
+
def get_updates(limit: nil, timeout: nil, marker: nil, types: nil, debug: false)
|
|
77
|
+
query = {}
|
|
78
|
+
query['limit'] = limit if limit && limit.to_i > 0
|
|
79
|
+
query['timeout'] = timeout.to_i if timeout && timeout.to_i > 0
|
|
80
|
+
query['marker'] = marker if marker && marker.to_i > 0
|
|
81
|
+
Array(types).each { |t| (query['types'] ||= []) << t }
|
|
82
|
+
|
|
83
|
+
result = request(:get, 'updates', query: query)
|
|
84
|
+
Updates::Parser.parse_update_list(result, debug: debug)
|
|
85
|
+
rescue TimeoutError
|
|
86
|
+
{ updates: [], marker: nil }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Fetch updates with retry/backoff.
|
|
90
|
+
def get_updates_with_retry(limit: nil, timeout: nil, marker: nil, types: nil, debug: false)
|
|
91
|
+
last_error = nil
|
|
92
|
+
|
|
93
|
+
MAX_RETRIES.times do |attempt|
|
|
94
|
+
return get_updates(limit: limit, timeout: timeout, marker: marker, types: types, debug: debug)
|
|
95
|
+
rescue Error => e
|
|
96
|
+
last_error = e
|
|
97
|
+
raise e if attempt == MAX_RETRIES - 1
|
|
98
|
+
|
|
99
|
+
sleep(2**attempt)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
raise last_error
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns an enumerator that yields updates indefinitely.
|
|
106
|
+
def updates_enum(pause: DEFAULT_PAUSE, limit: DEFAULT_UPDATES_LIMIT, timeout: nil, types: nil, debug: false)
|
|
107
|
+
Enumerator.new do |yielder|
|
|
108
|
+
marker = nil
|
|
109
|
+
loop do
|
|
110
|
+
updates_list = get_updates_with_retry(limit: limit, timeout: timeout, marker: marker, types: types,
|
|
111
|
+
debug: debug)
|
|
112
|
+
updates = Array(updates_list[:updates])
|
|
113
|
+
|
|
114
|
+
updates.each { |update| yielder << update }
|
|
115
|
+
marker = updates_list[:marker] if updates_list[:marker]
|
|
116
|
+
|
|
117
|
+
sleep(pause)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Iterates over updates, yielding each update hash.
|
|
123
|
+
def each_update(pause: DEFAULT_PAUSE, limit: DEFAULT_UPDATES_LIMIT, timeout: nil, types: nil, debug: false, &block)
|
|
124
|
+
return updates_enum(pause: pause, limit: limit, timeout: timeout, types: types, debug: debug) unless block
|
|
125
|
+
|
|
126
|
+
updates_enum(pause: pause, limit: limit, timeout: timeout, types: types, debug: debug).each(&block)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Parses a single webhook payload into an update hash.
|
|
130
|
+
def parse_webhook(body, debug: false)
|
|
131
|
+
Updates::Parser.parse_update(body.to_s, debug: debug)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Perform an HTTP request.
|
|
135
|
+
# @param method [Symbol]
|
|
136
|
+
# @param path [String]
|
|
137
|
+
# @param query [Hash]
|
|
138
|
+
# @param body [Hash, Array, String, nil]
|
|
139
|
+
# @param headers [Hash]
|
|
140
|
+
# @param reset [Boolean]
|
|
141
|
+
def request(method, path, query: nil, body: nil, headers: {}, reset: false)
|
|
142
|
+
query = (query || {}).dup
|
|
143
|
+
query['v'] = version
|
|
144
|
+
|
|
145
|
+
response = @conn.public_send(method) do |req|
|
|
146
|
+
req.url(path.to_s.sub(%r{\A/}, ''))
|
|
147
|
+
req.params.update(query) unless query.empty?
|
|
148
|
+
req.headers['User-Agent'] = "max-bot-api-client-ruby/#{VERSION}"
|
|
149
|
+
req.headers['Authorization'] = token unless reset
|
|
150
|
+
headers.each { |k, v| req.headers[k] = v }
|
|
151
|
+
|
|
152
|
+
if body
|
|
153
|
+
if body.is_a?(Hash) || body.is_a?(Array)
|
|
154
|
+
if multipart_body?(body)
|
|
155
|
+
req.body = body
|
|
156
|
+
else
|
|
157
|
+
req.headers['Content-Type'] ||= 'application/json'
|
|
158
|
+
req.body = JSON.generate(body)
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
req.body = body
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
handle_response(response)
|
|
167
|
+
rescue Faraday::TimeoutError => e
|
|
168
|
+
raise TimeoutError.new(op: "#{method.to_s.upcase} #{path}", reason: e.message)
|
|
169
|
+
rescue Faraday::ConnectionFailed, Faraday::SSLError, Faraday::Error => e
|
|
170
|
+
raise NetworkError.new(op: "#{method.to_s.upcase} #{path}", original_error: e)
|
|
171
|
+
rescue JSON::GeneratorError => e
|
|
172
|
+
raise SerializationError.new(op: 'marshal', type: 'request body', original_error: e)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def http_client
|
|
176
|
+
@conn
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def normalize_base_url(url)
|
|
182
|
+
uri = URI.parse(url.to_s)
|
|
183
|
+
raise InvalidUrlError, 'invalid API URL' if uri.scheme.nil? || uri.host.nil?
|
|
184
|
+
|
|
185
|
+
normalized = uri.to_s
|
|
186
|
+
normalized.end_with?('/') ? normalized : "#{normalized}/"
|
|
187
|
+
rescue URI::InvalidURIError => e
|
|
188
|
+
raise InvalidUrlError, "invalid API URL: #{e.message}"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def multipart_body?(body)
|
|
192
|
+
body.values.any? { |value| value.is_a?(Faraday::Multipart::FilePart) }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def handle_response(response)
|
|
196
|
+
return parse_body(response) if response.status == 200
|
|
197
|
+
|
|
198
|
+
api_message = parse_error_message(response)
|
|
199
|
+
raise ApiError.new(code: response.status, message: api_message)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def parse_body(response)
|
|
203
|
+
content_type = response.headers['content-type'].to_s
|
|
204
|
+
return response.body unless content_type.include?('application/json')
|
|
205
|
+
|
|
206
|
+
body = response.body.to_s
|
|
207
|
+
return nil if body.empty?
|
|
208
|
+
|
|
209
|
+
JSON.parse(body, symbolize_names: true)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def parse_error_message(response)
|
|
213
|
+
body = response.body.to_s
|
|
214
|
+
return response.reason_phrase.to_s if body.empty?
|
|
215
|
+
|
|
216
|
+
json = JSON.parse(body)
|
|
217
|
+
return json['error'] if json.is_a?(Hash) && json['error']
|
|
218
|
+
return json['message'] if json.is_a?(Hash) && json['message']
|
|
219
|
+
|
|
220
|
+
response.reason_phrase.to_s
|
|
221
|
+
rescue JSON::ParserError
|
|
222
|
+
response.reason_phrase.to_s
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxBotApi
|
|
4
|
+
# Base error for all library exceptions.
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when the bot token is missing.
|
|
8
|
+
class EmptyTokenError < Error; end
|
|
9
|
+
# Raised when the base URL is invalid.
|
|
10
|
+
class InvalidUrlError < Error; end
|
|
11
|
+
|
|
12
|
+
# Raised when the API responds with a non-200 status code.
|
|
13
|
+
class ApiError < Error
|
|
14
|
+
attr_reader :code, :message, :details
|
|
15
|
+
|
|
16
|
+
# @param code [Integer] HTTP status code
|
|
17
|
+
# @param message [String] error message
|
|
18
|
+
# @param details [String, nil] optional details
|
|
19
|
+
def initialize(code:, message:, details: nil)
|
|
20
|
+
@code = code
|
|
21
|
+
@message = message
|
|
22
|
+
@details = details
|
|
23
|
+
super(build_message)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ==(other)
|
|
27
|
+
other.is_a?(ApiError) && other.code == code
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def build_message
|
|
33
|
+
return "API error #{code}: #{message} (#{details})" if details && !details.empty?
|
|
34
|
+
|
|
35
|
+
"API error #{code}: #{message}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Raised when the HTTP request fails before reaching the API.
|
|
40
|
+
class NetworkError < Error
|
|
41
|
+
attr_reader :op, :original_error
|
|
42
|
+
|
|
43
|
+
# @param op [String] operation name
|
|
44
|
+
# @param original_error [Exception] original error
|
|
45
|
+
def initialize(op:, original_error:)
|
|
46
|
+
@op = op
|
|
47
|
+
@original_error = original_error
|
|
48
|
+
super("network error during #{op}: #{original_error}")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Raised when the request times out.
|
|
53
|
+
class TimeoutError < Error
|
|
54
|
+
attr_reader :op, :reason
|
|
55
|
+
|
|
56
|
+
# @param op [String] operation name
|
|
57
|
+
# @param reason [String, nil] timeout reason
|
|
58
|
+
def initialize(op:, reason: nil)
|
|
59
|
+
@op = op
|
|
60
|
+
@reason = reason
|
|
61
|
+
super(build_message)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def timeout?
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def build_message
|
|
71
|
+
return "timeout error during #{op}: #{reason}" if reason && !reason.empty?
|
|
72
|
+
|
|
73
|
+
"timeout error during #{op}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Raised when serialization or parsing fails.
|
|
78
|
+
class SerializationError < Error
|
|
79
|
+
attr_reader :op, :type, :original_error
|
|
80
|
+
|
|
81
|
+
# @param op [String] operation name
|
|
82
|
+
# @param type [String] serialized object type
|
|
83
|
+
# @param original_error [Exception] original error
|
|
84
|
+
def initialize(op:, type:, original_error:)
|
|
85
|
+
@op = op
|
|
86
|
+
@type = type
|
|
87
|
+
@original_error = original_error
|
|
88
|
+
super("serialization error during #{op} of #{type}: #{original_error}")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxBotApi
|
|
4
|
+
module Resources
|
|
5
|
+
# Bots API methods.
|
|
6
|
+
class Bots
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Get current bot info.
|
|
12
|
+
def get_bot
|
|
13
|
+
@client.request(:get, 'me')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Patch current bot info.
|
|
17
|
+
# @param patch [Hash]
|
|
18
|
+
def patch_bot(patch)
|
|
19
|
+
@client.request(:patch, 'me', body: patch)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxBotApi
|
|
4
|
+
module Resources
|
|
5
|
+
# Chats API methods.
|
|
6
|
+
class Chats
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# List chats with pagination.
|
|
12
|
+
# @param count [Integer, nil]
|
|
13
|
+
# @param marker [Integer, nil]
|
|
14
|
+
def get_chats(count: nil, marker: nil)
|
|
15
|
+
query = {}
|
|
16
|
+
query['count'] = count if count && count.to_i > 0
|
|
17
|
+
query['marker'] = marker if marker && marker.to_i > 0
|
|
18
|
+
@client.request(:get, 'chats', query: query)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Fetch a single chat.
|
|
22
|
+
# @param chat_id [Integer]
|
|
23
|
+
def get_chat(chat_id:)
|
|
24
|
+
@client.request(:get, "chats/#{chat_id}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Fetch current bot membership info.
|
|
28
|
+
# @param chat_id [Integer]
|
|
29
|
+
def get_chat_membership(chat_id:)
|
|
30
|
+
@client.request(:get, "chats/#{chat_id}/members/me")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# List chat members.
|
|
34
|
+
# @param chat_id [Integer]
|
|
35
|
+
def get_chat_members(chat_id:, count: nil, marker: nil)
|
|
36
|
+
query = {}
|
|
37
|
+
query['count'] = count if count && count.to_i > 0
|
|
38
|
+
query['marker'] = marker unless marker.nil?
|
|
39
|
+
@client.request(:get, "chats/#{chat_id}/members", query: query)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Fetch specific members by user IDs.
|
|
43
|
+
# @param chat_id [Integer]
|
|
44
|
+
# @param user_ids [Array<Integer>]
|
|
45
|
+
def get_specific_chat_members(chat_id:, user_ids:)
|
|
46
|
+
ids = Array(user_ids).map(&:to_s).join(',')
|
|
47
|
+
@client.request(:get, "chats/#{chat_id}/members", query: { 'user_ids' => ids })
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# List chat admins.
|
|
51
|
+
# @param chat_id [Integer]
|
|
52
|
+
def get_chat_admins(chat_id:)
|
|
53
|
+
@client.request(:get, "chats/#{chat_id}/members/admins")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Leave a chat.
|
|
57
|
+
# @param chat_id [Integer]
|
|
58
|
+
def leave_chat(chat_id:)
|
|
59
|
+
@client.request(:delete, "chats/#{chat_id}/members/me")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Patch chat info.
|
|
63
|
+
# @param chat_id [Integer]
|
|
64
|
+
# @param update [Hash]
|
|
65
|
+
def edit_chat(chat_id:, update:)
|
|
66
|
+
@client.request(:patch, "chats/#{chat_id}", body: update)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Add members to chat.
|
|
70
|
+
# @param chat_id [Integer]
|
|
71
|
+
# @param users [Hash]
|
|
72
|
+
def add_member(chat_id:, users:)
|
|
73
|
+
@client.request(:post, "chats/#{chat_id}/members", body: users)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Remove a member from chat.
|
|
77
|
+
# @param chat_id [Integer]
|
|
78
|
+
# @param user_id [Integer]
|
|
79
|
+
def remove_member(chat_id:, user_id:)
|
|
80
|
+
@client.request(:delete, "chats/#{chat_id}/members", query: { 'user_id' => user_id })
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Send a chat action.
|
|
84
|
+
# @param chat_id [Integer]
|
|
85
|
+
# @param action [String]
|
|
86
|
+
def send_action(chat_id:, action:)
|
|
87
|
+
@client.request(:post, "chats/#{chat_id}/actions", body: { action: action })
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxBotApi
|
|
4
|
+
module Resources
|
|
5
|
+
# Debug helper for sending raw updates/errors to a chat.
|
|
6
|
+
class Debugs
|
|
7
|
+
def initialize(client, chat_id: 0)
|
|
8
|
+
@client = client
|
|
9
|
+
@chat_id = chat_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Send raw update debug text to the configured chat.
|
|
13
|
+
# @param update [Hash]
|
|
14
|
+
def send(update)
|
|
15
|
+
message = Builders::MessageBuilder.new
|
|
16
|
+
.set_chat(@chat_id)
|
|
17
|
+
.set_text(update[:debug_raw].to_s)
|
|
18
|
+
|
|
19
|
+
@client.request(:post, 'messages', query: { 'chat_id' => @chat_id }, body: message.to_h)
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Send an error message to the configured chat.
|
|
24
|
+
# @param error [Exception, String]
|
|
25
|
+
def send_err(error)
|
|
26
|
+
message = Builders::MessageBuilder.new
|
|
27
|
+
.set_chat(@chat_id)
|
|
28
|
+
.set_text(error.to_s)
|
|
29
|
+
|
|
30
|
+
@client.request(:post, 'messages', query: { 'chat_id' => @chat_id }, body: message.to_h)
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|