max_api_client 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/.rubocop.yml +377 -0
- data/CHANGELOG.md +8 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +272 -0
- data/Rakefile +12 -0
- data/lib/max_api_client/api.rb +194 -0
- data/lib/max_api_client/attachments.rb +111 -0
- data/lib/max_api_client/base_api.rb +37 -0
- data/lib/max_api_client/client.rb +178 -0
- data/lib/max_api_client/error.rb +25 -0
- data/lib/max_api_client/polling.rb +95 -0
- data/lib/max_api_client/raw_api.rb +169 -0
- data/lib/max_api_client/upload.rb +147 -0
- data/lib/max_api_client/version.rb +6 -0
- data/lib/max_api_client.rb +23 -0
- data/sig/max_api_client.rbs +162 -0
- metadata +61 -0
data/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# max_api_client
|
|
2
|
+
|
|
3
|
+
Ruby gem для работы с Max Bot API.
|
|
4
|
+
|
|
5
|
+
## Состояние
|
|
6
|
+
|
|
7
|
+
Текущая реализация на Ruby включает API-клиент со следующими возможностями:
|
|
8
|
+
|
|
9
|
+
- высокоуровневый `MaxApiClient::Api`
|
|
10
|
+
- низкоуровневый `MaxApiClient::RawApi`
|
|
11
|
+
- сгруппированные методы для bot/chat/message/subscription/upload
|
|
12
|
+
- вспомогательные объекты вложений для результатов загрузки
|
|
13
|
+
|
|
14
|
+
## Установка
|
|
15
|
+
|
|
16
|
+
Добавьте gem в проект:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
# Gemfile
|
|
20
|
+
gem "max_api_client", git: "git@github.com:Ziaw/max_api_client.git"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Справочник API
|
|
28
|
+
|
|
29
|
+
### Методы бота
|
|
30
|
+
|
|
31
|
+
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
32
|
+
|
|
33
|
+
- `get_my_info`
|
|
34
|
+
- `edit_my_info(**extra)`
|
|
35
|
+
- `set_my_commands(commands)`
|
|
36
|
+
- `delete_my_commands`
|
|
37
|
+
|
|
38
|
+
`set_my_commands(commands)` это короткий хелпер над `edit_my_info(commands: ...)`.
|
|
39
|
+
Он принимает массив команд и отправляет его в поле `commands` профиля бота.
|
|
40
|
+
|
|
41
|
+
Ожидается массив хэшей с данными команды, например:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
api.set_my_commands([
|
|
45
|
+
{ name: "start", description: "Запустить бота" },
|
|
46
|
+
{ name: "help", description: "Показать справку" }
|
|
47
|
+
])
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`delete_my_commands` делает то же самое, но передаёт пустой массив и тем самым очищает список команд.
|
|
51
|
+
|
|
52
|
+
Соответствующие HTTP-маршруты:
|
|
53
|
+
|
|
54
|
+
- `GET /me`
|
|
55
|
+
- `PATCH /me`
|
|
56
|
+
|
|
57
|
+
Типовые сценарии:
|
|
58
|
+
|
|
59
|
+
- получить текущий профиль бота;
|
|
60
|
+
- обновить имя, описание, аватар и команды бота;
|
|
61
|
+
- опубликовать или очистить подсказки команд для пользователей.
|
|
62
|
+
|
|
63
|
+
### Методы чатов
|
|
64
|
+
|
|
65
|
+
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
66
|
+
|
|
67
|
+
- `get_all_chats(**extra)`
|
|
68
|
+
- `get_chat(chat_id)`
|
|
69
|
+
- `get_chat_by_link(chat_link)`
|
|
70
|
+
- `edit_chat_info(chat_id, **extra)`
|
|
71
|
+
- `get_chat_membership(chat_id)`
|
|
72
|
+
- `get_chat_admins(chat_id)`
|
|
73
|
+
- `add_chat_members(chat_id, user_ids)`
|
|
74
|
+
- `get_chat_members(chat_id, **extra)`
|
|
75
|
+
- `remove_chat_member(chat_id, user_id)`
|
|
76
|
+
- `get_pinned_message(chat_id)`
|
|
77
|
+
- `pin_message(chat_id, message_id, **extra)`
|
|
78
|
+
- `unpin_message(chat_id)`
|
|
79
|
+
- `send_action(chat_id, action)`
|
|
80
|
+
- `leave_chat(chat_id)`
|
|
81
|
+
|
|
82
|
+
Соответствующие HTTP-маршруты:
|
|
83
|
+
|
|
84
|
+
- `GET /chats`
|
|
85
|
+
- `GET /chats/{chat_id}`
|
|
86
|
+
- `GET /chats/{chat_link}`
|
|
87
|
+
- `PATCH /chats/{chat_id}`
|
|
88
|
+
- `GET /chats/{chat_id}/members/me`
|
|
89
|
+
- `GET /chats/{chat_id}/members/admins`
|
|
90
|
+
- `POST /chats/{chat_id}/members`
|
|
91
|
+
- `GET /chats/{chat_id}/members`
|
|
92
|
+
- `DELETE /chats/{chat_id}/members`
|
|
93
|
+
- `GET /chats/{chat_id}/pin`
|
|
94
|
+
- `PUT /chats/{chat_id}/pin`
|
|
95
|
+
- `DELETE /chats/{chat_id}/pin`
|
|
96
|
+
- `POST /chats/{chat_id}/actions`
|
|
97
|
+
- `DELETE /chats/{chat_id}/members/me`
|
|
98
|
+
|
|
99
|
+
Типовые сценарии:
|
|
100
|
+
|
|
101
|
+
- получить список чатов, доступных боту;
|
|
102
|
+
- найти чат по идентификатору или публичной ссылке;
|
|
103
|
+
- изменить заголовок, иконку и метаданные чата;
|
|
104
|
+
- управлять участниками и администраторами;
|
|
105
|
+
- читать, устанавливать и снимать закреплённые сообщения;
|
|
106
|
+
- отправлять статус набора текста и другие действия отправителя;
|
|
107
|
+
- выходить из чата.
|
|
108
|
+
|
|
109
|
+
### Методы сообщений
|
|
110
|
+
|
|
111
|
+
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
112
|
+
|
|
113
|
+
- `send_message_to_chat(chat_id, text, **extra)`
|
|
114
|
+
- `send_message_to_user(user_id, text, **extra)`
|
|
115
|
+
- `get_messages(chat_id, **extra)`
|
|
116
|
+
- `get_message(message_id)`
|
|
117
|
+
- `edit_message(message_id, **extra)`
|
|
118
|
+
- `delete_message(message_id, **extra)`
|
|
119
|
+
- `answer_on_callback(callback_id, **extra)`
|
|
120
|
+
|
|
121
|
+
Соответствующие HTTP-маршруты:
|
|
122
|
+
|
|
123
|
+
- `POST /messages`
|
|
124
|
+
- `GET /messages`
|
|
125
|
+
- `GET /messages/{message_id}`
|
|
126
|
+
- `PUT /messages`
|
|
127
|
+
- `DELETE /messages`
|
|
128
|
+
- `POST /answers`
|
|
129
|
+
|
|
130
|
+
Поддерживаемые возможности:
|
|
131
|
+
|
|
132
|
+
- отправка обычного текста в чат или напрямую пользователю;
|
|
133
|
+
- дополнительный payload для форматирования, reply-ссылок и вложений;
|
|
134
|
+
- редактирование и удаление сообщений;
|
|
135
|
+
- ответы на callback-кнопки;
|
|
136
|
+
- автоматический повтор запроса, если вложение после загрузки ещё не готово.
|
|
137
|
+
|
|
138
|
+
### Методы подписок
|
|
139
|
+
|
|
140
|
+
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
141
|
+
|
|
142
|
+
- `get_subscriptions`
|
|
143
|
+
- `subscribe(url, update_types: nil, secret: nil)`
|
|
144
|
+
- `unsubscribe(url)`
|
|
145
|
+
- `poll_updates(types = [], marker: nil, timeout: 20, retry_interval: 5, read_timeout: nil, &block)`
|
|
146
|
+
|
|
147
|
+
Соответствующие HTTP-маршруты:
|
|
148
|
+
|
|
149
|
+
- `GET /subscriptions`
|
|
150
|
+
- `POST /subscriptions`
|
|
151
|
+
- `DELETE /subscriptions`
|
|
152
|
+
- `GET /updates`
|
|
153
|
+
|
|
154
|
+
Типовые сценарии:
|
|
155
|
+
|
|
156
|
+
- получить список активных webhook-подписок бота;
|
|
157
|
+
- создать webhook-подписку на нужные типы обновлений;
|
|
158
|
+
- удалить подписку по URL webhook;
|
|
159
|
+
- использовать polling через `poll_updates`, если webhook не нужен.
|
|
160
|
+
|
|
161
|
+
Пример long polling:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
poller = api.poll_updates(%w[message_created], timeout: 20)
|
|
165
|
+
|
|
166
|
+
poller.each do |update|
|
|
167
|
+
puts update["update_type"]
|
|
168
|
+
# poller.stop if нужно остановить цикл
|
|
169
|
+
end
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`poll_updates` автоматически:
|
|
173
|
+
|
|
174
|
+
- передаёт `marker` между запросами;
|
|
175
|
+
- поднимает HTTP `read_timeout` выше API `timeout`;
|
|
176
|
+
- повторяет запрос после временных сетевых ошибок, `429` и `5xx`.
|
|
177
|
+
|
|
178
|
+
### Методы загрузки
|
|
179
|
+
|
|
180
|
+
Методы Ruby, доступные через `MaxApiClient::Api`:
|
|
181
|
+
|
|
182
|
+
- `upload_image(options)`
|
|
183
|
+
- `upload_video(options)`
|
|
184
|
+
- `upload_audio(options)`
|
|
185
|
+
- `upload_file(options)`
|
|
186
|
+
|
|
187
|
+
Связанный HTTP-маршрут:
|
|
188
|
+
|
|
189
|
+
- `POST /uploads`
|
|
190
|
+
|
|
191
|
+
Вспомогательные классы вложений:
|
|
192
|
+
|
|
193
|
+
- `ImageAttachment`
|
|
194
|
+
- `VideoAttachment`
|
|
195
|
+
- `AudioAttachment`
|
|
196
|
+
- `FileAttachment`
|
|
197
|
+
- `StickerAttachment`
|
|
198
|
+
- `LocationAttachment`
|
|
199
|
+
- `ShareAttachment`
|
|
200
|
+
|
|
201
|
+
### Доступ к Raw API
|
|
202
|
+
|
|
203
|
+
Низкоуровневый доступ через `api.raw` поддерживает:
|
|
204
|
+
|
|
205
|
+
- `get`
|
|
206
|
+
- `post`
|
|
207
|
+
- `put`
|
|
208
|
+
- `patch`
|
|
209
|
+
- `delete`
|
|
210
|
+
|
|
211
|
+
## Логирование
|
|
212
|
+
|
|
213
|
+
Если нужен отладочный лог HTTP-обмена, можно задать глобальный логгер:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
MaxApiClient.logger = Logger.new($stdout)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Либо передать логгер в конкретный клиент:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
api = MaxApiClient::Api.new(token: ENV.fetch("MAX_BOT_TOKEN"), logger: Logger.new($stdout))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Если логгер задан, клиент пишет в `debug` данные запроса и ответа.
|
|
226
|
+
|
|
227
|
+
## Разработка
|
|
228
|
+
|
|
229
|
+
Склонируйте репозиторий и установите зависимости:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
git clone git@github.com:Ziaw/max_api_client.git
|
|
233
|
+
cd max_api_client
|
|
234
|
+
bundle install
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Полезные команды:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
bundle exec rake test
|
|
241
|
+
bin/console
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
## Релиз
|
|
246
|
+
|
|
247
|
+
Релиз публикуется через GitHub Releases и GitHub Actions.
|
|
248
|
+
|
|
249
|
+
Перед релизом:
|
|
250
|
+
|
|
251
|
+
1. Обновите версию в `lib/max_api_client/version.rb`.
|
|
252
|
+
2. Перенесите изменения из `Unreleased` в `CHANGELOG.md`.
|
|
253
|
+
3. Закоммитьте изменения в `master`.
|
|
254
|
+
|
|
255
|
+
## Приоритеты реализации
|
|
256
|
+
|
|
257
|
+
Рекомендуемый порядок развития Ruby-клиента:
|
|
258
|
+
|
|
259
|
+
1. HTTP-клиент и слой ошибок.
|
|
260
|
+
2. Интерфейс raw-запросов.
|
|
261
|
+
3. Высокоуровневая обёртка `Api` для bot, chat, message и update endpoints.
|
|
262
|
+
4. Механизм загрузки файлов и объекты вложений. (вы находитесь здесь)
|
|
263
|
+
5. Опциональный bot framework с polling, context и middleware.
|
|
264
|
+
|
|
265
|
+
## Источники
|
|
266
|
+
|
|
267
|
+
- Официальная документация Max Bot API: <https://dev.max.ru/>
|
|
268
|
+
- TypeScript reference client: <https://github.com/max-messenger/max-bot-api-client-ts>
|
|
269
|
+
|
|
270
|
+
## Лицензия
|
|
271
|
+
|
|
272
|
+
Проект распространяется по лицензии MIT. См. [`LICENSE.txt`](./LICENSE.txt).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxApiClient
|
|
4
|
+
# High-level convenience wrapper over grouped Max Bot API endpoints.
|
|
5
|
+
class Api
|
|
6
|
+
attr_reader :raw, :upload, :client
|
|
7
|
+
|
|
8
|
+
# rubocop:disable Metrics/ParameterLists
|
|
9
|
+
def initialize(token:, base_url: Client::DEFAULT_BASE_URL, adapter: nil, open_timeout: nil, read_timeout: nil,
|
|
10
|
+
logger: nil)
|
|
11
|
+
@client = Client.new(
|
|
12
|
+
token:,
|
|
13
|
+
base_url:,
|
|
14
|
+
adapter:,
|
|
15
|
+
open_timeout:,
|
|
16
|
+
read_timeout:,
|
|
17
|
+
logger:
|
|
18
|
+
)
|
|
19
|
+
@raw = RawApi.new(client)
|
|
20
|
+
@upload = Upload.new(self)
|
|
21
|
+
end
|
|
22
|
+
# rubocop:enable Metrics/ParameterLists
|
|
23
|
+
|
|
24
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
25
|
+
def get_my_info
|
|
26
|
+
raw.bots.get_my_info
|
|
27
|
+
end
|
|
28
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
29
|
+
|
|
30
|
+
def edit_my_info(**extra)
|
|
31
|
+
raw.bots.edit_my_info(**extra)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
35
|
+
def set_my_commands(commands)
|
|
36
|
+
edit_my_info(commands:)
|
|
37
|
+
end
|
|
38
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
39
|
+
|
|
40
|
+
def delete_my_commands
|
|
41
|
+
edit_my_info(commands: [])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_all_chats(**extra)
|
|
45
|
+
raw.chats.get_all(**extra)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def get_chat(chat_id)
|
|
49
|
+
raw.chats.get_by_id(chat_id:)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get_chat_by_link(chat_link)
|
|
53
|
+
raw.chats.get_by_link(chat_link:)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def edit_chat_info(chat_id, **extra)
|
|
57
|
+
raw.chats.edit(chat_id:, **extra)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def get_chat_membership(chat_id)
|
|
61
|
+
raw.chats.get_chat_membership(chat_id:)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_chat_admins(chat_id)
|
|
65
|
+
raw.chats.get_chat_admins(chat_id:)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_chat_members(chat_id, user_ids)
|
|
69
|
+
raw.chats.add_chat_members(chat_id:, user_ids:)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get_chat_members(chat_id, **extra)
|
|
73
|
+
raw.chats.get_chat_members(chat_id:, **csv_query(extra, :user_ids))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def remove_chat_member(chat_id, user_id, block: nil)
|
|
77
|
+
raw.chats.remove_chat_member(chat_id:, user_id:, block:)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_pinned_message(chat_id)
|
|
81
|
+
raw.chats.get_pinned_message(chat_id:)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def pin_message(chat_id, message_id, **extra)
|
|
85
|
+
raw.chats.pin_message(chat_id:, message_id:, notify: extra[:notify])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def unpin_message(chat_id)
|
|
89
|
+
raw.chats.unpin_message(chat_id:)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def send_action(chat_id, action)
|
|
93
|
+
raw.chats.send_action(chat_id:, action:)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def leave_chat(chat_id)
|
|
97
|
+
raw.chats.leave_chat(chat_id:)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def send_message_to_chat(chat_id, text, **extra)
|
|
101
|
+
message_from(raw.messages.send(chat_id:, text:, **extra))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def send_message_to_user(user_id, text, **extra)
|
|
105
|
+
message_from(raw.messages.send(user_id:, text:, **extra))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def get_messages(chat_id, **extra)
|
|
109
|
+
raw.messages.get(chat_id:, **csv_query(extra, :message_ids))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def get_message(message_id)
|
|
113
|
+
raw.messages.get_by_id(message_id:)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def edit_message(message_id, **extra)
|
|
117
|
+
raw.messages.edit(message_id:, **extra)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def delete_message(message_id, **extra)
|
|
121
|
+
raw.messages.delete(message_id:, **extra)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def answer_on_callback(callback_id, **extra)
|
|
125
|
+
raw.messages.answer_on_callback(callback_id:, **extra)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
129
|
+
def get_subscriptions
|
|
130
|
+
raw.subscriptions.get_subscriptions
|
|
131
|
+
end
|
|
132
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
133
|
+
|
|
134
|
+
def subscribe(url, update_types: nil, secret: nil)
|
|
135
|
+
raw.subscriptions.subscribe(url:, update_types:, secret:)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def unsubscribe(url)
|
|
139
|
+
raw.subscriptions.unsubscribe(url:)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def poll_updates(types = [], marker: nil, timeout: Polling::DEFAULT_TIMEOUT,
|
|
143
|
+
retry_interval: Polling::DEFAULT_RETRY_INTERVAL, read_timeout: nil, &block)
|
|
144
|
+
poller = Polling.new(
|
|
145
|
+
self,
|
|
146
|
+
types:,
|
|
147
|
+
marker:,
|
|
148
|
+
timeout:,
|
|
149
|
+
retry_interval:,
|
|
150
|
+
read_timeout:
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return poller.each unless block
|
|
154
|
+
|
|
155
|
+
poller.each(&block)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def upload_image(options)
|
|
159
|
+
data = upload.image(**options)
|
|
160
|
+
ImageAttachment.new(token: data[:token], photos: data[:photos], url: data[:url] || data["url"])
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def upload_video(options)
|
|
164
|
+
data = upload.video(**options)
|
|
165
|
+
VideoAttachment.new(token: data[:token] || data["token"])
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def upload_audio(options)
|
|
169
|
+
data = upload.audio(**options)
|
|
170
|
+
AudioAttachment.new(token: data[:token] || data["token"])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def upload_file(options)
|
|
174
|
+
data = upload.file(**options)
|
|
175
|
+
FileAttachment.new(token: data[:token] || data["token"])
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def normalize_types(types)
|
|
181
|
+
return types unless types.is_a?(Array)
|
|
182
|
+
|
|
183
|
+
types.join(",")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def csv_query(query, key)
|
|
187
|
+
query.merge(key => normalize_types(query[key]))
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def message_from(response)
|
|
191
|
+
response.fetch("message") { response.fetch(:message) }
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxApiClient
|
|
4
|
+
# Base type for all outgoing attachment payload wrappers.
|
|
5
|
+
class Attachment
|
|
6
|
+
def to_h
|
|
7
|
+
raise NotImplementedError, "#{self.class} must implement #to_h"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Shared attachment implementation for upload-backed media objects.
|
|
12
|
+
class MediaAttachment < Attachment
|
|
13
|
+
attr_reader :token
|
|
14
|
+
|
|
15
|
+
def initialize(token: nil)
|
|
16
|
+
super()
|
|
17
|
+
@token = token
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def payload
|
|
21
|
+
{ token: token }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Attachment wrapper for uploaded or remote images.
|
|
26
|
+
class ImageAttachment < MediaAttachment
|
|
27
|
+
attr_reader :photos, :url
|
|
28
|
+
|
|
29
|
+
def initialize(token: nil, photos: nil, url: nil)
|
|
30
|
+
super(token:)
|
|
31
|
+
@photos = photos
|
|
32
|
+
@url = url
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def payload
|
|
36
|
+
return { token: token } if token
|
|
37
|
+
return { url: url } if url
|
|
38
|
+
|
|
39
|
+
{ photos: photos }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_h
|
|
43
|
+
{ type: "image", payload: }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Attachment wrapper for uploaded videos.
|
|
48
|
+
class VideoAttachment < MediaAttachment
|
|
49
|
+
def to_h
|
|
50
|
+
{ type: "video", payload: }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Attachment wrapper for uploaded audio files.
|
|
55
|
+
class AudioAttachment < MediaAttachment
|
|
56
|
+
def to_h
|
|
57
|
+
{ type: "audio", payload: }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Attachment wrapper for generic uploaded files.
|
|
62
|
+
class FileAttachment < MediaAttachment
|
|
63
|
+
def to_h
|
|
64
|
+
{ type: "file", payload: }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Attachment wrapper for sticker references.
|
|
69
|
+
class StickerAttachment < Attachment
|
|
70
|
+
attr_reader :code
|
|
71
|
+
|
|
72
|
+
def initialize(code:)
|
|
73
|
+
super()
|
|
74
|
+
@code = code
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def to_h
|
|
78
|
+
{ type: "sticker", payload: { code: } }
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Attachment wrapper for geo coordinates.
|
|
83
|
+
class LocationAttachment < Attachment
|
|
84
|
+
attr_reader :longitude, :latitude
|
|
85
|
+
|
|
86
|
+
def initialize(lon:, lat:)
|
|
87
|
+
super()
|
|
88
|
+
@longitude = lon
|
|
89
|
+
@latitude = lat
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def to_h
|
|
93
|
+
{ type: "location", latitude:, longitude: }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Attachment wrapper for shared links or tokens.
|
|
98
|
+
class ShareAttachment < Attachment
|
|
99
|
+
attr_reader :url, :token
|
|
100
|
+
|
|
101
|
+
def initialize(url: nil, token: nil)
|
|
102
|
+
super()
|
|
103
|
+
@url = url
|
|
104
|
+
@token = token
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_h
|
|
108
|
+
{ type: "share", payload: { url:, token: } }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MaxApiClient
|
|
4
|
+
# Shared HTTP helper for raw API groups.
|
|
5
|
+
class BaseApi
|
|
6
|
+
HTTP_METHODS = %i[get post put patch delete].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
HTTP_METHODS.each do |name|
|
|
13
|
+
define_method(name) do |path, **options|
|
|
14
|
+
call_api(name, path, **options)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :client
|
|
21
|
+
|
|
22
|
+
def call_api(http_method, path, **options)
|
|
23
|
+
result = client.call(
|
|
24
|
+
method: http_method,
|
|
25
|
+
path:,
|
|
26
|
+
**options
|
|
27
|
+
)
|
|
28
|
+
raise ApiError.new(result[:status], result[:data]) unless result[:status] == 200
|
|
29
|
+
|
|
30
|
+
result[:data]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def compact_nil(hash)
|
|
34
|
+
hash.compact
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|