kapso-client-ruby 1.0.1 → 1.0.2

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -81
  3. data/CHANGELOG.md +262 -91
  4. data/Gemfile +20 -20
  5. data/RAILS_INTEGRATION.md +477 -477
  6. data/README.md +1053 -752
  7. data/Rakefile +40 -40
  8. data/TEMPLATE_TOOLS_GUIDE.md +120 -120
  9. data/WHATSAPP_24_HOUR_GUIDE.md +133 -133
  10. data/examples/advanced_features.rb +352 -349
  11. data/examples/advanced_messaging.rb +241 -0
  12. data/examples/basic_messaging.rb +139 -136
  13. data/examples/enhanced_interactive.rb +400 -0
  14. data/examples/flows_usage.rb +307 -0
  15. data/examples/interactive_messages.rb +343 -0
  16. data/examples/media_management.rb +256 -253
  17. data/examples/rails/jobs.rb +387 -387
  18. data/examples/rails/models.rb +239 -239
  19. data/examples/rails/notifications_controller.rb +226 -226
  20. data/examples/template_management.rb +393 -390
  21. data/kapso-ruby-logo.jpg +0 -0
  22. data/lib/kapso_client_ruby/client.rb +321 -316
  23. data/lib/kapso_client_ruby/errors.rb +348 -329
  24. data/lib/kapso_client_ruby/rails/generators/install_generator.rb +75 -75
  25. data/lib/kapso_client_ruby/rails/generators/templates/env.erb +20 -20
  26. data/lib/kapso_client_ruby/rails/generators/templates/initializer.rb.erb +32 -32
  27. data/lib/kapso_client_ruby/rails/generators/templates/message_service.rb.erb +137 -137
  28. data/lib/kapso_client_ruby/rails/generators/templates/webhook_controller.rb.erb +61 -61
  29. data/lib/kapso_client_ruby/rails/railtie.rb +54 -54
  30. data/lib/kapso_client_ruby/rails/service.rb +188 -188
  31. data/lib/kapso_client_ruby/rails/tasks.rake +166 -166
  32. data/lib/kapso_client_ruby/resources/calls.rb +172 -172
  33. data/lib/kapso_client_ruby/resources/contacts.rb +190 -190
  34. data/lib/kapso_client_ruby/resources/conversations.rb +103 -103
  35. data/lib/kapso_client_ruby/resources/flows.rb +382 -0
  36. data/lib/kapso_client_ruby/resources/media.rb +205 -205
  37. data/lib/kapso_client_ruby/resources/messages.rb +760 -380
  38. data/lib/kapso_client_ruby/resources/phone_numbers.rb +85 -85
  39. data/lib/kapso_client_ruby/resources/templates.rb +283 -283
  40. data/lib/kapso_client_ruby/types.rb +348 -262
  41. data/lib/kapso_client_ruby/version.rb +5 -5
  42. data/lib/kapso_client_ruby.rb +75 -74
  43. data/scripts/.env.example +17 -17
  44. data/scripts/kapso_template_finder.rb +91 -91
  45. data/scripts/sdk_setup.rb +404 -404
  46. data/scripts/test.rb +60 -60
  47. metadata +12 -3
@@ -1,263 +1,349 @@
1
- # frozen_string_literal: true
2
-
3
- module KapsoClientRuby
4
- module Types
5
- # Message status types
6
- MESSAGE_STATUSES = %w[accepted held_for_quality_assessment].freeze
7
-
8
- # Template status types
9
- TEMPLATE_STATUSES = %w[APPROVED PENDING REJECTED PAUSED IN_APPEAL DISABLED].freeze
10
-
11
- # Template categories
12
- TEMPLATE_CATEGORIES = %w[MARKETING UTILITY AUTHENTICATION UNKNOWN].freeze
13
-
14
- # Media types
15
- MEDIA_TYPES = %w[image audio video document sticker].freeze
16
-
17
- # Interactive message types
18
- INTERACTIVE_TYPES = %w[button list product product_list flow address location_request call_permission].freeze
19
-
20
- # Message response structure
21
- class SendMessageResponse
22
- attr_reader :messaging_product, :contacts, :messages
23
-
24
- def initialize(data)
25
- @messaging_product = data['messaging_product']
26
- @contacts = data['contacts']&.map { |c| MessageContact.new(c) } || []
27
- @messages = data['messages']&.map { |m| MessageInfo.new(m) } || []
28
- end
29
- end
30
-
31
- class MessageContact
32
- attr_reader :input, :wa_id
33
-
34
- def initialize(data)
35
- @input = data['input']
36
- @wa_id = data['wa_id']
37
- end
38
- end
39
-
40
- class MessageInfo
41
- attr_reader :id, :message_status
42
-
43
- def initialize(data)
44
- @id = data['id']
45
- @message_status = data['message_status']
46
- end
47
- end
48
-
49
- # Media response structures
50
- class MediaUploadResponse
51
- attr_reader :id
52
-
53
- def initialize(data)
54
- @id = data['id']
55
- end
56
- end
57
-
58
- class MediaMetadataResponse
59
- attr_reader :messaging_product, :url, :mime_type, :sha256, :file_size, :id
60
-
61
- def initialize(data)
62
- @messaging_product = data['messaging_product']
63
- @url = data['url']
64
- @mime_type = data['mime_type']
65
- @sha256 = data['sha256']
66
- @file_size = data['file_size']
67
- @id = data['id']
68
- end
69
- end
70
-
71
- # Template structures
72
- class MessageTemplate
73
- attr_reader :id, :name, :category, :language, :status, :components,
74
- :quality_score_category, :warnings, :previous_category,
75
- :library_template_name, :last_updated_time
76
-
77
- def initialize(data)
78
- @id = data['id']
79
- @name = data['name']
80
- @category = data['category']
81
- @language = data['language']
82
- @status = data['status']
83
- @components = data['components']
84
- @quality_score_category = data['quality_score_category']
85
- @warnings = data['warnings']
86
- @previous_category = data['previous_category']
87
- @library_template_name = data['library_template_name']
88
- @last_updated_time = data['last_updated_time']
89
- end
90
- end
91
-
92
- class TemplateCreateResponse
93
- attr_reader :id, :status, :category
94
-
95
- def initialize(data)
96
- @id = data['id']
97
- @status = data['status']
98
- @category = data['category']
99
- end
100
- end
101
-
102
- # Paging structures
103
- class GraphPaging
104
- attr_reader :cursors, :next_page, :previous_page
105
-
106
- def initialize(data)
107
- @cursors = data['cursors'] || {}
108
- @next_page = data['next']
109
- @previous_page = data['previous']
110
- end
111
-
112
- def before
113
- cursors['before']
114
- end
115
-
116
- def after
117
- cursors['after']
118
- end
119
- end
120
-
121
- class PagedResponse
122
- attr_reader :data, :paging
123
-
124
- def initialize(data, item_class = nil)
125
- @data = if item_class && data['data'].is_a?(Array)
126
- data['data'].map { |item| item_class.new(item) }
127
- else
128
- data['data'] || []
129
- end
130
- @paging = GraphPaging.new(data['paging'] || {})
131
- end
132
- end
133
-
134
- # Success response
135
- class GraphSuccessResponse
136
- attr_reader :success
137
-
138
- def initialize(data = nil)
139
- @success = data.nil? || data['success'] || true
140
- end
141
-
142
- def success?
143
- @success
144
- end
145
- end
146
-
147
- # Business profile structures
148
- class BusinessProfileEntry
149
- attr_reader :about, :address, :description, :email, :websites,
150
- :vertical, :profile_picture_url, :profile_picture_handle
151
-
152
- def initialize(data)
153
- @about = data['about']
154
- @address = data['address']
155
- @description = data['description']
156
- @email = data['email']
157
- @websites = data['websites']
158
- @vertical = data['vertical']
159
- @profile_picture_url = data['profile_picture_url']
160
- @profile_picture_handle = data['profile_picture_handle']
161
- end
162
- end
163
-
164
- # Conversation structures
165
- class ConversationRecord
166
- attr_reader :id, :phone_number, :phone_number_id, :status, :last_active_at,
167
- :kapso, :metadata
168
-
169
- def initialize(data)
170
- @id = data['id']
171
- @phone_number = data['phone_number']
172
- @phone_number_id = data['phone_number_id']
173
- @status = data['status']
174
- @last_active_at = data['last_active_at']
175
- @kapso = data['kapso']
176
- @metadata = data['metadata']
177
- end
178
- end
179
-
180
- # Contact structures
181
- class ContactRecord
182
- attr_reader :wa_id, :phone_number, :profile_name, :metadata
183
-
184
- def initialize(data)
185
- @wa_id = data['wa_id']
186
- @phone_number = data['phone_number']
187
- @profile_name = data['profile_name']
188
- @metadata = data['metadata']
189
- end
190
- end
191
-
192
- # Call structures
193
- class CallRecord
194
- attr_reader :id, :direction, :status, :duration_seconds, :started_at,
195
- :ended_at, :whatsapp_conversation_id, :whatsapp_contact_id
196
-
197
- def initialize(data)
198
- @id = data['id']
199
- @direction = data['direction']
200
- @status = data['status']
201
- @duration_seconds = data['duration_seconds']
202
- @started_at = data['started_at']
203
- @ended_at = data['ended_at']
204
- @whatsapp_conversation_id = data['whatsapp_conversation_id']
205
- @whatsapp_contact_id = data['whatsapp_contact_id']
206
- end
207
- end
208
-
209
- class CallConnectResponse
210
- attr_reader :messaging_product, :calls
211
-
212
- def initialize(data)
213
- @messaging_product = data['messaging_product']
214
- @calls = data['calls'] || []
215
- end
216
- end
217
-
218
- class CallActionResponse < GraphSuccessResponse
219
- attr_reader :messaging_product
220
-
221
- def initialize(data)
222
- super(data)
223
- @messaging_product = data['messaging_product']
224
- end
225
- end
226
-
227
- # Utility method to convert snake_case to camelCase for API requests
228
- def self.to_camel_case(str)
229
- str.split('_').map.with_index { |word, i| i == 0 ? word : word.capitalize }.join
230
- end
231
-
232
- # Utility method to convert camelCase to snake_case for Ruby conventions
233
- def self.to_snake_case(str)
234
- str.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
235
- end
236
-
237
- # Deep convert hash keys from camelCase to snake_case
238
- def self.deep_snake_case_keys(obj)
239
- case obj
240
- when Hash
241
- obj.transform_keys { |key| to_snake_case(key.to_s) }
242
- .transform_values { |value| deep_snake_case_keys(value) }
243
- when Array
244
- obj.map { |item| deep_snake_case_keys(item) }
245
- else
246
- obj
247
- end
248
- end
249
-
250
- # Deep convert hash keys from snake_case to camelCase
251
- def self.deep_camel_case_keys(obj)
252
- case obj
253
- when Hash
254
- obj.transform_keys { |key| to_camel_case(key.to_s) }
255
- .transform_values { |value| deep_camel_case_keys(value) }
256
- when Array
257
- obj.map { |item| deep_camel_case_keys(item) }
258
- else
259
- obj
260
- end
261
- end
262
- end
1
+ # frozen_string_literal: true
2
+
3
+ module KapsoClientRuby
4
+ module Types
5
+ # Message status types
6
+ MESSAGE_STATUSES = %w[accepted held_for_quality_assessment].freeze
7
+
8
+ # Template status types
9
+ TEMPLATE_STATUSES = %w[APPROVED PENDING REJECTED PAUSED IN_APPEAL DISABLED].freeze
10
+
11
+ # Template categories
12
+ TEMPLATE_CATEGORIES = %w[MARKETING UTILITY AUTHENTICATION UNKNOWN].freeze
13
+
14
+ # Media types
15
+ MEDIA_TYPES = %w[image audio video document sticker].freeze
16
+
17
+ # Interactive message types
18
+ INTERACTIVE_TYPES = %w[button list product product_list flow address location_request call_permission].freeze
19
+
20
+ # Message response structure
21
+ class SendMessageResponse
22
+ attr_reader :messaging_product, :contacts, :messages
23
+
24
+ def initialize(data)
25
+ @messaging_product = data['messaging_product']
26
+ @contacts = data['contacts']&.map { |c| MessageContact.new(c) } || []
27
+ @messages = data['messages']&.map { |m| MessageInfo.new(m) } || []
28
+ end
29
+ end
30
+
31
+ class MessageContact
32
+ attr_reader :input, :wa_id
33
+
34
+ def initialize(data)
35
+ @input = data['input']
36
+ @wa_id = data['wa_id']
37
+ end
38
+ end
39
+
40
+ class MessageInfo
41
+ attr_reader :id, :message_status
42
+
43
+ def initialize(data)
44
+ @id = data['id']
45
+ @message_status = data['message_status']
46
+ end
47
+ end
48
+
49
+ # Media response structures
50
+ class MediaUploadResponse
51
+ attr_reader :id
52
+
53
+ def initialize(data)
54
+ @id = data['id']
55
+ end
56
+ end
57
+
58
+ class MediaMetadataResponse
59
+ attr_reader :messaging_product, :url, :mime_type, :sha256, :file_size, :id
60
+
61
+ def initialize(data)
62
+ @messaging_product = data['messaging_product']
63
+ @url = data['url']
64
+ @mime_type = data['mime_type']
65
+ @sha256 = data['sha256']
66
+ @file_size = data['file_size']
67
+ @id = data['id']
68
+ end
69
+ end
70
+
71
+ # Template structures
72
+ class MessageTemplate
73
+ attr_reader :id, :name, :category, :language, :status, :components,
74
+ :quality_score_category, :warnings, :previous_category,
75
+ :library_template_name, :last_updated_time
76
+
77
+ def initialize(data)
78
+ @id = data['id']
79
+ @name = data['name']
80
+ @category = data['category']
81
+ @language = data['language']
82
+ @status = data['status']
83
+ @components = data['components']
84
+ @quality_score_category = data['quality_score_category']
85
+ @warnings = data['warnings']
86
+ @previous_category = data['previous_category']
87
+ @library_template_name = data['library_template_name']
88
+ @last_updated_time = data['last_updated_time']
89
+ end
90
+ end
91
+
92
+ class TemplateCreateResponse
93
+ attr_reader :id, :status, :category
94
+
95
+ def initialize(data)
96
+ @id = data['id']
97
+ @status = data['status']
98
+ @category = data['category']
99
+ end
100
+ end
101
+
102
+ # Paging structures
103
+ class GraphPaging
104
+ attr_reader :cursors, :next_page, :previous_page
105
+
106
+ def initialize(data)
107
+ @cursors = data['cursors'] || {}
108
+ @next_page = data['next']
109
+ @previous_page = data['previous']
110
+ end
111
+
112
+ def before
113
+ cursors['before']
114
+ end
115
+
116
+ def after
117
+ cursors['after']
118
+ end
119
+ end
120
+
121
+ class PagedResponse
122
+ attr_reader :data, :paging
123
+
124
+ def initialize(data, item_class = nil)
125
+ @data = if item_class && data['data'].is_a?(Array)
126
+ data['data'].map { |item| item_class.new(item) }
127
+ else
128
+ data['data'] || []
129
+ end
130
+ @paging = GraphPaging.new(data['paging'] || {})
131
+ end
132
+ end
133
+
134
+ # Success response
135
+ class GraphSuccessResponse
136
+ attr_reader :success
137
+
138
+ def initialize(data = nil)
139
+ @success = data.nil? || data['success'] || true
140
+ end
141
+
142
+ def success?
143
+ @success
144
+ end
145
+ end
146
+
147
+ # Business profile structures
148
+ class BusinessProfileEntry
149
+ attr_reader :about, :address, :description, :email, :websites,
150
+ :vertical, :profile_picture_url, :profile_picture_handle
151
+
152
+ def initialize(data)
153
+ @about = data['about']
154
+ @address = data['address']
155
+ @description = data['description']
156
+ @email = data['email']
157
+ @websites = data['websites']
158
+ @vertical = data['vertical']
159
+ @profile_picture_url = data['profile_picture_url']
160
+ @profile_picture_handle = data['profile_picture_handle']
161
+ end
162
+ end
163
+
164
+ # Conversation structures
165
+ class ConversationRecord
166
+ attr_reader :id, :phone_number, :phone_number_id, :status, :last_active_at,
167
+ :kapso, :metadata
168
+
169
+ def initialize(data)
170
+ @id = data['id']
171
+ @phone_number = data['phone_number']
172
+ @phone_number_id = data['phone_number_id']
173
+ @status = data['status']
174
+ @last_active_at = data['last_active_at']
175
+ @kapso = data['kapso']
176
+ @metadata = data['metadata']
177
+ end
178
+ end
179
+
180
+ # Contact structures
181
+ class ContactRecord
182
+ attr_reader :wa_id, :phone_number, :profile_name, :metadata
183
+
184
+ def initialize(data)
185
+ @wa_id = data['wa_id']
186
+ @phone_number = data['phone_number']
187
+ @profile_name = data['profile_name']
188
+ @metadata = data['metadata']
189
+ end
190
+ end
191
+
192
+ # Call structures
193
+ class CallRecord
194
+ attr_reader :id, :direction, :status, :duration_seconds, :started_at,
195
+ :ended_at, :whatsapp_conversation_id, :whatsapp_contact_id
196
+
197
+ def initialize(data)
198
+ @id = data['id']
199
+ @direction = data['direction']
200
+ @status = data['status']
201
+ @duration_seconds = data['duration_seconds']
202
+ @started_at = data['started_at']
203
+ @ended_at = data['ended_at']
204
+ @whatsapp_conversation_id = data['whatsapp_conversation_id']
205
+ @whatsapp_contact_id = data['whatsapp_contact_id']
206
+ end
207
+ end
208
+
209
+ class CallConnectResponse
210
+ attr_reader :messaging_product, :calls
211
+
212
+ def initialize(data)
213
+ @messaging_product = data['messaging_product']
214
+ @calls = data['calls'] || []
215
+ end
216
+ end
217
+
218
+ class CallActionResponse < GraphSuccessResponse
219
+ attr_reader :messaging_product
220
+
221
+ def initialize(data)
222
+ super(data)
223
+ @messaging_product = data['messaging_product']
224
+ end
225
+ end
226
+
227
+ # Flow structures
228
+ class FlowResponse
229
+ attr_reader :id, :name, :status, :categories, :validation_errors, :json_version
230
+
231
+ def initialize(data)
232
+ @id = data['id']
233
+ @name = data['name']
234
+ @status = data['status']
235
+ @categories = data['categories'] || []
236
+ @validation_errors = data['validation_errors']
237
+ @json_version = data['json_version']
238
+ end
239
+ end
240
+
241
+ class FlowData < FlowResponse
242
+ attr_reader :endpoint_uri, :preview, :whatsapp_business_account, :application
243
+
244
+ def initialize(data)
245
+ super(data)
246
+ @endpoint_uri = data['endpoint_uri']
247
+ @preview = data['preview']
248
+ @whatsapp_business_account = data['whatsapp_business_account']
249
+ @application = data['application']
250
+ end
251
+ end
252
+
253
+ class FlowAssetResponse
254
+ attr_reader :validation_errors, :success
255
+
256
+ def initialize(data)
257
+ @validation_errors = data['validation_errors'] || []
258
+ @success = data['success'] || validation_errors.empty?
259
+ end
260
+
261
+ def valid?
262
+ validation_errors.empty?
263
+ end
264
+
265
+ def errors
266
+ validation_errors
267
+ end
268
+ end
269
+
270
+ class FlowPreviewResponse
271
+ attr_reader :preview_url, :expires_at
272
+
273
+ def initialize(data)
274
+ preview_data = data['preview'] || {}
275
+ @preview_url = preview_data['preview_url']
276
+ @expires_at = preview_data['expires_at']
277
+ end
278
+ end
279
+
280
+ class FlowEventData
281
+ attr_reader :version, :screen, :data, :flow_token, :action
282
+
283
+ def initialize(data)
284
+ @version = data['version']
285
+ @screen = data['screen']
286
+ @data = data['data'] || {}
287
+ @flow_token = data['flow_token']
288
+ @action = data['action']
289
+ end
290
+ end
291
+
292
+ class FlowScreen
293
+ attr_reader :id, :title, :data, :terminal, :success, :error_message
294
+
295
+ def initialize(data)
296
+ @id = data['id']
297
+ @title = data['title']
298
+ @data = data['data'] || {}
299
+ @terminal = data['terminal'] || false
300
+ @success = data['success']
301
+ @error_message = data['error_message']
302
+ end
303
+
304
+ def terminal?
305
+ @terminal
306
+ end
307
+
308
+ def success?
309
+ @success
310
+ end
311
+ end
312
+
313
+ # Utility method to convert snake_case to camelCase for API requests
314
+ def self.to_camel_case(str)
315
+ str.split('_').map.with_index { |word, i| i == 0 ? word : word.capitalize }.join
316
+ end
317
+
318
+ # Utility method to convert camelCase to snake_case for Ruby conventions
319
+ def self.to_snake_case(str)
320
+ str.gsub(/([A-Z])/, '_\1').downcase.sub(/^_/, '')
321
+ end
322
+
323
+ # Deep convert hash keys from camelCase to snake_case
324
+ def self.deep_snake_case_keys(obj)
325
+ case obj
326
+ when Hash
327
+ obj.transform_keys { |key| to_snake_case(key.to_s) }
328
+ .transform_values { |value| deep_snake_case_keys(value) }
329
+ when Array
330
+ obj.map { |item| deep_snake_case_keys(item) }
331
+ else
332
+ obj
333
+ end
334
+ end
335
+
336
+ # Deep convert hash keys from snake_case to camelCase
337
+ def self.deep_camel_case_keys(obj)
338
+ case obj
339
+ when Hash
340
+ obj.transform_keys { |key| to_camel_case(key.to_s) }
341
+ .transform_values { |value| deep_camel_case_keys(value) }
342
+ when Array
343
+ obj.map { |item| deep_camel_case_keys(item) }
344
+ else
345
+ obj
346
+ end
347
+ end
348
+ end
263
349
  end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module KapsoClientRuby
4
- VERSION = '1.0.1'
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module KapsoClientRuby
4
+ VERSION = '1.0.2'
5
+ end